Add order entities and mocks for client order feature
Introduces new domain entities for order types and one-time orders, along with their positions. Adds a mock OrderRepository to the data_connect package and wires it into the module. Updates localization files for new order flows and refactors Equatable usage for consistency. Also adds a minus icon to the design system.
This commit is contained in:
@@ -245,6 +245,58 @@
|
|||||||
"recurring_desc": "Ongoing Weekly / Monthly Coverage",
|
"recurring_desc": "Ongoing Weekly / Monthly Coverage",
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"permanent_desc": "Long-Term Staffing Placement"
|
"permanent_desc": "Long-Term Staffing Placement"
|
||||||
|
},
|
||||||
|
"rapid": {
|
||||||
|
"title": "RAPID Order",
|
||||||
|
"subtitle": "Emergency staffing in minutes",
|
||||||
|
"urgent_badge": "URGENT",
|
||||||
|
"tell_us": "Tell us what you need",
|
||||||
|
"need_staff": "Need staff urgently?",
|
||||||
|
"type_or_speak": "Type or speak what you need. I'll handle the rest",
|
||||||
|
"example": "Example: ",
|
||||||
|
"hint": "Type or speak... (e.g., \"Need 5 cooks ASAP until 5am\")",
|
||||||
|
"speak": "Speak",
|
||||||
|
"listening": "Listening...",
|
||||||
|
"send": "Send Message",
|
||||||
|
"sending": "Sending...",
|
||||||
|
"success_title": "Request Sent!",
|
||||||
|
"success_message": "We're finding available workers for you right now. You'll be notified as they accept.",
|
||||||
|
"back_to_orders": "Back to Orders"
|
||||||
|
},
|
||||||
|
"one_time": {
|
||||||
|
"title": "One-Time Order",
|
||||||
|
"subtitle": "Single event or shift request",
|
||||||
|
"create_your_order": "Create Your Order",
|
||||||
|
"date_label": "Date",
|
||||||
|
"date_hint": "Select date",
|
||||||
|
"location_label": "Location",
|
||||||
|
"location_hint": "Enter address",
|
||||||
|
"positions_title": "Positions",
|
||||||
|
"add_position": "Add Position",
|
||||||
|
"position_number": "Position $number",
|
||||||
|
"remove": "Remove",
|
||||||
|
"select_role": "Select role",
|
||||||
|
"start_label": "Start",
|
||||||
|
"end_label": "End",
|
||||||
|
"workers_label": "Workers",
|
||||||
|
"lunch_break_label": "Lunch Break",
|
||||||
|
"different_location": "Use different location for this position",
|
||||||
|
"different_location_title": "Different Location",
|
||||||
|
"different_location_hint": "Enter different address",
|
||||||
|
"create_order": "Create Order",
|
||||||
|
"creating": "Creating...",
|
||||||
|
"success_title": "Order Created!",
|
||||||
|
"success_message": "Your shift request has been posted. Workers will start applying soon."
|
||||||
|
},
|
||||||
|
"recurring": {
|
||||||
|
"title": "Recurring Order",
|
||||||
|
"subtitle": "Ongoing weekly/monthly coverage",
|
||||||
|
"placeholder": "Recurring Order Flow (Work in Progress)"
|
||||||
|
},
|
||||||
|
"permanent": {
|
||||||
|
"title": "Permanent Order",
|
||||||
|
"subtitle": "Long-term staffing placement",
|
||||||
|
"placeholder": "Permanent Order Flow (Work in Progress)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,48 @@
|
|||||||
"recurring_desc": "Cobertura Continua Semanal / Mensual",
|
"recurring_desc": "Cobertura Continua Semanal / Mensual",
|
||||||
"permanent": "Permanente",
|
"permanent": "Permanente",
|
||||||
"permanent_desc": "Colocación de Personal a Largo Plazo"
|
"permanent_desc": "Colocación de Personal a Largo Plazo"
|
||||||
|
},
|
||||||
|
"rapid": {
|
||||||
|
"title": "Orden RÁPIDA",
|
||||||
|
"subtitle": "Personal de emergencia en minutos",
|
||||||
|
"urgent_badge": "URGENTE",
|
||||||
|
"tell_us": "Dinos qué necesitas",
|
||||||
|
"need_staff": "¿Necesitas personal urgentemente?",
|
||||||
|
"type_or_speak": "Escribe o habla lo que necesitas. Yo me encargo del resto",
|
||||||
|
"example": "Ejemplo: ",
|
||||||
|
"hint": "Escribe o habla... (ej., \"Necesito 5 cocineros YA hasta las 5am\")",
|
||||||
|
"speak": "Hablar",
|
||||||
|
"listening": "Escuchando...",
|
||||||
|
"send": "Enviar Mensaje",
|
||||||
|
"sending": "Enviando...",
|
||||||
|
"success_title": "¡Solicitud Enviada!",
|
||||||
|
"success_message": "Estamos encontrando trabajadores disponibles para ti ahora mismo. Te notificaremos cuando acepten.",
|
||||||
|
"back_to_orders": "Volver a Órdenes"
|
||||||
|
},
|
||||||
|
"one_time": {
|
||||||
|
"title": "Orden Única Vez",
|
||||||
|
"subtitle": "Evento único o petición de turno",
|
||||||
|
"create_your_order": "Crea Tu Orden",
|
||||||
|
"date_label": "Fecha",
|
||||||
|
"date_hint": "Seleccionar fecha",
|
||||||
|
"location_label": "Ubicación",
|
||||||
|
"location_hint": "Ingresar dirección",
|
||||||
|
"positions_title": "Posiciones",
|
||||||
|
"add_position": "Añadir Posición",
|
||||||
|
"position_number": "Posición $number",
|
||||||
|
"remove": "Eliminar",
|
||||||
|
"select_role": "Seleccionar rol",
|
||||||
|
"start_label": "Inicio",
|
||||||
|
"end_label": "Fin",
|
||||||
|
"workers_label": "Trabajadores",
|
||||||
|
"lunch_break_label": "Descanso para Almuerzo",
|
||||||
|
"different_location": "Usar ubicación diferente para esta posición",
|
||||||
|
"different_location_title": "Ubicación Diferente",
|
||||||
|
"different_location_hint": "Ingresar dirección diferente",
|
||||||
|
"create_order": "Crear Orden",
|
||||||
|
"creating": "Creando...",
|
||||||
|
"success_title": "¡Orden Creada!",
|
||||||
|
"success_message": "Tu solicitud de turno ha sido publicada. Los trabajadores comenzarán a postularse pronto."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
///
|
///
|
||||||
/// TODO: These mocks currently do not implement any specific interfaces.
|
/// TODO: These mocks currently do not implement any specific interfaces.
|
||||||
/// They will implement interfaces defined in feature packages once those are created.
|
/// They will implement interfaces defined in feature packages once those are created.
|
||||||
|
library;
|
||||||
|
|
||||||
export 'src/mocks/auth_repository_mock.dart';
|
export 'src/mocks/auth_repository_mock.dart';
|
||||||
export 'src/mocks/staff_repository_mock.dart';
|
export 'src/mocks/staff_repository_mock.dart';
|
||||||
@@ -15,6 +16,7 @@ export 'src/mocks/rating_repository_mock.dart';
|
|||||||
export 'src/mocks/support_repository_mock.dart';
|
export 'src/mocks/support_repository_mock.dart';
|
||||||
export 'src/mocks/home_repository_mock.dart';
|
export 'src/mocks/home_repository_mock.dart';
|
||||||
export 'src/mocks/business_repository_mock.dart';
|
export 'src/mocks/business_repository_mock.dart';
|
||||||
|
export 'src/mocks/order_repository_mock.dart';
|
||||||
export 'src/data_connect_module.dart';
|
export 'src/data_connect_module.dart';
|
||||||
|
|
||||||
// Export the generated Data Connect SDK
|
// Export the generated Data Connect SDK
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter_modular/flutter_modular.dart';
|
|||||||
import 'mocks/auth_repository_mock.dart';
|
import 'mocks/auth_repository_mock.dart';
|
||||||
import 'mocks/business_repository_mock.dart';
|
import 'mocks/business_repository_mock.dart';
|
||||||
import 'mocks/home_repository_mock.dart';
|
import 'mocks/home_repository_mock.dart';
|
||||||
|
import 'mocks/order_repository_mock.dart';
|
||||||
|
|
||||||
/// A module that provides Data Connect dependencies, including mocks.
|
/// A module that provides Data Connect dependencies, including mocks.
|
||||||
class DataConnectModule extends Module {
|
class DataConnectModule extends Module {
|
||||||
@@ -11,5 +12,6 @@ class DataConnectModule extends Module {
|
|||||||
i.addLazySingleton(AuthRepositoryMock.new);
|
i.addLazySingleton(AuthRepositoryMock.new);
|
||||||
i.addLazySingleton(HomeRepositoryMock.new);
|
i.addLazySingleton(HomeRepositoryMock.new);
|
||||||
i.addLazySingleton(BusinessRepositoryMock.new);
|
i.addLazySingleton(BusinessRepositoryMock.new);
|
||||||
|
i.addLazySingleton(OrderRepositoryMock.new);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class BusinessRepositoryMock {
|
|||||||
|
|
||||||
Future<List<Hub>> getHubs(String businessId) async {
|
Future<List<Hub>> getHubs(String businessId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
return [
|
return <Hub>[
|
||||||
const Hub(
|
const Hub(
|
||||||
id: 'hub_1',
|
id: 'hub_1',
|
||||||
businessId: 'biz_1',
|
businessId: 'biz_1',
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class EventRepositoryMock {
|
|||||||
|
|
||||||
Future<List<EventShift>> getEventShifts(String eventId) async {
|
Future<List<EventShift>> getEventShifts(String eventId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
return [
|
return <EventShift>[
|
||||||
const EventShift(
|
const EventShift(
|
||||||
id: 'shift_1',
|
id: 'shift_1',
|
||||||
eventId: 'event_1',
|
eventId: 'event_1',
|
||||||
@@ -31,7 +31,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.delayed(const Duration(milliseconds: 500));
|
||||||
return [
|
return <Assignment>[
|
||||||
const Assignment(
|
const Assignment(
|
||||||
id: 'assign_1',
|
id: 'assign_1',
|
||||||
positionId: 'pos_1',
|
positionId: 'pos_1',
|
||||||
@@ -43,10 +43,10 @@ class EventRepositoryMock {
|
|||||||
|
|
||||||
Future<List<Event>> getUpcomingEvents() async {
|
Future<List<Event>> getUpcomingEvents() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 800));
|
await Future.delayed(const Duration(milliseconds: 800));
|
||||||
return [_mockEvent];
|
return <Event>[_mockEvent];
|
||||||
}
|
}
|
||||||
|
|
||||||
static final _mockEvent = Event(
|
static final Event _mockEvent = Event(
|
||||||
id: 'event_1',
|
id: 'event_1',
|
||||||
businessId: 'biz_1',
|
businessId: 'biz_1',
|
||||||
hubId: 'hub_1',
|
hubId: 'hub_1',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
class FinancialRepositoryMock {
|
class FinancialRepositoryMock {
|
||||||
Future<List<Invoice>> getInvoices(String businessId) async {
|
Future<List<Invoice>> getInvoices(String businessId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
return [
|
return <Invoice>[
|
||||||
const Invoice(
|
const Invoice(
|
||||||
id: 'inv_1',
|
id: 'inv_1',
|
||||||
eventId: 'event_1',
|
eventId: 'event_1',
|
||||||
@@ -19,7 +19,7 @@ class FinancialRepositoryMock {
|
|||||||
|
|
||||||
Future<List<StaffPayment>> getStaffPayments(String staffId) async {
|
Future<List<StaffPayment>> getStaffPayments(String staffId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
return [
|
return <StaffPayment>[
|
||||||
StaffPayment(
|
StaffPayment(
|
||||||
id: 'pay_1',
|
id: 'pay_1',
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
/// Mock implementation of order-related data operations.
|
||||||
|
///
|
||||||
|
/// This class simulates backend responses for order types and order creation.
|
||||||
|
/// It is used by the feature-level repository implementations.
|
||||||
|
class OrderRepositoryMock {
|
||||||
|
/// Returns a list of available [OrderType]s.
|
||||||
|
Future<List<OrderType>> getOrderTypes() async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
return const <OrderType>[
|
||||||
|
OrderType(
|
||||||
|
id: 'rapid',
|
||||||
|
titleKey: 'client_create_order.types.rapid',
|
||||||
|
descriptionKey: 'client_create_order.types.rapid_desc',
|
||||||
|
),
|
||||||
|
OrderType(
|
||||||
|
id: 'one-time',
|
||||||
|
titleKey: 'client_create_order.types.one_time',
|
||||||
|
descriptionKey: 'client_create_order.types.one_time_desc',
|
||||||
|
),
|
||||||
|
OrderType(
|
||||||
|
id: 'recurring',
|
||||||
|
titleKey: 'client_create_order.types.recurring',
|
||||||
|
descriptionKey: 'client_create_order.types.recurring_desc',
|
||||||
|
),
|
||||||
|
OrderType(
|
||||||
|
id: 'permanent',
|
||||||
|
titleKey: 'client_create_order.types.permanent',
|
||||||
|
descriptionKey: 'client_create_order.types.permanent_desc',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simulates creating a one-time order.
|
||||||
|
Future<void> createOneTimeOrder(OneTimeOrder order) async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 800));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simulates creating a rapid order.
|
||||||
|
Future<void> createRapidOrder(String description) async {
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
class RatingRepositoryMock {
|
class RatingRepositoryMock {
|
||||||
Future<List<StaffRating>> getStaffRatings(String staffId) async {
|
Future<List<StaffRating>> getStaffRatings(String staffId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 400));
|
await Future.delayed(const Duration(milliseconds: 400));
|
||||||
return [
|
return <StaffRating>[
|
||||||
const StaffRating(
|
const StaffRating(
|
||||||
id: 'rate_1',
|
id: 'rate_1',
|
||||||
staffId: 'staff_1',
|
staffId: 'staff_1',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class SkillRepositoryMock {
|
|||||||
|
|
||||||
Future<List<Skill>> getAllSkills() async {
|
Future<List<Skill>> getAllSkills() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
return [
|
return <Skill>[
|
||||||
const Skill(
|
const Skill(
|
||||||
id: 'skill_1',
|
id: 'skill_1',
|
||||||
categoryId: 'cat_1',
|
categoryId: 'cat_1',
|
||||||
@@ -26,7 +26,7 @@ class SkillRepositoryMock {
|
|||||||
|
|
||||||
Future<List<StaffSkill>> getStaffSkills(String staffId) async {
|
Future<List<StaffSkill>> getStaffSkills(String staffId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 400));
|
await Future.delayed(const Duration(milliseconds: 400));
|
||||||
return [
|
return <StaffSkill>[
|
||||||
const StaffSkill(
|
const StaffSkill(
|
||||||
id: 'staff_skill_1',
|
id: 'staff_skill_1',
|
||||||
staffId: 'staff_1',
|
staffId: 'staff_1',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class StaffRepositoryMock {
|
|||||||
|
|
||||||
Future<List<Membership>> getMemberships(String userId) async {
|
Future<List<Membership>> getMemberships(String userId) async {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
return [
|
return <Membership>[
|
||||||
Membership(
|
Membership(
|
||||||
id: 'mem_1',
|
id: 'mem_1',
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
class SupportRepositoryMock {
|
class SupportRepositoryMock {
|
||||||
Future<List<Tag>> getTags() async {
|
Future<List<Tag>> getTags() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
return [
|
return <Tag>[
|
||||||
const Tag(id: 'tag_1', label: 'Urgent'),
|
const Tag(id: 'tag_1', label: 'Urgent'),
|
||||||
const Tag(id: 'tag_2', label: 'VIP Event'),
|
const Tag(id: 'tag_2', label: 'VIP Event'),
|
||||||
];
|
];
|
||||||
@@ -12,7 +12,7 @@ class SupportRepositoryMock {
|
|||||||
|
|
||||||
Future<List<WorkingArea>> getWorkingAreas() async {
|
Future<List<WorkingArea>> getWorkingAreas() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
return [
|
return <WorkingArea>[
|
||||||
const WorkingArea(
|
const WorkingArea(
|
||||||
id: 'area_1',
|
id: 'area_1',
|
||||||
name: 'Central London',
|
name: 'Central London',
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class UiIcons {
|
|||||||
/// Plus/Add icon
|
/// Plus/Add icon
|
||||||
static const IconData add = _IconLib.plus;
|
static const IconData add = _IconLib.plus;
|
||||||
|
|
||||||
|
/// Minus icon
|
||||||
|
static const IconData minus = _IconLib.minus;
|
||||||
|
|
||||||
/// Edit icon
|
/// Edit icon
|
||||||
static const IconData edit = _IconLib.edit2;
|
static const IconData edit = _IconLib.edit2;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
/// It is pure Dart and has no dependencies on Flutter or Firebase.
|
/// It is pure Dart and has no dependencies on Flutter or Firebase.
|
||||||
///
|
///
|
||||||
/// Note: Repository Interfaces are now located in their respective Feature packages.
|
/// Note: Repository Interfaces are now located in their respective Feature packages.
|
||||||
|
library;
|
||||||
|
|
||||||
// Users & Membership
|
// Users & Membership
|
||||||
export 'src/entities/users/user.dart';
|
export 'src/entities/users/user.dart';
|
||||||
@@ -26,6 +27,11 @@ 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';
|
||||||
|
|
||||||
|
// Orders & Requests
|
||||||
|
export 'src/entities/orders/order_type.dart';
|
||||||
|
export 'src/entities/orders/one_time_order.dart';
|
||||||
|
export 'src/entities/orders/one_time_order_position.dart';
|
||||||
|
|
||||||
// Skills & Certs
|
// Skills & Certs
|
||||||
export 'src/entities/skills/skill.dart';
|
export 'src/entities/skills/skill.dart';
|
||||||
export 'src/entities/skills/skill_category.dart';
|
export 'src/entities/skills/skill_category.dart';
|
||||||
|
|||||||
@@ -4,6 +4,15 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Can be between a business and the platform, or a business and staff.
|
/// Can be between a business and the platform, or a business and staff.
|
||||||
class BizContract extends Equatable {
|
class BizContract extends Equatable {
|
||||||
|
|
||||||
|
const BizContract({
|
||||||
|
required this.id,
|
||||||
|
required this.businessId,
|
||||||
|
required this.name,
|
||||||
|
required this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
required this.contentUrl,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -22,15 +31,6 @@ class BizContract extends Equatable {
|
|||||||
/// URL to the document content (PDF/HTML).
|
/// URL to the document content (PDF/HTML).
|
||||||
final String contentUrl;
|
final String contentUrl;
|
||||||
|
|
||||||
const BizContract({
|
|
||||||
required this.id,
|
|
||||||
required this.businessId,
|
|
||||||
required this.name,
|
|
||||||
required this.startDate,
|
|
||||||
this.endDate,
|
|
||||||
required this.contentUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, businessId, name, startDate, endDate, contentUrl];
|
List<Object?> get props => <Object?>[id, businessId, name, startDate, endDate, contentUrl];
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,14 @@ enum BusinessStatus {
|
|||||||
///
|
///
|
||||||
/// This is the top-level organizational entity in the system.
|
/// This is the top-level organizational entity in the system.
|
||||||
class Business extends Equatable {
|
class Business extends Equatable {
|
||||||
|
|
||||||
|
const Business({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.registrationNumber,
|
||||||
|
required this.status,
|
||||||
|
this.avatar,
|
||||||
|
});
|
||||||
/// Unique identifier for the business.
|
/// Unique identifier for the business.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -34,14 +42,6 @@ class Business extends Equatable {
|
|||||||
/// URL to the business logo.
|
/// URL to the business logo.
|
||||||
final String? avatar;
|
final String? avatar;
|
||||||
|
|
||||||
const Business({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.registrationNumber,
|
|
||||||
required this.status,
|
|
||||||
this.avatar,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, name, registrationNumber, status, avatar];
|
List<Object?> get props => <Object?>[id, name, registrationNumber, status, avatar];
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,15 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents payroll and operational configuration for a [Business].
|
/// Represents payroll and operational configuration for a [Business].
|
||||||
class BusinessSetting extends Equatable {
|
class BusinessSetting extends Equatable {
|
||||||
|
|
||||||
|
const BusinessSetting({
|
||||||
|
required this.id,
|
||||||
|
required this.businessId,
|
||||||
|
required this.prefix,
|
||||||
|
required this.overtimeEnabled,
|
||||||
|
this.clockInRequirement,
|
||||||
|
this.clockOutRequirement,
|
||||||
|
});
|
||||||
/// Unique identifier for the settings record.
|
/// Unique identifier for the settings record.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -20,17 +29,8 @@ class BusinessSetting extends Equatable {
|
|||||||
/// Requirement method for clocking out.
|
/// Requirement method for clocking out.
|
||||||
final String? clockOutRequirement;
|
final String? clockOutRequirement;
|
||||||
|
|
||||||
const BusinessSetting({
|
|
||||||
required this.id,
|
|
||||||
required this.businessId,
|
|
||||||
required this.prefix,
|
|
||||||
required this.overtimeEnabled,
|
|
||||||
this.clockInRequirement,
|
|
||||||
this.clockOutRequirement,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => <Object?>[
|
||||||
id,
|
id,
|
||||||
businessId,
|
businessId,
|
||||||
prefix,
|
prefix,
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ enum HubStatus {
|
|||||||
|
|
||||||
/// Represents a branch location or operational unit within a [Business].
|
/// Represents a branch location or operational unit within a [Business].
|
||||||
class Hub extends Equatable {
|
class Hub extends Equatable {
|
||||||
|
|
||||||
|
const Hub({
|
||||||
|
required this.id,
|
||||||
|
required this.businessId,
|
||||||
|
required this.name,
|
||||||
|
required this.address,
|
||||||
|
this.nfcTagId,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -32,15 +41,6 @@ class Hub extends Equatable {
|
|||||||
/// Operational status.
|
/// Operational status.
|
||||||
final HubStatus status;
|
final HubStatus status;
|
||||||
|
|
||||||
const Hub({
|
|
||||||
required this.id,
|
|
||||||
required this.businessId,
|
|
||||||
required this.name,
|
|
||||||
required this.address,
|
|
||||||
this.nfcTagId,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, businessId, name, address, nfcTagId, status];
|
List<Object?> get props => <Object?>[id, businessId, name, address, nfcTagId, status];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Used for more granular organization of staff and events (e.g. "Kitchen", "Service").
|
/// Used for more granular organization of staff and events (e.g. "Kitchen", "Service").
|
||||||
class HubDepartment extends Equatable {
|
class HubDepartment extends Equatable {
|
||||||
|
|
||||||
|
const HubDepartment({
|
||||||
|
required this.id,
|
||||||
|
required this.hubId,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -13,12 +19,6 @@ class HubDepartment extends Equatable {
|
|||||||
/// Name of the department.
|
/// Name of the department.
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
const HubDepartment({
|
|
||||||
required this.id,
|
|
||||||
required this.hubId,
|
|
||||||
required this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, hubId, name];
|
List<Object?> get props => <Object?>[id, hubId, name];
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,15 @@ enum AssignmentStatus {
|
|||||||
|
|
||||||
/// Represents the link between a [Staff] member and an [EventShiftPosition].
|
/// Represents the link between a [Staff] member and an [EventShiftPosition].
|
||||||
class Assignment extends Equatable {
|
class Assignment extends Equatable {
|
||||||
|
|
||||||
|
const Assignment({
|
||||||
|
required this.id,
|
||||||
|
required this.positionId,
|
||||||
|
required this.staffId,
|
||||||
|
required this.status,
|
||||||
|
this.clockIn,
|
||||||
|
this.clockOut,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -44,15 +53,6 @@ class Assignment extends Equatable {
|
|||||||
/// Actual timestamp when staff clocked out.
|
/// Actual timestamp when staff clocked out.
|
||||||
final DateTime? clockOut;
|
final DateTime? clockOut;
|
||||||
|
|
||||||
const Assignment({
|
|
||||||
required this.id,
|
|
||||||
required this.positionId,
|
|
||||||
required this.staffId,
|
|
||||||
required this.status,
|
|
||||||
this.clockIn,
|
|
||||||
this.clockOut,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, positionId, staffId, status, clockIn, clockOut];
|
List<Object?> get props => <Object?>[id, positionId, staffId, status, clockIn, clockOut];
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,16 @@ enum EventStatus {
|
|||||||
///
|
///
|
||||||
/// This is the central entity for scheduling work. An Event contains [EventShift]s.
|
/// This is the central entity for scheduling work. An Event contains [EventShift]s.
|
||||||
class Event extends Equatable {
|
class Event extends Equatable {
|
||||||
|
|
||||||
|
const Event({
|
||||||
|
required this.id,
|
||||||
|
required this.businessId,
|
||||||
|
required this.hubId,
|
||||||
|
required this.name,
|
||||||
|
required this.date,
|
||||||
|
required this.status,
|
||||||
|
required this.contractType,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -55,16 +65,6 @@ class Event extends Equatable {
|
|||||||
/// Type of employment contract (e.g., 'freelance', 'permanent').
|
/// Type of employment contract (e.g., 'freelance', 'permanent').
|
||||||
final String contractType;
|
final String contractType;
|
||||||
|
|
||||||
const Event({
|
|
||||||
required this.id,
|
|
||||||
required this.businessId,
|
|
||||||
required this.hubId,
|
|
||||||
required this.name,
|
|
||||||
required this.date,
|
|
||||||
required this.status,
|
|
||||||
required this.contractType,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, businessId, hubId, name, date, status, contractType];
|
List<Object?> get props => <Object?>[id, businessId, hubId, name, date, status, contractType];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// An Event can have multiple shifts (e.g. "Morning Shift", "Evening Shift").
|
/// An Event can have multiple shifts (e.g. "Morning Shift", "Evening Shift").
|
||||||
class EventShift extends Equatable {
|
class EventShift extends Equatable {
|
||||||
|
|
||||||
|
const EventShift({
|
||||||
|
required this.id,
|
||||||
|
required this.eventId,
|
||||||
|
required this.name,
|
||||||
|
required this.address,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -16,13 +23,6 @@ class EventShift extends Equatable {
|
|||||||
/// Specific address for this shift (if different from Hub).
|
/// Specific address for this shift (if different from Hub).
|
||||||
final String address;
|
final String address;
|
||||||
|
|
||||||
const EventShift({
|
|
||||||
required this.id,
|
|
||||||
required this.eventId,
|
|
||||||
required this.name,
|
|
||||||
required this.address,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, eventId, name, address];
|
List<Object?> get props => <Object?>[id, eventId, name, address];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,17 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Defines the requirement for a specific [Skill], the quantity needed, and the pay.
|
/// Defines the requirement for a specific [Skill], the quantity needed, and the pay.
|
||||||
class EventShiftPosition extends Equatable {
|
class EventShiftPosition extends Equatable {
|
||||||
|
|
||||||
|
const EventShiftPosition({
|
||||||
|
required this.id,
|
||||||
|
required this.shiftId,
|
||||||
|
required this.skillId,
|
||||||
|
required this.count,
|
||||||
|
required this.rate,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
required this.breakDurationMinutes,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -28,19 +39,8 @@ class EventShiftPosition extends Equatable {
|
|||||||
/// Deducted break duration in minutes.
|
/// Deducted break duration in minutes.
|
||||||
final int breakDurationMinutes;
|
final int breakDurationMinutes;
|
||||||
|
|
||||||
const EventShiftPosition({
|
|
||||||
required this.id,
|
|
||||||
required this.shiftId,
|
|
||||||
required this.skillId,
|
|
||||||
required this.count,
|
|
||||||
required this.rate,
|
|
||||||
required this.startTime,
|
|
||||||
required this.endTime,
|
|
||||||
required this.breakDurationMinutes,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => <Object?>[
|
||||||
id,
|
id,
|
||||||
shiftId,
|
shiftId,
|
||||||
skillId,
|
skillId,
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Derived from [Assignment] clock-in/out times, used for payroll.
|
/// Derived from [Assignment] clock-in/out times, used for payroll.
|
||||||
class WorkSession extends Equatable {
|
class WorkSession extends Equatable {
|
||||||
|
|
||||||
|
const WorkSession({
|
||||||
|
required this.id,
|
||||||
|
required this.assignmentId,
|
||||||
|
required this.startTime,
|
||||||
|
this.endTime,
|
||||||
|
required this.breakDurationMinutes,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -19,14 +27,6 @@ class WorkSession extends Equatable {
|
|||||||
/// Verified break duration.
|
/// Verified break duration.
|
||||||
final int breakDurationMinutes;
|
final int breakDurationMinutes;
|
||||||
|
|
||||||
const WorkSession({
|
|
||||||
required this.id,
|
|
||||||
required this.assignmentId,
|
|
||||||
required this.startTime,
|
|
||||||
this.endTime,
|
|
||||||
required this.breakDurationMinutes,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, assignmentId, startTime, endTime, breakDurationMinutes];
|
List<Object?> get props => <Object?>[id, assignmentId, startTime, endTime, breakDurationMinutes];
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,16 @@ enum InvoiceStatus {
|
|||||||
|
|
||||||
/// Represents a bill sent to a [Business] for services rendered.
|
/// Represents a bill sent to a [Business] for services rendered.
|
||||||
class Invoice extends Equatable {
|
class Invoice extends Equatable {
|
||||||
|
|
||||||
|
const Invoice({
|
||||||
|
required this.id,
|
||||||
|
required this.eventId,
|
||||||
|
required this.businessId,
|
||||||
|
required this.status,
|
||||||
|
required this.totalAmount,
|
||||||
|
required this.workAmount,
|
||||||
|
required this.addonsAmount,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -47,18 +57,8 @@ class Invoice extends Equatable {
|
|||||||
/// Total amount for addons/extras.
|
/// Total amount for addons/extras.
|
||||||
final double addonsAmount;
|
final double addonsAmount;
|
||||||
|
|
||||||
const Invoice({
|
|
||||||
required this.id,
|
|
||||||
required this.eventId,
|
|
||||||
required this.businessId,
|
|
||||||
required this.status,
|
|
||||||
required this.totalAmount,
|
|
||||||
required this.workAmount,
|
|
||||||
required this.addonsAmount,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => <Object?>[
|
||||||
id,
|
id,
|
||||||
eventId,
|
eventId,
|
||||||
businessId,
|
businessId,
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents a reason or log for a declined [Invoice].
|
/// Represents a reason or log for a declined [Invoice].
|
||||||
class InvoiceDecline extends Equatable {
|
class InvoiceDecline extends Equatable {
|
||||||
|
|
||||||
|
const InvoiceDecline({
|
||||||
|
required this.id,
|
||||||
|
required this.invoiceId,
|
||||||
|
required this.reason,
|
||||||
|
required this.declinedAt,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -14,13 +21,6 @@ class InvoiceDecline extends Equatable {
|
|||||||
/// When the decline happened.
|
/// When the decline happened.
|
||||||
final DateTime declinedAt;
|
final DateTime declinedAt;
|
||||||
|
|
||||||
const InvoiceDecline({
|
|
||||||
required this.id,
|
|
||||||
required this.invoiceId,
|
|
||||||
required this.reason,
|
|
||||||
required this.declinedAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, invoiceId, reason, declinedAt];
|
List<Object?> get props => <Object?>[id, invoiceId, reason, declinedAt];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,15 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Corresponds to the work done by one [Staff] member.
|
/// Corresponds to the work done by one [Staff] member.
|
||||||
class InvoiceItem extends Equatable {
|
class InvoiceItem extends Equatable {
|
||||||
|
|
||||||
|
const InvoiceItem({
|
||||||
|
required this.id,
|
||||||
|
required this.invoiceId,
|
||||||
|
required this.staffId,
|
||||||
|
required this.workHours,
|
||||||
|
required this.rate,
|
||||||
|
required this.amount,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -22,15 +31,6 @@ class InvoiceItem extends Equatable {
|
|||||||
/// Total line item amount (workHours * rate).
|
/// Total line item amount (workHours * rate).
|
||||||
final double amount;
|
final double amount;
|
||||||
|
|
||||||
const InvoiceItem({
|
|
||||||
required this.id,
|
|
||||||
required this.invoiceId,
|
|
||||||
required this.staffId,
|
|
||||||
required this.workHours,
|
|
||||||
required this.rate,
|
|
||||||
required this.amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, invoiceId, staffId, workHours, rate, amount];
|
List<Object?> get props => <Object?>[id, invoiceId, staffId, workHours, rate, amount];
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,15 @@ enum PaymentStatus {
|
|||||||
|
|
||||||
/// Represents a payout to a [Staff] member for a completed [Assignment].
|
/// Represents a payout to a [Staff] member for a completed [Assignment].
|
||||||
class StaffPayment extends Equatable {
|
class StaffPayment extends Equatable {
|
||||||
|
|
||||||
|
const StaffPayment({
|
||||||
|
required this.id,
|
||||||
|
required this.staffId,
|
||||||
|
required this.assignmentId,
|
||||||
|
required this.amount,
|
||||||
|
required this.status,
|
||||||
|
this.paidAt,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -35,15 +44,6 @@ class StaffPayment extends Equatable {
|
|||||||
/// When the payment was successfully processed.
|
/// When the payment was successfully processed.
|
||||||
final DateTime? paidAt;
|
final DateTime? paidAt;
|
||||||
|
|
||||||
const StaffPayment({
|
|
||||||
required this.id,
|
|
||||||
required this.staffId,
|
|
||||||
required this.assignmentId,
|
|
||||||
required this.amount,
|
|
||||||
required this.status,
|
|
||||||
this.paidAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, staffId, assignmentId, amount, status, paidAt];
|
List<Object?> get props => <Object?>[id, staffId, assignmentId, amount, status, paidAt];
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,16 @@ import 'package:equatable/equatable.dart';
|
|||||||
/// This entity provides aggregated metrics such as spending and shift counts
|
/// This entity provides aggregated metrics such as spending and shift counts
|
||||||
/// for both the current week and the upcoming 7 days.
|
/// for both the current week and the upcoming 7 days.
|
||||||
class HomeDashboardData extends Equatable {
|
class HomeDashboardData extends Equatable {
|
||||||
|
|
||||||
|
/// Creates a [HomeDashboardData] instance.
|
||||||
|
const HomeDashboardData({
|
||||||
|
required this.weeklySpending,
|
||||||
|
required this.next7DaysSpending,
|
||||||
|
required this.weeklyShifts,
|
||||||
|
required this.next7DaysScheduled,
|
||||||
|
required this.totalNeeded,
|
||||||
|
required this.totalFilled,
|
||||||
|
});
|
||||||
/// Total spending for the current week.
|
/// Total spending for the current week.
|
||||||
final double weeklySpending;
|
final double weeklySpending;
|
||||||
|
|
||||||
@@ -23,18 +33,8 @@ class HomeDashboardData extends Equatable {
|
|||||||
/// Total workers filled for today's shifts.
|
/// Total workers filled for today's shifts.
|
||||||
final int totalFilled;
|
final int totalFilled;
|
||||||
|
|
||||||
/// Creates a [HomeDashboardData] instance.
|
|
||||||
const HomeDashboardData({
|
|
||||||
required this.weeklySpending,
|
|
||||||
required this.next7DaysSpending,
|
|
||||||
required this.weeklyShifts,
|
|
||||||
required this.next7DaysScheduled,
|
|
||||||
required this.totalNeeded,
|
|
||||||
required this.totalFilled,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => <Object?>[
|
||||||
weeklySpending,
|
weeklySpending,
|
||||||
next7DaysSpending,
|
next7DaysSpending,
|
||||||
weeklyShifts,
|
weeklyShifts,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'one_time_order_position.dart';
|
||||||
|
|
||||||
|
/// Represents a customer's request for a single event or shift.
|
||||||
|
///
|
||||||
|
/// Encapsulates the date, primary location, and a list of specific [OneTimeOrderPosition] requirements.
|
||||||
|
class OneTimeOrder extends Equatable {
|
||||||
|
|
||||||
|
const OneTimeOrder({
|
||||||
|
required this.date,
|
||||||
|
required this.location,
|
||||||
|
required this.positions,
|
||||||
|
});
|
||||||
|
/// The specific date for the shift or event.
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
/// The primary location where the work will take place.
|
||||||
|
final String location;
|
||||||
|
|
||||||
|
/// The list of positions and headcounts required for this order.
|
||||||
|
final List<OneTimeOrderPosition> positions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[date, location, positions];
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// Represents a specific position requirement within a [OneTimeOrder].
|
||||||
|
///
|
||||||
|
/// Defines the role, headcount, and scheduling details for a single staffing requirement.
|
||||||
|
class OneTimeOrderPosition extends Equatable {
|
||||||
|
|
||||||
|
const OneTimeOrderPosition({
|
||||||
|
required this.role,
|
||||||
|
required this.count,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
this.lunchBreak = 30,
|
||||||
|
this.location,
|
||||||
|
});
|
||||||
|
/// The job role or title required.
|
||||||
|
final String role;
|
||||||
|
|
||||||
|
/// The number of workers required for this position.
|
||||||
|
final int count;
|
||||||
|
|
||||||
|
/// The scheduled start time (e.g., "09:00 AM").
|
||||||
|
final String startTime;
|
||||||
|
|
||||||
|
/// The scheduled end time (e.g., "05:00 PM").
|
||||||
|
final String endTime;
|
||||||
|
|
||||||
|
/// The duration of the lunch break in minutes. Defaults to 30.
|
||||||
|
final int lunchBreak;
|
||||||
|
|
||||||
|
/// Optional specific location for this position, if different from the order's main location.
|
||||||
|
final String? location;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[
|
||||||
|
role,
|
||||||
|
count,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
lunchBreak,
|
||||||
|
location,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Creates a copy of this position with the given fields replaced.
|
||||||
|
OneTimeOrderPosition copyWith({
|
||||||
|
String? role,
|
||||||
|
int? count,
|
||||||
|
String? startTime,
|
||||||
|
String? endTime,
|
||||||
|
int? lunchBreak,
|
||||||
|
String? location,
|
||||||
|
}) {
|
||||||
|
return OneTimeOrderPosition(
|
||||||
|
role: role ?? this.role,
|
||||||
|
count: count ?? this.count,
|
||||||
|
startTime: startTime ?? this.startTime,
|
||||||
|
endTime: endTime ?? this.endTime,
|
||||||
|
lunchBreak: lunchBreak ?? this.lunchBreak,
|
||||||
|
location: location ?? this.location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// Represents a type of order that can be created (e.g., Rapid, One-Time).
|
||||||
|
///
|
||||||
|
/// This entity defines the identity and display metadata (keys) for the order type.
|
||||||
|
/// UI-specific properties like colors and icons are handled by the presentation layer.
|
||||||
|
class OrderType extends Equatable {
|
||||||
|
|
||||||
|
const OrderType({
|
||||||
|
required this.id,
|
||||||
|
required this.titleKey,
|
||||||
|
required this.descriptionKey,
|
||||||
|
});
|
||||||
|
/// Unique identifier for the order type.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Translation key for the title.
|
||||||
|
final String titleKey;
|
||||||
|
|
||||||
|
/// Translation key for the description.
|
||||||
|
final String descriptionKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, titleKey, descriptionKey];
|
||||||
|
}
|
||||||
@@ -4,17 +4,17 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Can apply to Staff (needs) or Events (provision).
|
/// Can apply to Staff (needs) or Events (provision).
|
||||||
class Accessibility extends Equatable {
|
class Accessibility extends Equatable {
|
||||||
|
|
||||||
|
const Accessibility({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// Description (e.g. "Wheelchair Access").
|
/// Description (e.g. "Wheelchair Access").
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
const Accessibility({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, name];
|
List<Object?> get props => <Object?>[id, name];
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,15 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents bank account details for payroll.
|
/// Represents bank account details for payroll.
|
||||||
class BankAccount extends Equatable {
|
class BankAccount extends Equatable {
|
||||||
|
|
||||||
|
const BankAccount({
|
||||||
|
required this.id,
|
||||||
|
required this.userId,
|
||||||
|
required this.bankName,
|
||||||
|
required this.accountNumber,
|
||||||
|
required this.accountName,
|
||||||
|
this.sortCode,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -20,15 +29,6 @@ class BankAccount extends Equatable {
|
|||||||
/// Sort code (if applicable).
|
/// Sort code (if applicable).
|
||||||
final String? sortCode;
|
final String? sortCode;
|
||||||
|
|
||||||
const BankAccount({
|
|
||||||
required this.id,
|
|
||||||
required this.userId,
|
|
||||||
required this.bankName,
|
|
||||||
required this.accountNumber,
|
|
||||||
required this.accountName,
|
|
||||||
this.sortCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, userId, bankName, accountNumber, accountName, sortCode];
|
List<Object?> get props => <Object?>[id, userId, bankName, accountNumber, accountName, sortCode];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,12 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Critical for staff safety during shifts.
|
/// Critical for staff safety during shifts.
|
||||||
class EmergencyContact extends Equatable {
|
class EmergencyContact extends Equatable {
|
||||||
|
|
||||||
|
const EmergencyContact({
|
||||||
|
required this.name,
|
||||||
|
required this.relationship,
|
||||||
|
required this.phone,
|
||||||
|
});
|
||||||
/// Full name of the contact.
|
/// Full name of the contact.
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
@@ -13,12 +19,6 @@ class EmergencyContact extends Equatable {
|
|||||||
/// Phone number.
|
/// Phone number.
|
||||||
final String phone;
|
final String phone;
|
||||||
|
|
||||||
const EmergencyContact({
|
|
||||||
required this.name,
|
|
||||||
required this.relationship,
|
|
||||||
required this.phone,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [name, relationship, phone];
|
List<Object?> get props => <Object?>[name, relationship, phone];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,14 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Defines recurring availability (e.g., "Mondays 9-5").
|
/// Defines recurring availability (e.g., "Mondays 9-5").
|
||||||
class Schedule extends Equatable {
|
class Schedule extends Equatable {
|
||||||
|
|
||||||
|
const Schedule({
|
||||||
|
required this.id,
|
||||||
|
required this.staffId,
|
||||||
|
required this.dayOfWeek,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -19,14 +27,6 @@ class Schedule extends Equatable {
|
|||||||
/// End time of availability.
|
/// End time of availability.
|
||||||
final DateTime endTime;
|
final DateTime endTime;
|
||||||
|
|
||||||
const Schedule({
|
|
||||||
required this.id,
|
|
||||||
required this.staffId,
|
|
||||||
required this.dayOfWeek,
|
|
||||||
required this.startTime,
|
|
||||||
required this.endTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, staffId, dayOfWeek, startTime, endTime];
|
List<Object?> get props => <Object?>[id, staffId, dayOfWeek, startTime, endTime];
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,13 @@ enum PreferenceType {
|
|||||||
|
|
||||||
/// Represents a business's specific preference for a staff member.
|
/// Represents a business's specific preference for a staff member.
|
||||||
class BusinessStaffPreference extends Equatable {
|
class BusinessStaffPreference extends Equatable {
|
||||||
|
|
||||||
|
const BusinessStaffPreference({
|
||||||
|
required this.id,
|
||||||
|
required this.businessId,
|
||||||
|
required this.staffId,
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -23,13 +30,6 @@ class BusinessStaffPreference extends Equatable {
|
|||||||
/// Whether they are a favorite or blocked.
|
/// Whether they are a favorite or blocked.
|
||||||
final PreferenceType type;
|
final PreferenceType type;
|
||||||
|
|
||||||
const BusinessStaffPreference({
|
|
||||||
required this.id,
|
|
||||||
required this.businessId,
|
|
||||||
required this.staffId,
|
|
||||||
required this.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, businessId, staffId, type];
|
List<Object?> get props => <Object?>[id, businessId, staffId, type];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,15 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Penalties are issued for no-shows, cancellations, or poor conduct.
|
/// Penalties are issued for no-shows, cancellations, or poor conduct.
|
||||||
class PenaltyLog extends Equatable {
|
class PenaltyLog extends Equatable {
|
||||||
|
|
||||||
|
const PenaltyLog({
|
||||||
|
required this.id,
|
||||||
|
required this.staffId,
|
||||||
|
required this.assignmentId,
|
||||||
|
required this.reason,
|
||||||
|
required this.points,
|
||||||
|
required this.issuedAt,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -22,15 +31,6 @@ class PenaltyLog extends Equatable {
|
|||||||
/// When the penalty was issued.
|
/// When the penalty was issued.
|
||||||
final DateTime issuedAt;
|
final DateTime issuedAt;
|
||||||
|
|
||||||
const PenaltyLog({
|
|
||||||
required this.id,
|
|
||||||
required this.staffId,
|
|
||||||
required this.assignmentId,
|
|
||||||
required this.reason,
|
|
||||||
required this.points,
|
|
||||||
required this.issuedAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, staffId, assignmentId, reason, points, issuedAt];
|
List<Object?> get props => <Object?>[id, staffId, assignmentId, reason, points, issuedAt];
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,15 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents a rating given to a staff member by a client.
|
/// Represents a rating given to a staff member by a client.
|
||||||
class StaffRating extends Equatable {
|
class StaffRating extends Equatable {
|
||||||
|
|
||||||
|
const StaffRating({
|
||||||
|
required this.id,
|
||||||
|
required this.staffId,
|
||||||
|
required this.eventId,
|
||||||
|
required this.businessId,
|
||||||
|
required this.rating,
|
||||||
|
this.comment,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -20,15 +29,6 @@ class StaffRating extends Equatable {
|
|||||||
/// Optional feedback text.
|
/// Optional feedback text.
|
||||||
final String? comment;
|
final String? comment;
|
||||||
|
|
||||||
const StaffRating({
|
|
||||||
required this.id,
|
|
||||||
required this.staffId,
|
|
||||||
required this.eventId,
|
|
||||||
required this.businessId,
|
|
||||||
required this.rating,
|
|
||||||
this.comment,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, staffId, eventId, businessId, rating, comment];
|
List<Object?> get props => <Object?>[id, staffId, eventId, businessId, rating, comment];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,12 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Examples: "Food Hygiene Level 2", "SIA Badge".
|
/// Examples: "Food Hygiene Level 2", "SIA Badge".
|
||||||
class Certificate extends Equatable {
|
class Certificate extends Equatable {
|
||||||
|
|
||||||
|
const Certificate({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.isRequired,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -13,12 +19,6 @@ class Certificate extends Equatable {
|
|||||||
/// Whether this certificate is mandatory for platform access or specific roles.
|
/// Whether this certificate is mandatory for platform access or specific roles.
|
||||||
final bool isRequired;
|
final bool isRequired;
|
||||||
|
|
||||||
const Certificate({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.isRequired,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, name, isRequired];
|
List<Object?> get props => <Object?>[id, name, isRequired];
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
/// Examples: "Waiter", "Security Guard", "Bartender".
|
/// Examples: "Waiter", "Security Guard", "Bartender".
|
||||||
/// Linked to a [SkillCategory].
|
/// Linked to a [SkillCategory].
|
||||||
class Skill extends Equatable {
|
class Skill extends Equatable {
|
||||||
|
|
||||||
|
const Skill({
|
||||||
|
required this.id,
|
||||||
|
required this.categoryId,
|
||||||
|
required this.name,
|
||||||
|
required this.basePrice,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -17,13 +24,6 @@ class Skill extends Equatable {
|
|||||||
/// Default hourly rate suggested for this skill.
|
/// Default hourly rate suggested for this skill.
|
||||||
final double basePrice;
|
final double basePrice;
|
||||||
|
|
||||||
const Skill({
|
|
||||||
required this.id,
|
|
||||||
required this.categoryId,
|
|
||||||
required this.name,
|
|
||||||
required this.basePrice,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, categoryId, name, basePrice];
|
List<Object?> get props => <Object?>[id, categoryId, name, basePrice];
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,17 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents a broad category of skills (e.g. "Hospitality", "Logistics").
|
/// Represents a broad category of skills (e.g. "Hospitality", "Logistics").
|
||||||
class SkillCategory extends Equatable {
|
class SkillCategory extends Equatable {
|
||||||
|
|
||||||
|
const SkillCategory({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// Display name.
|
/// Display name.
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
const SkillCategory({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, name];
|
List<Object?> get props => <Object?>[id, name];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,14 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Examples: "Black Shirt" (Uniform), "Safety Boots" (Equipment).
|
/// Examples: "Black Shirt" (Uniform), "Safety Boots" (Equipment).
|
||||||
class SkillKit extends Equatable {
|
class SkillKit extends Equatable {
|
||||||
|
|
||||||
|
const SkillKit({
|
||||||
|
required this.id,
|
||||||
|
required this.skillId,
|
||||||
|
required this.name,
|
||||||
|
required this.isRequired,
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -19,14 +27,6 @@ class SkillKit extends Equatable {
|
|||||||
/// Type of kit ('uniform' or 'equipment').
|
/// Type of kit ('uniform' or 'equipment').
|
||||||
final String type;
|
final String type;
|
||||||
|
|
||||||
const SkillKit({
|
|
||||||
required this.id,
|
|
||||||
required this.skillId,
|
|
||||||
required this.name,
|
|
||||||
required this.isRequired,
|
|
||||||
required this.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, skillId, name, isRequired, type];
|
List<Object?> get props => <Object?>[id, skillId, name, isRequired, type];
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,15 @@ enum StaffSkillStatus {
|
|||||||
|
|
||||||
/// Represents a staff member's qualification in a specific [Skill].
|
/// Represents a staff member's qualification in a specific [Skill].
|
||||||
class StaffSkill extends Equatable {
|
class StaffSkill extends Equatable {
|
||||||
|
|
||||||
|
const StaffSkill({
|
||||||
|
required this.id,
|
||||||
|
required this.staffId,
|
||||||
|
required this.skillId,
|
||||||
|
required this.level,
|
||||||
|
required this.experienceYears,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -44,15 +53,6 @@ class StaffSkill extends Equatable {
|
|||||||
/// Verification status.
|
/// Verification status.
|
||||||
final StaffSkillStatus status;
|
final StaffSkillStatus status;
|
||||||
|
|
||||||
const StaffSkill({
|
|
||||||
required this.id,
|
|
||||||
required this.staffId,
|
|
||||||
required this.skillId,
|
|
||||||
required this.level,
|
|
||||||
required this.experienceYears,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, staffId, skillId, level, experienceYears, status];
|
List<Object?> get props => <Object?>[id, staffId, skillId, level, experienceYears, status];
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents a financial addon/bonus/deduction applied to an Invoice or Payment.
|
/// Represents a financial addon/bonus/deduction applied to an Invoice or Payment.
|
||||||
class Addon extends Equatable {
|
class Addon extends Equatable {
|
||||||
|
|
||||||
|
const Addon({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.amount,
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -14,13 +21,6 @@ class Addon extends Equatable {
|
|||||||
/// Type ('credit' or 'debit').
|
/// Type ('credit' or 'debit').
|
||||||
final String type;
|
final String type;
|
||||||
|
|
||||||
const Addon({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.amount,
|
|
||||||
required this.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, name, amount, type];
|
List<Object?> get props => <Object?>[id, name, amount, type];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,12 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Used for avatars, certificates, or event photos.
|
/// Used for avatars, certificates, or event photos.
|
||||||
class Media extends Equatable {
|
class Media extends Equatable {
|
||||||
|
|
||||||
|
const Media({
|
||||||
|
required this.id,
|
||||||
|
required this.url,
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -13,12 +19,6 @@ class Media extends Equatable {
|
|||||||
/// MIME type or general type (image, pdf).
|
/// MIME type or general type (image, pdf).
|
||||||
final String type;
|
final String type;
|
||||||
|
|
||||||
const Media({
|
|
||||||
required this.id,
|
|
||||||
required this.url,
|
|
||||||
required this.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, url, type];
|
List<Object?> get props => <Object?>[id, url, type];
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,17 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents a descriptive tag used for categorizing events or staff.
|
/// Represents a descriptive tag used for categorizing events or staff.
|
||||||
class Tag extends Equatable {
|
class Tag extends Equatable {
|
||||||
|
|
||||||
|
const Tag({
|
||||||
|
required this.id,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// Text label.
|
/// Text label.
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
const Tag({
|
|
||||||
required this.id,
|
|
||||||
required this.label,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, label];
|
List<Object?> get props => <Object?>[id, label];
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,14 @@ import 'package:equatable/equatable.dart';
|
|||||||
|
|
||||||
/// Represents a geographical area where a [Staff] member is willing to work.
|
/// Represents a geographical area where a [Staff] member is willing to work.
|
||||||
class WorkingArea extends Equatable {
|
class WorkingArea extends Equatable {
|
||||||
|
|
||||||
|
const WorkingArea({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.centerLat,
|
||||||
|
required this.centerLng,
|
||||||
|
required this.radiusKm,
|
||||||
|
});
|
||||||
/// Unique identifier.
|
/// Unique identifier.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -17,14 +25,6 @@ class WorkingArea extends Equatable {
|
|||||||
/// Radius in Kilometers.
|
/// Radius in Kilometers.
|
||||||
final double radiusKm;
|
final double radiusKm;
|
||||||
|
|
||||||
const WorkingArea({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.centerLat,
|
|
||||||
required this.centerLng,
|
|
||||||
required this.radiusKm,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, name, centerLat, centerLng, radiusKm];
|
List<Object?> get props => <Object?>[id, name, centerLat, centerLng, radiusKm];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Grants a user access to business-level operations.
|
/// Grants a user access to business-level operations.
|
||||||
class BizMember extends Equatable {
|
class BizMember extends Equatable {
|
||||||
|
|
||||||
|
const BizMember({
|
||||||
|
required this.id,
|
||||||
|
required this.businessId,
|
||||||
|
required this.userId,
|
||||||
|
required this.role,
|
||||||
|
});
|
||||||
/// Unique identifier for this membership.
|
/// Unique identifier for this membership.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -16,13 +23,6 @@ class BizMember extends Equatable {
|
|||||||
/// The role within the business.
|
/// The role within the business.
|
||||||
final String role;
|
final String role;
|
||||||
|
|
||||||
const BizMember({
|
|
||||||
required this.id,
|
|
||||||
required this.businessId,
|
|
||||||
required this.userId,
|
|
||||||
required this.role,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, businessId, userId, role];
|
List<Object?> get props => <Object?>[id, businessId, userId, role];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Grants a user access to specific [Hub] operations, distinct from [BizMember].
|
/// Grants a user access to specific [Hub] operations, distinct from [BizMember].
|
||||||
class HubMember extends Equatable {
|
class HubMember extends Equatable {
|
||||||
|
|
||||||
|
const HubMember({
|
||||||
|
required this.id,
|
||||||
|
required this.hubId,
|
||||||
|
required this.userId,
|
||||||
|
required this.role,
|
||||||
|
});
|
||||||
/// Unique identifier for this membership.
|
/// Unique identifier for this membership.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -16,13 +23,6 @@ class HubMember extends Equatable {
|
|||||||
/// The role within the hub.
|
/// The role within the hub.
|
||||||
final String role;
|
final String role;
|
||||||
|
|
||||||
const HubMember({
|
|
||||||
required this.id,
|
|
||||||
required this.hubId,
|
|
||||||
required this.userId,
|
|
||||||
required this.role,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, hubId, userId, role];
|
List<Object?> get props => <Object?>[id, hubId, userId, role];
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,14 @@ import 'package:equatable/equatable.dart';
|
|||||||
///
|
///
|
||||||
/// Allows a [User] to be a member of either a [Business] or a [Hub].
|
/// Allows a [User] to be a member of either a [Business] or a [Hub].
|
||||||
class Membership extends Equatable {
|
class Membership extends Equatable {
|
||||||
|
|
||||||
|
const Membership({
|
||||||
|
required this.id,
|
||||||
|
required this.userId,
|
||||||
|
required this.memberableId,
|
||||||
|
required this.memberableType,
|
||||||
|
required this.role,
|
||||||
|
});
|
||||||
/// Unique identifier for the membership record.
|
/// Unique identifier for the membership record.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -19,14 +27,6 @@ class Membership extends Equatable {
|
|||||||
/// The role within that organization (e.g., 'manager', 'viewer').
|
/// The role within that organization (e.g., 'manager', 'viewer').
|
||||||
final String role;
|
final String role;
|
||||||
|
|
||||||
const Membership({
|
|
||||||
required this.id,
|
|
||||||
required this.userId,
|
|
||||||
required this.memberableId,
|
|
||||||
required this.memberableType,
|
|
||||||
required this.role,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, userId, memberableId, memberableType, role];
|
List<Object?> get props => <Object?>[id, userId, memberableId, memberableType, role];
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,18 @@ enum StaffStatus {
|
|||||||
/// Contains all personal and professional details of a staff member.
|
/// Contains all personal and professional details of a staff member.
|
||||||
/// Linked to a [User] via [authProviderId].
|
/// Linked to a [User] via [authProviderId].
|
||||||
class Staff extends Equatable {
|
class Staff extends Equatable {
|
||||||
|
|
||||||
|
const Staff({
|
||||||
|
required this.id,
|
||||||
|
required this.authProviderId,
|
||||||
|
required this.name,
|
||||||
|
required this.email,
|
||||||
|
this.phone,
|
||||||
|
required this.status,
|
||||||
|
this.address,
|
||||||
|
this.avatar,
|
||||||
|
this.livePhoto,
|
||||||
|
});
|
||||||
/// Unique identifier for the staff profile.
|
/// Unique identifier for the staff profile.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -56,20 +68,8 @@ class Staff extends Equatable {
|
|||||||
/// URL to a verified live photo for identity verification.
|
/// URL to a verified live photo for identity verification.
|
||||||
final String? livePhoto;
|
final String? livePhoto;
|
||||||
|
|
||||||
const Staff({
|
|
||||||
required this.id,
|
|
||||||
required this.authProviderId,
|
|
||||||
required this.name,
|
|
||||||
required this.email,
|
|
||||||
this.phone,
|
|
||||||
required this.status,
|
|
||||||
this.address,
|
|
||||||
this.avatar,
|
|
||||||
this.livePhoto,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => <Object?>[
|
||||||
id,
|
id,
|
||||||
authProviderId,
|
authProviderId,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ import 'package:equatable/equatable.dart';
|
|||||||
/// This entity corresponds to the Firebase Auth user record and acts as the
|
/// This entity corresponds to the Firebase Auth user record and acts as the
|
||||||
/// linkage between the authentication system and the specific [Staff] or Client profiles.
|
/// linkage between the authentication system and the specific [Staff] or Client profiles.
|
||||||
class User extends Equatable {
|
class User extends Equatable {
|
||||||
|
|
||||||
|
const User({
|
||||||
|
required this.id,
|
||||||
|
required this.email,
|
||||||
|
this.phone,
|
||||||
|
required this.role,
|
||||||
|
});
|
||||||
/// The unique identifier from the authentication provider (e.g., Firebase UID).
|
/// The unique identifier from the authentication provider (e.g., Firebase UID).
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@@ -18,13 +25,6 @@ class User extends Equatable {
|
|||||||
/// This determines the initial routing and permissions.
|
/// This determines the initial routing and permissions.
|
||||||
final String role;
|
final String role;
|
||||||
|
|
||||||
const User({
|
|
||||||
required this.id,
|
|
||||||
required this.email,
|
|
||||||
this.phone,
|
|
||||||
required this.role,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, email, phone, role];
|
List<Object?> get props => <Object?>[id, email, phone, role];
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,46 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'data/repositories/client_create_order_repository_impl.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'domain/repositories/i_client_create_order_repository.dart';
|
import 'data/repositories_impl/client_create_order_repository_impl.dart';
|
||||||
|
import 'domain/repositories/client_create_order_repository_interface.dart';
|
||||||
|
import 'domain/usecases/create_one_time_order_usecase.dart';
|
||||||
|
import 'domain/usecases/create_rapid_order_usecase.dart';
|
||||||
import 'domain/usecases/get_order_types_usecase.dart';
|
import 'domain/usecases/get_order_types_usecase.dart';
|
||||||
import 'presentation/blocs/client_create_order_bloc.dart';
|
import 'presentation/blocs/client_create_order_bloc.dart';
|
||||||
|
import 'presentation/blocs/one_time_order_bloc.dart';
|
||||||
|
import 'presentation/blocs/rapid_order_bloc.dart';
|
||||||
import 'presentation/pages/create_order_page.dart';
|
import 'presentation/pages/create_order_page.dart';
|
||||||
import 'presentation/pages/one_time_order_page.dart';
|
import 'presentation/pages/one_time_order_page.dart';
|
||||||
import 'presentation/pages/permanent_order_page.dart';
|
import 'presentation/pages/permanent_order_page.dart';
|
||||||
import 'presentation/pages/rapid_order_page.dart';
|
import 'presentation/pages/rapid_order_page.dart';
|
||||||
import 'presentation/pages/recurring_order_page.dart';
|
import 'presentation/pages/recurring_order_page.dart';
|
||||||
|
|
||||||
|
/// Module for the Client Create Order feature.
|
||||||
|
///
|
||||||
|
/// This module orchestrates the dependency injection for the create order feature,
|
||||||
|
/// connecting the domain use cases with their data layer implementations and
|
||||||
|
/// presentation layer BLoCs.
|
||||||
class ClientCreateOrderModule extends Module {
|
class ClientCreateOrderModule extends Module {
|
||||||
|
@override
|
||||||
|
List<Module> get imports => <Module>[DataConnectModule()];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
i.add<IClientCreateOrderRepository>(ClientCreateOrderRepositoryImpl.new);
|
// Repositories
|
||||||
i.add<GetOrderTypesUseCase>(GetOrderTypesUseCase.new);
|
i.addLazySingleton<ClientCreateOrderRepositoryInterface>(
|
||||||
|
() => ClientCreateOrderRepositoryImpl(
|
||||||
|
orderMock: i.get<OrderRepositoryMock>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// UseCases
|
||||||
|
i.addLazySingleton(GetOrderTypesUseCase.new);
|
||||||
|
i.addLazySingleton(CreateOneTimeOrderUseCase.new);
|
||||||
|
i.addLazySingleton(CreateRapidOrderUseCase.new);
|
||||||
|
|
||||||
|
// BLoCs
|
||||||
i.addSingleton<ClientCreateOrderBloc>(ClientCreateOrderBloc.new);
|
i.addSingleton<ClientCreateOrderBloc>(ClientCreateOrderBloc.new);
|
||||||
|
i.add<RapidOrderBloc>(RapidOrderBloc.new);
|
||||||
|
i.add<OneTimeOrderBloc>(OneTimeOrderBloc.new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import '../../domain/entities/create_order_type.dart';
|
|
||||||
import '../../domain/repositories/i_client_create_order_repository.dart';
|
|
||||||
|
|
||||||
class ClientCreateOrderRepositoryImpl implements IClientCreateOrderRepository {
|
|
||||||
@override
|
|
||||||
Future<List<CreateOrderType>> getOrderTypes() async {
|
|
||||||
// Simulating async data fetch
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
return [
|
|
||||||
const CreateOrderType(
|
|
||||||
id: 'rapid',
|
|
||||||
icon: UiIcons.zap,
|
|
||||||
titleKey: 'client_create_order.types.rapid',
|
|
||||||
descriptionKey: 'client_create_order.types.rapid_desc',
|
|
||||||
backgroundColor: UiColors.tagError,
|
|
||||||
borderColor: UiColors.destructive,
|
|
||||||
iconBackgroundColor: UiColors.tagError,
|
|
||||||
iconColor: UiColors.destructive,
|
|
||||||
textColor: UiColors.destructive,
|
|
||||||
descriptionColor: UiColors.textError,
|
|
||||||
),
|
|
||||||
const CreateOrderType(
|
|
||||||
id: 'one-time',
|
|
||||||
icon: UiIcons.calendar,
|
|
||||||
titleKey: 'client_create_order.types.one_time',
|
|
||||||
descriptionKey: 'client_create_order.types.one_time_desc',
|
|
||||||
backgroundColor: UiColors.tagInProgress,
|
|
||||||
borderColor: UiColors.primary,
|
|
||||||
iconBackgroundColor: UiColors.tagInProgress,
|
|
||||||
iconColor: UiColors.primary,
|
|
||||||
textColor: UiColors.primary,
|
|
||||||
descriptionColor: UiColors.primary,
|
|
||||||
),
|
|
||||||
const CreateOrderType(
|
|
||||||
id: 'recurring',
|
|
||||||
icon: UiIcons.rotateCcw,
|
|
||||||
titleKey: 'client_create_order.types.recurring',
|
|
||||||
descriptionKey: 'client_create_order.types.recurring_desc',
|
|
||||||
backgroundColor: UiColors.tagRefunded,
|
|
||||||
borderColor: UiColors.primary,
|
|
||||||
iconBackgroundColor: UiColors.tagRefunded,
|
|
||||||
iconColor: UiColors.primary,
|
|
||||||
textColor: UiColors.primary,
|
|
||||||
descriptionColor: UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
const CreateOrderType(
|
|
||||||
id: 'permanent',
|
|
||||||
icon: UiIcons.briefcase,
|
|
||||||
titleKey: 'client_create_order.types.permanent',
|
|
||||||
descriptionKey: 'client_create_order.types.permanent_desc',
|
|
||||||
backgroundColor: UiColors.tagSuccess,
|
|
||||||
borderColor: UiColors.textSuccess,
|
|
||||||
iconBackgroundColor: UiColors.tagSuccess,
|
|
||||||
iconColor: UiColors.textSuccess,
|
|
||||||
textColor: UiColors.textSuccess,
|
|
||||||
descriptionColor: UiColors.textSuccess,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:krow_data_connect/krow_data_connect.dart' hide OrderType;
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../../domain/repositories/client_create_order_repository_interface.dart';
|
||||||
|
|
||||||
|
/// Implementation of [ClientCreateOrderRepositoryInterface].
|
||||||
|
///
|
||||||
|
/// This implementation delegates all data access to the Data Connect layer,
|
||||||
|
/// specifically using [OrderRepositoryMock] for now as per the platform's mocking strategy.
|
||||||
|
class ClientCreateOrderRepositoryImpl
|
||||||
|
implements ClientCreateOrderRepositoryInterface {
|
||||||
|
|
||||||
|
/// Creates a [ClientCreateOrderRepositoryImpl].
|
||||||
|
///
|
||||||
|
/// Requires an [OrderRepositoryMock] from the Data Connect shared package.
|
||||||
|
ClientCreateOrderRepositoryImpl({required OrderRepositoryMock orderMock})
|
||||||
|
: _orderMock = orderMock;
|
||||||
|
final OrderRepositoryMock _orderMock;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<OrderType>> getOrderTypes() {
|
||||||
|
return _orderMock.getOrderTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createOneTimeOrder(OneTimeOrder order) {
|
||||||
|
return _orderMock.createOneTimeOrder(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createRapidOrder(String description) {
|
||||||
|
return _orderMock.createRapidOrder(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
/// Represents the arguments required for the [CreateOneTimeOrderUseCase].
|
||||||
|
class OneTimeOrderArguments extends UseCaseArgument {
|
||||||
|
|
||||||
|
const OneTimeOrderArguments({required this.order});
|
||||||
|
/// The order details to be created.
|
||||||
|
final OneTimeOrder order;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[order];
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
|
||||||
|
/// Represents the arguments required for the [CreateRapidOrderUseCase].
|
||||||
|
class RapidOrderArguments extends UseCaseArgument {
|
||||||
|
|
||||||
|
const RapidOrderArguments({required this.description});
|
||||||
|
/// The text description of the urgent staffing need.
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[description];
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
/// Entity representing an Order Type.
|
|
||||||
class CreateOrderType extends Equatable {
|
|
||||||
final String id;
|
|
||||||
final IconData icon;
|
|
||||||
final String titleKey; // Key for translation
|
|
||||||
final String descriptionKey; // Key for translation
|
|
||||||
final Color backgroundColor;
|
|
||||||
final Color borderColor;
|
|
||||||
final Color iconBackgroundColor;
|
|
||||||
final Color iconColor;
|
|
||||||
final Color textColor;
|
|
||||||
final Color descriptionColor;
|
|
||||||
|
|
||||||
const CreateOrderType({
|
|
||||||
required this.id,
|
|
||||||
required this.icon,
|
|
||||||
required this.titleKey,
|
|
||||||
required this.descriptionKey,
|
|
||||||
required this.backgroundColor,
|
|
||||||
required this.borderColor,
|
|
||||||
required this.iconBackgroundColor,
|
|
||||||
required this.iconColor,
|
|
||||||
required this.textColor,
|
|
||||||
required this.descriptionColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
id,
|
|
||||||
icon,
|
|
||||||
titleKey,
|
|
||||||
descriptionKey,
|
|
||||||
backgroundColor,
|
|
||||||
borderColor,
|
|
||||||
iconBackgroundColor,
|
|
||||||
iconColor,
|
|
||||||
textColor,
|
|
||||||
descriptionColor,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
/// Interface for the Client Create Order repository.
|
||||||
|
///
|
||||||
|
/// This repository handles the retrieval of available order types and the
|
||||||
|
/// submission of different types of staffing orders (Rapid, One-Time, etc.).
|
||||||
|
abstract interface class ClientCreateOrderRepositoryInterface {
|
||||||
|
/// Retrieves the list of available order types.
|
||||||
|
Future<List<OrderType>> getOrderTypes();
|
||||||
|
|
||||||
|
/// Submits a one-time staffing order.
|
||||||
|
Future<void> createOneTimeOrder(OneTimeOrder order);
|
||||||
|
|
||||||
|
/// Submits a rapid (urgent) staffing order with a text description.
|
||||||
|
Future<void> createRapidOrder(String description);
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import '../entities/create_order_type.dart';
|
|
||||||
|
|
||||||
abstract interface class IClientCreateOrderRepository {
|
|
||||||
Future<List<CreateOrderType>> getOrderTypes();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
import '../arguments/one_time_order_arguments.dart';
|
||||||
|
import '../repositories/client_create_order_repository_interface.dart';
|
||||||
|
|
||||||
|
/// Use case for creating a one-time staffing order.
|
||||||
|
///
|
||||||
|
/// This use case uses the [ClientCreateOrderRepositoryInterface] to submit
|
||||||
|
/// a [OneTimeOrder] provided via [OneTimeOrderArguments].
|
||||||
|
class CreateOneTimeOrderUseCase
|
||||||
|
implements UseCase<OneTimeOrderArguments, void> {
|
||||||
|
|
||||||
|
/// Creates a [CreateOneTimeOrderUseCase].
|
||||||
|
const CreateOneTimeOrderUseCase(this._repository);
|
||||||
|
final ClientCreateOrderRepositoryInterface _repository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> call(OneTimeOrderArguments input) {
|
||||||
|
return _repository.createOneTimeOrder(input.order);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
import '../arguments/rapid_order_arguments.dart';
|
||||||
|
import '../repositories/client_create_order_repository_interface.dart';
|
||||||
|
|
||||||
|
/// Use case for creating a rapid (urgent) staffing order.
|
||||||
|
///
|
||||||
|
/// This use case uses the [ClientCreateOrderRepositoryInterface] to submit
|
||||||
|
/// a text-based urgent request via [RapidOrderArguments].
|
||||||
|
class CreateRapidOrderUseCase implements UseCase<RapidOrderArguments, void> {
|
||||||
|
|
||||||
|
/// Creates a [CreateRapidOrderUseCase].
|
||||||
|
const CreateRapidOrderUseCase(this._repository);
|
||||||
|
final ClientCreateOrderRepositoryInterface _repository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> call(RapidOrderArguments input) {
|
||||||
|
return _repository.createRapidOrder(input.description);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
import '../entities/create_order_type.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import '../repositories/i_client_create_order_repository.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../repositories/client_create_order_repository_interface.dart';
|
||||||
|
|
||||||
class GetOrderTypesUseCase {
|
/// Use case for retrieving the available order types for a client.
|
||||||
final IClientCreateOrderRepository _repository;
|
///
|
||||||
|
/// This use case interacts with the [ClientCreateOrderRepositoryInterface] to
|
||||||
|
/// fetch the list of staffing order types (e.g., Rapid, One-Time).
|
||||||
|
class GetOrderTypesUseCase implements NoInputUseCase<List<OrderType>> {
|
||||||
|
|
||||||
GetOrderTypesUseCase(this._repository);
|
/// Creates a [GetOrderTypesUseCase].
|
||||||
|
const GetOrderTypesUseCase(this._repository);
|
||||||
|
final ClientCreateOrderRepositoryInterface _repository;
|
||||||
|
|
||||||
Future<List<CreateOrderType>> call() {
|
@override
|
||||||
|
Future<List<OrderType>> call() {
|
||||||
return _repository.getOrderTypes();
|
return _repository.getOrderTypes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../domain/usecases/get_order_types_usecase.dart';
|
import '../../domain/usecases/get_order_types_usecase.dart';
|
||||||
import 'client_create_order_event.dart';
|
import 'client_create_order_event.dart';
|
||||||
import 'client_create_order_state.dart';
|
import 'client_create_order_state.dart';
|
||||||
|
|
||||||
|
/// BLoC for managing the list of available order types.
|
||||||
class ClientCreateOrderBloc
|
class ClientCreateOrderBloc
|
||||||
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState> {
|
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState> {
|
||||||
final GetOrderTypesUseCase _getOrderTypesUseCase;
|
|
||||||
|
|
||||||
ClientCreateOrderBloc(this._getOrderTypesUseCase)
|
ClientCreateOrderBloc(this._getOrderTypesUseCase)
|
||||||
: super(const ClientCreateOrderInitial()) {
|
: super(const ClientCreateOrderInitial()) {
|
||||||
on<ClientCreateOrderTypesRequested>(_onTypesRequested);
|
on<ClientCreateOrderTypesRequested>(_onTypesRequested);
|
||||||
}
|
}
|
||||||
|
final GetOrderTypesUseCase _getOrderTypesUseCase;
|
||||||
|
|
||||||
Future<void> _onTypesRequested(
|
Future<void> _onTypesRequested(
|
||||||
ClientCreateOrderTypesRequested event,
|
ClientCreateOrderTypesRequested event,
|
||||||
Emitter<ClientCreateOrderState> emit,
|
Emitter<ClientCreateOrderState> emit,
|
||||||
) async {
|
) async {
|
||||||
final types = await _getOrderTypesUseCase();
|
final List<OrderType> types = await _getOrderTypesUseCase();
|
||||||
emit(ClientCreateOrderLoadSuccess(types));
|
emit(ClientCreateOrderLoadSuccess(types));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ abstract class ClientCreateOrderEvent extends Equatable {
|
|||||||
const ClientCreateOrderEvent();
|
const ClientCreateOrderEvent();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => <Object?>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientCreateOrderTypesRequested extends ClientCreateOrderEvent {
|
class ClientCreateOrderTypesRequested extends ClientCreateOrderEvent {
|
||||||
@@ -12,10 +12,10 @@ class ClientCreateOrderTypesRequested extends ClientCreateOrderEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ClientCreateOrderTypeSelected extends ClientCreateOrderEvent {
|
class ClientCreateOrderTypeSelected extends ClientCreateOrderEvent {
|
||||||
final String typeId;
|
|
||||||
|
|
||||||
const ClientCreateOrderTypeSelected(this.typeId);
|
const ClientCreateOrderTypeSelected(this.typeId);
|
||||||
|
final String typeId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [typeId];
|
List<Object?> get props => <Object?>[typeId];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import '../../domain/entities/create_order_type.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
|
/// Base state for the [ClientCreateOrderBloc].
|
||||||
abstract class ClientCreateOrderState extends Equatable {
|
abstract class ClientCreateOrderState extends Equatable {
|
||||||
const ClientCreateOrderState();
|
const ClientCreateOrderState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => <Object?>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initial state when order types haven't been loaded yet.
|
||||||
class ClientCreateOrderInitial extends ClientCreateOrderState {
|
class ClientCreateOrderInitial extends ClientCreateOrderState {
|
||||||
const ClientCreateOrderInitial();
|
const ClientCreateOrderInitial();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// State representing successfully loaded order types from the repository.
|
||||||
class ClientCreateOrderLoadSuccess extends ClientCreateOrderState {
|
class ClientCreateOrderLoadSuccess extends ClientCreateOrderState {
|
||||||
final List<CreateOrderType> orderTypes;
|
|
||||||
|
|
||||||
const ClientCreateOrderLoadSuccess(this.orderTypes);
|
const ClientCreateOrderLoadSuccess(this.orderTypes);
|
||||||
|
/// The list of available order types retrieved from the domain.
|
||||||
|
final List<OrderType> orderTypes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [orderTypes];
|
List<Object?> get props => <Object?>[orderTypes];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../../domain/arguments/one_time_order_arguments.dart';
|
||||||
|
import '../../domain/usecases/create_one_time_order_usecase.dart';
|
||||||
|
import 'one_time_order_event.dart';
|
||||||
|
import 'one_time_order_state.dart';
|
||||||
|
|
||||||
|
/// BLoC for managing the multi-step one-time order creation form.
|
||||||
|
class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||||
|
|
||||||
|
OneTimeOrderBloc(this._createOneTimeOrderUseCase)
|
||||||
|
: super(OneTimeOrderState.initial()) {
|
||||||
|
on<OneTimeOrderDateChanged>(_onDateChanged);
|
||||||
|
on<OneTimeOrderLocationChanged>(_onLocationChanged);
|
||||||
|
on<OneTimeOrderPositionAdded>(_onPositionAdded);
|
||||||
|
on<OneTimeOrderPositionRemoved>(_onPositionRemoved);
|
||||||
|
on<OneTimeOrderPositionUpdated>(_onPositionUpdated);
|
||||||
|
on<OneTimeOrderSubmitted>(_onSubmitted);
|
||||||
|
}
|
||||||
|
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
|
||||||
|
|
||||||
|
void _onDateChanged(
|
||||||
|
OneTimeOrderDateChanged event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(date: event.date));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLocationChanged(
|
||||||
|
OneTimeOrderLocationChanged event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(location: event.location));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPositionAdded(
|
||||||
|
OneTimeOrderPositionAdded event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
final List<OneTimeOrderPosition> newPositions =
|
||||||
|
List<OneTimeOrderPosition>.from(state.positions)
|
||||||
|
..add(const OneTimeOrderPosition(
|
||||||
|
role: '',
|
||||||
|
count: 1,
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
));
|
||||||
|
emit(state.copyWith(positions: newPositions));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPositionRemoved(
|
||||||
|
OneTimeOrderPositionRemoved event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
if (state.positions.length > 1) {
|
||||||
|
final List<OneTimeOrderPosition> newPositions =
|
||||||
|
List<OneTimeOrderPosition>.from(state.positions)
|
||||||
|
..removeAt(event.index);
|
||||||
|
emit(state.copyWith(positions: newPositions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPositionUpdated(
|
||||||
|
OneTimeOrderPositionUpdated event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) {
|
||||||
|
final List<OneTimeOrderPosition> newPositions =
|
||||||
|
List<OneTimeOrderPosition>.from(state.positions);
|
||||||
|
newPositions[event.index] = event.position;
|
||||||
|
emit(state.copyWith(positions: newPositions));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSubmitted(
|
||||||
|
OneTimeOrderSubmitted event,
|
||||||
|
Emitter<OneTimeOrderState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: OneTimeOrderStatus.loading));
|
||||||
|
try {
|
||||||
|
final OneTimeOrder order = OneTimeOrder(
|
||||||
|
date: state.date,
|
||||||
|
location: state.location,
|
||||||
|
positions: state.positions,
|
||||||
|
);
|
||||||
|
await _createOneTimeOrderUseCase(OneTimeOrderArguments(order: order));
|
||||||
|
emit(state.copyWith(status: OneTimeOrderStatus.success));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: OneTimeOrderStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
abstract class OneTimeOrderEvent extends Equatable {
|
||||||
|
const OneTimeOrderEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderDateChanged extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderDateChanged(this.date);
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[date];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderLocationChanged extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderLocationChanged(this.location);
|
||||||
|
final String location;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[location];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderPositionAdded extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderPositionAdded();
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderPositionRemoved extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderPositionRemoved(this.index);
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderPositionUpdated extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderPositionUpdated(this.index, this.position);
|
||||||
|
final int index;
|
||||||
|
final OneTimeOrderPosition position;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[index, position];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeOrderSubmitted extends OneTimeOrderEvent {
|
||||||
|
const OneTimeOrderSubmitted();
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
enum OneTimeOrderStatus { initial, loading, success, failure }
|
||||||
|
|
||||||
|
class OneTimeOrderState extends Equatable {
|
||||||
|
const OneTimeOrderState({
|
||||||
|
required this.date,
|
||||||
|
required this.location,
|
||||||
|
required this.positions,
|
||||||
|
this.status = OneTimeOrderStatus.initial,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory OneTimeOrderState.initial() {
|
||||||
|
return OneTimeOrderState(
|
||||||
|
date: DateTime.now(),
|
||||||
|
location: '',
|
||||||
|
positions: const <OneTimeOrderPosition>[
|
||||||
|
OneTimeOrderPosition(
|
||||||
|
role: '',
|
||||||
|
count: 1,
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final DateTime date;
|
||||||
|
final String location;
|
||||||
|
final List<OneTimeOrderPosition> positions;
|
||||||
|
final OneTimeOrderStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
OneTimeOrderState copyWith({
|
||||||
|
DateTime? date,
|
||||||
|
String? location,
|
||||||
|
List<OneTimeOrderPosition>? positions,
|
||||||
|
OneTimeOrderStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return OneTimeOrderState(
|
||||||
|
date: date ?? this.date,
|
||||||
|
location: location ?? this.location,
|
||||||
|
positions: positions ?? this.positions,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[
|
||||||
|
date,
|
||||||
|
location,
|
||||||
|
positions,
|
||||||
|
status,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../domain/arguments/rapid_order_arguments.dart';
|
||||||
|
import '../../domain/usecases/create_rapid_order_usecase.dart';
|
||||||
|
import 'rapid_order_event.dart';
|
||||||
|
import 'rapid_order_state.dart';
|
||||||
|
|
||||||
|
/// BLoC for managing the rapid (urgent) order creation flow.
|
||||||
|
class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
|
||||||
|
|
||||||
|
RapidOrderBloc(this._createRapidOrderUseCase)
|
||||||
|
: super(
|
||||||
|
const RapidOrderInitial(
|
||||||
|
examples: <String>[
|
||||||
|
'"We had a call out. Need 2 cooks ASAP"',
|
||||||
|
'"Need 5 bartenders ASAP until 5am"',
|
||||||
|
'"Emergency! Need 3 servers right now till midnight"',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
on<RapidOrderMessageChanged>(_onMessageChanged);
|
||||||
|
on<RapidOrderVoiceToggled>(_onVoiceToggled);
|
||||||
|
on<RapidOrderSubmitted>(_onSubmitted);
|
||||||
|
on<RapidOrderExampleSelected>(_onExampleSelected);
|
||||||
|
}
|
||||||
|
final CreateRapidOrderUseCase _createRapidOrderUseCase;
|
||||||
|
|
||||||
|
void _onMessageChanged(
|
||||||
|
RapidOrderMessageChanged event,
|
||||||
|
Emitter<RapidOrderState> emit,
|
||||||
|
) {
|
||||||
|
if (state is RapidOrderInitial) {
|
||||||
|
emit((state as RapidOrderInitial).copyWith(message: event.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onVoiceToggled(
|
||||||
|
RapidOrderVoiceToggled event,
|
||||||
|
Emitter<RapidOrderState> emit,
|
||||||
|
) async {
|
||||||
|
if (state is RapidOrderInitial) {
|
||||||
|
final RapidOrderInitial currentState = state as RapidOrderInitial;
|
||||||
|
final bool newListeningState = !currentState.isListening;
|
||||||
|
|
||||||
|
emit(currentState.copyWith(isListening: newListeningState));
|
||||||
|
|
||||||
|
// Simulate voice recognition
|
||||||
|
if (newListeningState) {
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
if (state is RapidOrderInitial) {
|
||||||
|
emit(
|
||||||
|
(state as RapidOrderInitial).copyWith(
|
||||||
|
message: 'Need 2 servers for a banquet right now.',
|
||||||
|
isListening: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSubmitted(
|
||||||
|
RapidOrderSubmitted event,
|
||||||
|
Emitter<RapidOrderState> emit,
|
||||||
|
) async {
|
||||||
|
final RapidOrderState currentState = state;
|
||||||
|
if (currentState is RapidOrderInitial) {
|
||||||
|
final String message = currentState.message;
|
||||||
|
emit(const RapidOrderSubmitting());
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _createRapidOrderUseCase(
|
||||||
|
RapidOrderArguments(description: message));
|
||||||
|
emit(const RapidOrderSuccess());
|
||||||
|
} catch (e) {
|
||||||
|
emit(RapidOrderFailure(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onExampleSelected(
|
||||||
|
RapidOrderExampleSelected event,
|
||||||
|
Emitter<RapidOrderState> emit,
|
||||||
|
) {
|
||||||
|
if (state is RapidOrderInitial) {
|
||||||
|
final String cleanedExample = event.example.replaceAll('"', '');
|
||||||
|
emit((state as RapidOrderInitial).copyWith(message: cleanedExample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
abstract class RapidOrderEvent extends Equatable {
|
||||||
|
const RapidOrderEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderMessageChanged extends RapidOrderEvent {
|
||||||
|
|
||||||
|
const RapidOrderMessageChanged(this.message);
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[message];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderVoiceToggled extends RapidOrderEvent {
|
||||||
|
const RapidOrderVoiceToggled();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderSubmitted extends RapidOrderEvent {
|
||||||
|
const RapidOrderSubmitted();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderExampleSelected extends RapidOrderEvent {
|
||||||
|
|
||||||
|
const RapidOrderExampleSelected(this.example);
|
||||||
|
final String example;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[example];
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
abstract class RapidOrderState extends Equatable {
|
||||||
|
const RapidOrderState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderInitial extends RapidOrderState {
|
||||||
|
|
||||||
|
const RapidOrderInitial({
|
||||||
|
this.message = '',
|
||||||
|
this.isListening = false,
|
||||||
|
required this.examples,
|
||||||
|
});
|
||||||
|
final String message;
|
||||||
|
final bool isListening;
|
||||||
|
final List<String> examples;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[message, isListening, examples];
|
||||||
|
|
||||||
|
RapidOrderInitial copyWith({
|
||||||
|
String? message,
|
||||||
|
bool? isListening,
|
||||||
|
List<String>? examples,
|
||||||
|
}) {
|
||||||
|
return RapidOrderInitial(
|
||||||
|
message: message ?? this.message,
|
||||||
|
isListening: isListening ?? this.isListening,
|
||||||
|
examples: examples ?? this.examples,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderSubmitting extends RapidOrderState {
|
||||||
|
const RapidOrderSubmitting();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderSuccess extends RapidOrderState {
|
||||||
|
const RapidOrderSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RapidOrderFailure extends RapidOrderState {
|
||||||
|
|
||||||
|
const RapidOrderFailure(this.error);
|
||||||
|
final String error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[error];
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import '../../domain/entities/create_order_type.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../blocs/client_create_order_bloc.dart';
|
import '../blocs/client_create_order_bloc.dart';
|
||||||
import '../blocs/client_create_order_event.dart';
|
import '../blocs/client_create_order_event.dart';
|
||||||
import '../blocs/client_create_order_state.dart';
|
import '../blocs/client_create_order_state.dart';
|
||||||
@@ -11,7 +11,6 @@ import '../navigation/client_create_order_navigator.dart';
|
|||||||
|
|
||||||
/// One-time helper to map keys to translations since they are dynamic in BLoC state
|
/// One-time helper to map keys to translations since they are dynamic in BLoC state
|
||||||
String _getTranslation(String key) {
|
String _getTranslation(String key) {
|
||||||
// Safe mapping - explicit keys expected
|
|
||||||
if (key == 'client_create_order.types.rapid') {
|
if (key == 'client_create_order.types.rapid') {
|
||||||
return t.client_create_order.types.rapid;
|
return t.client_create_order.types.rapid;
|
||||||
} else if (key == 'client_create_order.types.rapid_desc') {
|
} else if (key == 'client_create_order.types.rapid_desc') {
|
||||||
@@ -76,7 +75,7 @@ class _CreateOrderView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space6),
|
padding: const EdgeInsets.only(bottom: UiConstants.space6),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -103,19 +102,22 @@ class _CreateOrderView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
itemCount: state.orderTypes.length,
|
itemCount: state.orderTypes.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final CreateOrderType type = state.orderTypes[index];
|
final OrderType type = state.orderTypes[index];
|
||||||
|
final _OrderTypeUiMetadata ui =
|
||||||
|
_OrderTypeUiMetadata.fromId(type.id);
|
||||||
|
|
||||||
return _OrderTypeCard(
|
return _OrderTypeCard(
|
||||||
icon: type.icon,
|
icon: ui.icon,
|
||||||
title: _getTranslation(type.titleKey),
|
title: _getTranslation(type.titleKey),
|
||||||
description: _getTranslation(
|
description: _getTranslation(
|
||||||
type.descriptionKey,
|
type.descriptionKey,
|
||||||
),
|
),
|
||||||
backgroundColor: type.backgroundColor,
|
backgroundColor: ui.backgroundColor,
|
||||||
borderColor: type.borderColor,
|
borderColor: ui.borderColor,
|
||||||
iconBackgroundColor: type.iconBackgroundColor,
|
iconBackgroundColor: ui.iconBackgroundColor,
|
||||||
iconColor: type.iconColor,
|
iconColor: ui.iconColor,
|
||||||
textColor: type.textColor,
|
textColor: ui.textColor,
|
||||||
descriptionColor: type.descriptionColor,
|
descriptionColor: ui.descriptionColor,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
switch (type.id) {
|
switch (type.id) {
|
||||||
case 'rapid':
|
case 'rapid':
|
||||||
@@ -149,17 +151,6 @@ class _CreateOrderView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _OrderTypeCard extends StatelessWidget {
|
class _OrderTypeCard extends StatelessWidget {
|
||||||
final IconData icon;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final Color backgroundColor;
|
|
||||||
final Color borderColor;
|
|
||||||
final Color iconBackgroundColor;
|
|
||||||
final Color iconColor;
|
|
||||||
final Color textColor;
|
|
||||||
final Color descriptionColor;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
const _OrderTypeCard({
|
const _OrderTypeCard({
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -173,6 +164,17 @@ class _OrderTypeCard extends StatelessWidget {
|
|||||||
required this.onTap,
|
required this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color borderColor;
|
||||||
|
final Color iconBackgroundColor;
|
||||||
|
final Color iconColor;
|
||||||
|
final Color textColor;
|
||||||
|
final Color descriptionColor;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -187,7 +189,7 @@ class _OrderTypeCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
@@ -213,3 +215,78 @@ class _OrderTypeCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _OrderTypeUiMetadata {
|
||||||
|
|
||||||
|
const _OrderTypeUiMetadata({
|
||||||
|
required this.icon,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.borderColor,
|
||||||
|
required this.iconBackgroundColor,
|
||||||
|
required this.iconColor,
|
||||||
|
required this.textColor,
|
||||||
|
required this.descriptionColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _OrderTypeUiMetadata.fromId(String id) {
|
||||||
|
switch (id) {
|
||||||
|
case 'rapid':
|
||||||
|
return const _OrderTypeUiMetadata(
|
||||||
|
icon: UiIcons.zap,
|
||||||
|
backgroundColor: Color(0xFFFFF7ED),
|
||||||
|
borderColor: Color(0xFFFFEDD5),
|
||||||
|
iconBackgroundColor: Color(0xFFF97316),
|
||||||
|
iconColor: Colors.white,
|
||||||
|
textColor: Color(0xFF9A3412),
|
||||||
|
descriptionColor: Color(0xFFC2410C),
|
||||||
|
);
|
||||||
|
case 'one-time':
|
||||||
|
return const _OrderTypeUiMetadata(
|
||||||
|
icon: UiIcons.calendar,
|
||||||
|
backgroundColor: Color(0xFFF0F9FF),
|
||||||
|
borderColor: Color(0xFFE0F2FE),
|
||||||
|
iconBackgroundColor: Color(0xFF0EA5E9),
|
||||||
|
iconColor: Colors.white,
|
||||||
|
textColor: Color(0xFF075985),
|
||||||
|
descriptionColor: Color(0xFF0369A1),
|
||||||
|
);
|
||||||
|
case 'recurring':
|
||||||
|
return const _OrderTypeUiMetadata(
|
||||||
|
icon: UiIcons.rotateCcw,
|
||||||
|
backgroundColor: Color(0xFFF0FDF4),
|
||||||
|
borderColor: Color(0xFFDCFCE7),
|
||||||
|
iconBackgroundColor: Color(0xFF22C55E),
|
||||||
|
iconColor: Colors.white,
|
||||||
|
textColor: Color(0xFF166534),
|
||||||
|
descriptionColor: Color(0xFF15803D),
|
||||||
|
);
|
||||||
|
case 'permanent':
|
||||||
|
return const _OrderTypeUiMetadata(
|
||||||
|
icon: UiIcons.briefcase,
|
||||||
|
backgroundColor: Color(0xFFF5F3FF),
|
||||||
|
borderColor: Color(0xFFEDE9FE),
|
||||||
|
iconBackgroundColor: Color(0xFF8B5CF6),
|
||||||
|
iconColor: Colors.white,
|
||||||
|
textColor: Color(0xFF5B21B6),
|
||||||
|
descriptionColor: Color(0xFF6D28D9),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const _OrderTypeUiMetadata(
|
||||||
|
icon: UiIcons.help,
|
||||||
|
backgroundColor: Colors.grey,
|
||||||
|
borderColor: Colors.grey,
|
||||||
|
iconBackgroundColor: Colors.grey,
|
||||||
|
iconColor: Colors.white,
|
||||||
|
textColor: Colors.black,
|
||||||
|
descriptionColor: Colors.black54,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final IconData icon;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color borderColor;
|
||||||
|
final Color iconBackgroundColor;
|
||||||
|
final Color iconColor;
|
||||||
|
final Color textColor;
|
||||||
|
final Color descriptionColor;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,648 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../blocs/one_time_order_bloc.dart';
|
||||||
|
import '../blocs/one_time_order_event.dart';
|
||||||
|
import '../blocs/one_time_order_state.dart';
|
||||||
|
|
||||||
|
/// One-Time Order Page - Single event or shift request
|
||||||
class OneTimeOrderPage extends StatelessWidget {
|
class OneTimeOrderPage extends StatelessWidget {
|
||||||
const OneTimeOrderPage({super.key});
|
const OneTimeOrderPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return BlocProvider<OneTimeOrderBloc>(
|
||||||
backgroundColor: UiColors.background,
|
create: (BuildContext context) => Modular.get<OneTimeOrderBloc>(),
|
||||||
appBar: AppBar(
|
child: const _OneTimeOrderView(),
|
||||||
title:
|
);
|
||||||
Text('One-Time Order', style: UiTypography.headline3m.textPrimary),
|
}
|
||||||
leading: IconButton(
|
}
|
||||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
|
||||||
onPressed: () => Modular.to.pop(),
|
class _OneTimeOrderView extends StatelessWidget {
|
||||||
|
const _OneTimeOrderView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn labels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return BlocBuilder<OneTimeOrderBloc, OneTimeOrderState>(
|
||||||
|
builder: (BuildContext context, OneTimeOrderState state) {
|
||||||
|
if (state.status == OneTimeOrderStatus.success) {
|
||||||
|
return const _SuccessView();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: UiColors.background,
|
||||||
|
appBar: AppBar(
|
||||||
|
title:
|
||||||
|
Text(labels.title, style: UiTypography.headline3m.textPrimary),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(UiIcons.chevronLeft,
|
||||||
|
color: UiColors.iconSecondary),
|
||||||
|
onPressed: () => Modular.to.pop(),
|
||||||
|
),
|
||||||
|
backgroundColor: UiColors.white,
|
||||||
|
elevation: 0,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(1.0),
|
||||||
|
child: Container(color: UiColors.border, height: 1.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
_OneTimeOrderForm(state: state),
|
||||||
|
if (state.status == OneTimeOrderStatus.loading)
|
||||||
|
const Center(child: CircularProgressIndicator()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottomNavigationBar: _BottomActionButton(
|
||||||
|
label: labels.create_order,
|
||||||
|
isLoading: state.status == OneTimeOrderStatus.loading,
|
||||||
|
onPressed: () => BlocProvider.of<OneTimeOrderBloc>(context)
|
||||||
|
.add(const OneTimeOrderSubmitted()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OneTimeOrderForm extends StatelessWidget {
|
||||||
|
const _OneTimeOrderForm({required this.state});
|
||||||
|
final OneTimeOrderState state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn labels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
children: <Widget>[
|
||||||
|
_SectionHeader(title: labels.create_your_order),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Date Picker Field
|
||||||
|
_DatePickerField(
|
||||||
|
label: labels.date_label,
|
||||||
|
value: state.date,
|
||||||
|
onChanged: (DateTime date) =>
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context)
|
||||||
|
.add(OneTimeOrderDateChanged(date)),
|
||||||
),
|
),
|
||||||
backgroundColor: UiColors.white,
|
const SizedBox(height: UiConstants.space4),
|
||||||
elevation: 0,
|
|
||||||
bottom: PreferredSize(
|
// Location Field
|
||||||
preferredSize: const Size.fromHeight(1.0),
|
_LocationField(
|
||||||
child: Container(color: UiColors.border, height: 1.0),
|
label: labels.location_label,
|
||||||
|
value: state.location,
|
||||||
|
onChanged: (String location) =>
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context)
|
||||||
|
.add(OneTimeOrderLocationChanged(location)),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
_SectionHeader(
|
||||||
|
title: labels.positions_title,
|
||||||
|
actionLabel: labels.add_position,
|
||||||
|
onAction: () => BlocProvider.of<OneTimeOrderBloc>(context)
|
||||||
|
.add(const OneTimeOrderPositionAdded()),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Positions List
|
||||||
|
...state.positions
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.map((MapEntry<int, OneTimeOrderPosition> entry) {
|
||||||
|
final int index = entry.key;
|
||||||
|
final OneTimeOrderPosition position = entry.value;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: UiConstants.space4),
|
||||||
|
child: _PositionCard(
|
||||||
|
index: index,
|
||||||
|
position: position,
|
||||||
|
isRemovable: state.positions.length > 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const SizedBox(height: 100), // Space for bottom button
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionHeader extends StatelessWidget {
|
||||||
|
const _SectionHeader({
|
||||||
|
required this.title,
|
||||||
|
this.actionLabel,
|
||||||
|
this.onAction,
|
||||||
|
});
|
||||||
|
final String title;
|
||||||
|
final String? actionLabel;
|
||||||
|
final VoidCallback? onAction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(title, style: UiTypography.headline4m.textPrimary),
|
||||||
|
if (actionLabel != null && onAction != null)
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: onAction,
|
||||||
|
icon: const Icon(UiIcons.add, size: 16, color: UiColors.primary),
|
||||||
|
label: Text(actionLabel!, style: UiTypography.body2b.textPrimary),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: UiConstants.space2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatePickerField extends StatelessWidget {
|
||||||
|
const _DatePickerField({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
final String label;
|
||||||
|
final DateTime value;
|
||||||
|
final ValueChanged<DateTime> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(label, style: UiTypography.footnote1m.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: value,
|
||||||
|
firstDate: DateTime.now(),
|
||||||
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||||
|
);
|
||||||
|
if (picked != null) onChanged(picked);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: UiConstants.space3 + 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(UiIcons.calendar,
|
||||||
|
size: 20, color: UiColors.iconSecondary),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Text(
|
||||||
|
DateFormat('EEEE, MMM d, yyyy').format(value),
|
||||||
|
style: UiTypography.body1r.textPrimary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LocationField extends StatelessWidget {
|
||||||
|
const _LocationField({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
final ValueChanged<String> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(label, style: UiTypography.footnote1m.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
// Simplified for now - can use a dropdown or Autocomplete
|
||||||
|
TextField(
|
||||||
|
controller: TextEditingController(text: value)
|
||||||
|
..selection = TextSelection.collapsed(offset: value.length),
|
||||||
|
onChanged: onChanged,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Select Branch/Location',
|
||||||
|
prefixIcon: const Icon(UiIcons.mapPin,
|
||||||
|
size: 20, color: UiColors.iconSecondary),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
borderSide: const BorderSide(color: UiColors.border),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: UiTypography.body1r.textPrimary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PositionCard extends StatelessWidget {
|
||||||
|
const _PositionCard({
|
||||||
|
required this.index,
|
||||||
|
required this.position,
|
||||||
|
required this.isRemovable,
|
||||||
|
});
|
||||||
|
final int index;
|
||||||
|
final OneTimeOrderPosition position;
|
||||||
|
final bool isRemovable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn labels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
boxShadow: <BoxShadow>[
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Center(
|
child: Column(
|
||||||
child: Text('One-Time Order Flow (WIP)',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: UiTypography.body1r.textSecondary),
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'${labels.positions_title} #${index + 1}',
|
||||||
|
style: UiTypography.body1b.textPrimary,
|
||||||
|
),
|
||||||
|
if (isRemovable)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(UiIcons.delete,
|
||||||
|
size: 20, color: UiColors.destructive),
|
||||||
|
onPressed: () => BlocProvider.of<OneTimeOrderBloc>(context)
|
||||||
|
.add(OneTimeOrderPositionRemoved(index)),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: UiConstants.space6),
|
||||||
|
|
||||||
|
// Role (Dropdown simulation)
|
||||||
|
_LabelField(
|
||||||
|
label: labels.select_role,
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
initialValue: position.role.isEmpty ? null : position.role,
|
||||||
|
items: <String>['Server', 'Bartender', 'Cook', 'Busser', 'Host']
|
||||||
|
.map((String role) => DropdownMenuItem<String>(
|
||||||
|
value: role,
|
||||||
|
child:
|
||||||
|
Text(role, style: UiTypography.body1r.textPrimary),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (String? val) {
|
||||||
|
if (val != null) {
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||||
|
OneTimeOrderPositionUpdated(
|
||||||
|
index, position.copyWith(role: val)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: _inputDecoration(UiIcons.briefcase),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Count
|
||||||
|
_LabelField(
|
||||||
|
label: labels.workers_label,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
_CounterButton(
|
||||||
|
icon: UiIcons.minus,
|
||||||
|
onPressed: position.count > 1
|
||||||
|
? () => BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||||
|
OneTimeOrderPositionUpdated(index,
|
||||||
|
position.copyWith(count: position.count - 1)),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4),
|
||||||
|
child: Text('${position.count}',
|
||||||
|
style: UiTypography.headline3m.textPrimary),
|
||||||
|
),
|
||||||
|
_CounterButton(
|
||||||
|
icon: UiIcons.add,
|
||||||
|
onPressed: () =>
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||||
|
OneTimeOrderPositionUpdated(
|
||||||
|
index, position.copyWith(count: position.count + 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Start/End Time
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: _LabelField(
|
||||||
|
label: labels.start_label,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final TimeOfDay? picked = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: const TimeOfDay(hour: 9, minute: 0),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||||
|
OneTimeOrderPositionUpdated(
|
||||||
|
index,
|
||||||
|
position.copyWith(
|
||||||
|
startTime: picked.format(context)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space3),
|
||||||
|
decoration: _boxDecoration(),
|
||||||
|
child: Text(
|
||||||
|
position.startTime.isEmpty
|
||||||
|
? '--:--'
|
||||||
|
: position.startTime,
|
||||||
|
style: UiTypography.body1r.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(
|
||||||
|
child: _LabelField(
|
||||||
|
label: labels.end_label,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final TimeOfDay? picked = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: const TimeOfDay(hour: 17, minute: 0),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||||
|
OneTimeOrderPositionUpdated(
|
||||||
|
index,
|
||||||
|
position.copyWith(endTime: picked.format(context)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space3),
|
||||||
|
decoration: _boxDecoration(),
|
||||||
|
child: Text(
|
||||||
|
position.endTime.isEmpty ? '--:--' : position.endTime,
|
||||||
|
style: UiTypography.body1r.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Lunch Break
|
||||||
|
_LabelField(
|
||||||
|
label: labels.lunch_break_label,
|
||||||
|
child: DropdownButtonFormField<int>(
|
||||||
|
initialValue: position.lunchBreak,
|
||||||
|
items: <int>[0, 30, 45, 60]
|
||||||
|
.map((int mins) => DropdownMenuItem<int>(
|
||||||
|
value: mins,
|
||||||
|
child: Text('${mins}m',
|
||||||
|
style: UiTypography.body1r.textPrimary),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (int? val) {
|
||||||
|
if (val != null) {
|
||||||
|
BlocProvider.of<OneTimeOrderBloc>(context).add(
|
||||||
|
OneTimeOrderPositionUpdated(
|
||||||
|
index, position.copyWith(lunchBreak: val)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: _inputDecoration(UiIcons.clock),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDecoration _inputDecoration(IconData icon) => InputDecoration(
|
||||||
|
prefixIcon: Icon(icon, size: 18, color: UiColors.iconSecondary),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: UiConstants.space3),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
borderSide: const BorderSide(color: UiColors.border),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BoxDecoration _boxDecoration() => BoxDecoration(
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LabelField extends StatelessWidget {
|
||||||
|
const _LabelField({required this.label, required this.child});
|
||||||
|
final String label;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(label, style: UiTypography.footnote1m.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CounterButton extends StatelessWidget {
|
||||||
|
const _CounterButton({required this.icon, this.onPressed});
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onPressed,
|
||||||
|
child: Container(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: onPressed != null
|
||||||
|
? UiColors.border
|
||||||
|
: UiColors.border.withOpacity(0.5)),
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
color: onPressed != null ? UiColors.white : UiColors.background,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 16,
|
||||||
|
color: onPressed != null
|
||||||
|
? UiColors.iconPrimary
|
||||||
|
: UiColors.iconSecondary.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BottomActionButton extends StatelessWidget {
|
||||||
|
const _BottomActionButton({
|
||||||
|
required this.label,
|
||||||
|
required this.onPressed,
|
||||||
|
this.isLoading = false,
|
||||||
|
});
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: UiConstants.space5,
|
||||||
|
right: UiConstants.space5,
|
||||||
|
top: UiConstants.space4,
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space4,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
boxShadow: <BoxShadow>[
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, -4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: isLoading ? null : onPressed,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: UiColors.primary,
|
||||||
|
foregroundColor: UiColors.white,
|
||||||
|
minimumSize: const Size(double.infinity, 56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
child: isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: UiColors.white, strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: Text(label,
|
||||||
|
style: UiTypography.body1b.copyWith(color: UiColors.white)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SuccessView extends StatelessWidget {
|
||||||
|
const _SuccessView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderOneTimeEn labels =
|
||||||
|
t.client_create_order.one_time;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: UiColors.white,
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: UiColors.tagSuccess,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(UiIcons.check,
|
||||||
|
size: 50, color: UiColors.textSuccess),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
Text(
|
||||||
|
labels.success_title,
|
||||||
|
style: UiTypography.headline2m.textPrimary,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
Text(
|
||||||
|
labels.success_message,
|
||||||
|
style: UiTypography.body1r.textSecondary,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space10),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Modular.to.pop(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: UiColors.primary,
|
||||||
|
foregroundColor: UiColors.white,
|
||||||
|
minimumSize: const Size(double.infinity, 56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text('Done',
|
||||||
|
style: UiTypography.body1b.copyWith(color: UiColors.white)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
|
||||||
|
/// Permanent Order Page - Long-term staffing placement
|
||||||
class PermanentOrderPage extends StatelessWidget {
|
class PermanentOrderPage extends StatelessWidget {
|
||||||
const PermanentOrderPage({super.key});
|
const PermanentOrderPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderPermanentEn labels =
|
||||||
|
t.client_create_order.permanent;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.background,
|
backgroundColor: UiColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title:
|
title: Text(labels.title, style: UiTypography.headline3m.textPrimary),
|
||||||
Text('Permanent Order', style: UiTypography.headline3m.textPrimary),
|
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||||
onPressed: () => Modular.to.pop(),
|
onPressed: () => Modular.to.pop(),
|
||||||
@@ -24,8 +28,16 @@ class PermanentOrderPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text('Permanent Order Flow (WIP)',
|
child: Column(
|
||||||
style: UiTypography.body1r.textSecondary),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
labels.subtitle,
|
||||||
|
style: UiTypography.body1r.textSecondary,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,561 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import '../blocs/rapid_order_bloc.dart';
|
||||||
|
import '../blocs/rapid_order_event.dart';
|
||||||
|
import '../blocs/rapid_order_state.dart';
|
||||||
|
|
||||||
|
/// Rapid Order Flow Page - Emergency staffing requests
|
||||||
class RapidOrderPage extends StatelessWidget {
|
class RapidOrderPage extends StatelessWidget {
|
||||||
const RapidOrderPage({super.key});
|
const RapidOrderPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return BlocProvider<RapidOrderBloc>(
|
||||||
backgroundColor: UiColors.background,
|
create: (BuildContext context) => Modular.get<RapidOrderBloc>(),
|
||||||
appBar: AppBar(
|
child: const _RapidOrderView(),
|
||||||
title: Text('Rapid Order', style: UiTypography.headline3m.textPrimary),
|
);
|
||||||
leading: IconButton(
|
}
|
||||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
}
|
||||||
onPressed: () => Modular.to.pop(),
|
|
||||||
|
class _RapidOrderView extends StatelessWidget {
|
||||||
|
const _RapidOrderView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<RapidOrderBloc, RapidOrderState>(
|
||||||
|
builder: (BuildContext context, RapidOrderState state) {
|
||||||
|
if (state is RapidOrderSuccess) {
|
||||||
|
return const _SuccessView();
|
||||||
|
}
|
||||||
|
|
||||||
|
return const _RapidOrderForm();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RapidOrderForm extends StatefulWidget {
|
||||||
|
const _RapidOrderForm();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RapidOrderForm> createState() => _RapidOrderFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RapidOrderFormState extends State<_RapidOrderForm> {
|
||||||
|
final TextEditingController _messageController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_messageController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderRapidEn labels = t.client_create_order.rapid;
|
||||||
|
final DateTime now = DateTime.now();
|
||||||
|
final String dateStr = DateFormat('EEE, MMM dd, yyyy').format(now);
|
||||||
|
final String timeStr = DateFormat('h:mm a').format(now);
|
||||||
|
|
||||||
|
return BlocListener<RapidOrderBloc, RapidOrderState>(
|
||||||
|
listener: (BuildContext context, RapidOrderState state) {
|
||||||
|
if (state is RapidOrderInitial) {
|
||||||
|
if (_messageController.text != state.message) {
|
||||||
|
_messageController.text = state.message;
|
||||||
|
_messageController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: _messageController.text.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: UiColors.background,
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
// Header with gradient
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: MediaQuery.of(context).padding.top + UiConstants.space5,
|
||||||
|
bottom: UiConstants.space5,
|
||||||
|
left: UiConstants.space5,
|
||||||
|
right: UiConstants.space5,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: <Color>[
|
||||||
|
UiColors.destructive,
|
||||||
|
UiColors.destructive.withValues(alpha: 0.85),
|
||||||
|
],
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Modular.to.pop(),
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white.withValues(alpha: 0.2),
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
UiIcons.chevronLeft,
|
||||||
|
color: UiColors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(
|
||||||
|
UiIcons.zap,
|
||||||
|
color: UiColors.accent,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space2),
|
||||||
|
Text(
|
||||||
|
labels.title,
|
||||||
|
style: UiTypography.headline3m.copyWith(
|
||||||
|
color: UiColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
labels.subtitle,
|
||||||
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.white.withValues(alpha: 0.8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
dateStr,
|
||||||
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.white.withValues(alpha: 0.9),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
timeStr,
|
||||||
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.white.withValues(alpha: 0.9),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Content
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
labels.tell_us,
|
||||||
|
style: UiTypography.headline3m.textPrimary,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space2,
|
||||||
|
vertical: UiConstants.space1,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.destructive,
|
||||||
|
borderRadius: UiConstants.radiusSm,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
labels.urgent_badge,
|
||||||
|
style: UiTypography.footnote2b.copyWith(
|
||||||
|
color: UiColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Main Card
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<RapidOrderBloc, RapidOrderState>(
|
||||||
|
builder: (BuildContext context, RapidOrderState state) {
|
||||||
|
final RapidOrderInitial? initialState =
|
||||||
|
state is RapidOrderInitial ? state : null;
|
||||||
|
final bool isSubmitting = state is RapidOrderSubmitting;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
// Icon
|
||||||
|
Container(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: <Color>[
|
||||||
|
UiColors.destructive,
|
||||||
|
UiColors.destructive
|
||||||
|
.withValues(alpha: 0.85),
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
boxShadow: <BoxShadow>[
|
||||||
|
BoxShadow(
|
||||||
|
color: UiColors.destructive
|
||||||
|
.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
UiIcons.zap,
|
||||||
|
color: UiColors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
Text(
|
||||||
|
labels.need_staff,
|
||||||
|
style: UiTypography.headline2m.textPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
Text(
|
||||||
|
labels.type_or_speak,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: UiTypography.body2r.textSecondary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
// Examples
|
||||||
|
if (initialState != null)
|
||||||
|
...initialState.examples
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.map((MapEntry<int, String> entry) {
|
||||||
|
final int index = entry.key;
|
||||||
|
final String example = entry.value;
|
||||||
|
final bool isFirst = index == 0;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: UiConstants.space2),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () =>
|
||||||
|
BlocProvider.of<RapidOrderBloc>(
|
||||||
|
context)
|
||||||
|
.add(
|
||||||
|
RapidOrderExampleSelected(example),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: UiConstants.space3,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isFirst
|
||||||
|
? UiColors.accent
|
||||||
|
.withValues(alpha: 0.15)
|
||||||
|
: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
border: Border.all(
|
||||||
|
color: isFirst
|
||||||
|
? UiColors.accent
|
||||||
|
: UiColors.border,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style:
|
||||||
|
UiTypography.body2r.textPrimary,
|
||||||
|
children: <InlineSpan>[
|
||||||
|
TextSpan(
|
||||||
|
text: labels.example,
|
||||||
|
style: UiTypography
|
||||||
|
.body2b.textPrimary,
|
||||||
|
),
|
||||||
|
TextSpan(text: example),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Input
|
||||||
|
TextField(
|
||||||
|
controller: _messageController,
|
||||||
|
maxLines: 4,
|
||||||
|
onChanged: (String value) {
|
||||||
|
BlocProvider.of<RapidOrderBloc>(context).add(
|
||||||
|
RapidOrderMessageChanged(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: labels.hint,
|
||||||
|
hintStyle: UiTypography.body2r.copyWith(
|
||||||
|
color: UiColors.textPlaceholder,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: UiColors.border,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: UiColors.border,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.all(UiConstants.space4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 52,
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
onPressed: initialState != null
|
||||||
|
? () =>
|
||||||
|
BlocProvider.of<RapidOrderBloc>(
|
||||||
|
context)
|
||||||
|
.add(
|
||||||
|
const RapidOrderVoiceToggled(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
|
UiIcons
|
||||||
|
.bell, // Using bell as mic placeholder
|
||||||
|
size: 20,
|
||||||
|
color:
|
||||||
|
initialState?.isListening == true
|
||||||
|
? UiColors.destructive
|
||||||
|
: UiColors.iconPrimary,
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
initialState?.isListening == true
|
||||||
|
? labels.listening
|
||||||
|
: labels.speak,
|
||||||
|
style: UiTypography.body2b.copyWith(
|
||||||
|
color: initialState?.isListening ==
|
||||||
|
true
|
||||||
|
? UiColors.destructive
|
||||||
|
: UiColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
initialState?.isListening == true
|
||||||
|
? UiColors.destructive
|
||||||
|
.withValues(alpha: 0.05)
|
||||||
|
: UiColors.white,
|
||||||
|
side: BorderSide(
|
||||||
|
color: initialState?.isListening ==
|
||||||
|
true
|
||||||
|
? UiColors.destructive
|
||||||
|
: UiColors.border,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 52,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: isSubmitting ||
|
||||||
|
(initialState?.message
|
||||||
|
.trim()
|
||||||
|
.isEmpty ??
|
||||||
|
true)
|
||||||
|
? null
|
||||||
|
: () =>
|
||||||
|
BlocProvider.of<RapidOrderBloc>(
|
||||||
|
context)
|
||||||
|
.add(
|
||||||
|
const RapidOrderSubmitted(),
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
UiIcons.arrowRight,
|
||||||
|
size: 20,
|
||||||
|
color: UiColors.white,
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
isSubmitting
|
||||||
|
? labels.sending
|
||||||
|
: labels.send,
|
||||||
|
style: UiTypography.body2b.copyWith(
|
||||||
|
color: UiColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: UiColors.primary,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SuccessView extends StatelessWidget {
|
||||||
|
const _SuccessView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderRapidEn labels = t.client_create_order.rapid;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: <Color>[
|
||||||
|
UiColors.primary,
|
||||||
|
UiColors.primary.withValues(alpha: 0.85),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
margin:
|
||||||
|
const EdgeInsets.symmetric(horizontal: UiConstants.space10),
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
boxShadow: <BoxShadow>[
|
||||||
|
BoxShadow(
|
||||||
|
color: UiColors.black.withValues(alpha: 0.2),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: UiColors.accent,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
|
UiIcons.zap,
|
||||||
|
color: UiColors.textPrimary,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
Text(
|
||||||
|
labels.success_title,
|
||||||
|
style: UiTypography.headline1m.textPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
Text(
|
||||||
|
labels.success_message,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: UiTypography.body2r.copyWith(
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 52,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Modular.to.pop(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: UiColors.textPrimary,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
labels.back_to_orders,
|
||||||
|
style: UiTypography.body1b.copyWith(
|
||||||
|
color: UiColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: UiColors.white,
|
|
||||||
elevation: 0,
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(1.0),
|
|
||||||
child: Container(color: UiColors.border, height: 1.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: Text('Rapid Order Flow (WIP)',
|
|
||||||
style: UiTypography.body1r.textSecondary),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
|
||||||
|
/// Recurring Order Page - Ongoing weekly/monthly coverage
|
||||||
class RecurringOrderPage extends StatelessWidget {
|
class RecurringOrderPage extends StatelessWidget {
|
||||||
const RecurringOrderPage({super.key});
|
const RecurringOrderPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final TranslationsClientCreateOrderRecurringEn labels =
|
||||||
|
t.client_create_order.recurring;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.background,
|
backgroundColor: UiColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title:
|
title: Text(labels.title, style: UiTypography.headline3m.textPrimary),
|
||||||
Text('Recurring Order', style: UiTypography.headline3m.textPrimary),
|
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
|
||||||
onPressed: () => Modular.to.pop(),
|
onPressed: () => Modular.to.pop(),
|
||||||
@@ -24,8 +28,16 @@ class RecurringOrderPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text('Recurring Order Flow (WIP)',
|
child: Column(
|
||||||
style: UiTypography.body1r.textSecondary),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
labels.subtitle,
|
||||||
|
style: UiTypography.body1r.textSecondary,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
@@ -324,12 +324,19 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.7.2"
|
||||||
krow_core:
|
krow_core:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../../core"
|
path: "../../../core"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
krow_data_connect:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../../data_connect"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
krow_domain:
|
krow_domain:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -12,12 +12,17 @@ dependencies:
|
|||||||
flutter_bloc: ^8.1.3
|
flutter_bloc: ^8.1.3
|
||||||
flutter_modular: ^6.3.2
|
flutter_modular: ^6.3.2
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
intl: 0.20.2
|
||||||
design_system:
|
design_system:
|
||||||
path: ../../../design_system
|
path: ../../../design_system
|
||||||
core_localization:
|
core_localization:
|
||||||
path: ../../../core_localization
|
path: ../../../core_localization
|
||||||
krow_domain:
|
krow_domain:
|
||||||
path: ../../../domain
|
path: ../../../domain
|
||||||
|
krow_core:
|
||||||
|
path: ../../../core
|
||||||
|
krow_data_connect:
|
||||||
|
path: ../../../data_connect
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user