Merge branch '208-p0-auth-05-get-started-screen' of github.com:Oloodi/krow-workforce into 208-p0-auth-05-get-started-screen

This commit is contained in:
José Salazar
2026-01-22 17:37:01 -05:00
88 changed files with 2737 additions and 596 deletions

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@

View File

@@ -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)"
} }
} }
} }

View File

@@ -245,6 +245,58 @@
"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."
},
"recurring": {
"title": "Orden Recurrente",
"subtitle": "Cobertura continua semanal/mensual",
"placeholder": "Flujo de Orden Recurrente (Trabajo en Progreso)"
},
"permanent": {
"title": "Orden Permanente",
"subtitle": "Colocación de personal a largo plazo",
"placeholder": "Flujo de Orden Permanente (Trabajo en Progreso)"
} }
} }
} }

View File

@@ -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

View File

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

View File

@@ -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',

View File

@@ -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',

View File

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

View File

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

View File

@@ -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',

View File

@@ -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',

View File

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

View File

@@ -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',

View File

@@ -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;

View File

@@ -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';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +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 'package:krow_data_connect/krow_data_connect.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 '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) {
// Repositories
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/client_create_order_repository_interface.dart';
/// Use case for retrieving the available order types for a client.
///
/// 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>> {
/// Creates a [GetOrderTypesUseCase].
const GetOrderTypesUseCase(this._repository);
final ClientCreateOrderRepositoryInterface _repository;
@override
Future<List<OrderType>> call() {
return _repository.getOrderTypes();
}
}

View File

@@ -1,69 +1,24 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:design_system/design_system.dart'; import 'package:krow_domain/krow_domain.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> {
ClientCreateOrderBloc() : super(const ClientCreateOrderInitial()) {
ClientCreateOrderBloc(this._getOrderTypesUseCase)
: super(const ClientCreateOrderInitial()) {
on<ClientCreateOrderTypesRequested>(_onTypesRequested); on<ClientCreateOrderTypesRequested>(_onTypesRequested);
} }
final GetOrderTypesUseCase _getOrderTypesUseCase;
void _onTypesRequested( Future<void> _onTypesRequested(
ClientCreateOrderTypesRequested event, ClientCreateOrderTypesRequested event,
Emitter<ClientCreateOrderState> emit, Emitter<ClientCreateOrderState> emit,
) { ) async {
// In a real app, this might come from a repository or config final List<OrderType> types = await _getOrderTypesUseCase();
final List<CreateOrderType> types = [
const CreateOrderType(
id: 'rapid',
icon: UiIcons.zap,
titleKey: 'client_create_order.types.rapid',
descriptionKey: 'client_create_order.types.rapid_desc',
backgroundColor: UiColors.tagError, // Red-ish background
borderColor: UiColors.destructive, // Red border
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, // Blue-ish
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, // Indigo-ish (Purple sub)
borderColor: UiColors.primary, // No purple, use primary or mix
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, // Green
borderColor: UiColors.textSuccess,
iconBackgroundColor: UiColors.tagSuccess,
iconColor: UiColors.textSuccess,
textColor: UiColors.textSuccess,
descriptionColor: UiColors.textSuccess,
),
];
emit(ClientCreateOrderLoadSuccess(types)); emit(ClientCreateOrderLoadSuccess(types));
} }
} }

View File

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

View File

@@ -1,63 +1,26 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:krow_domain/krow_domain.dart';
/// Represents an available 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,
];
}
/// 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];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,14 +3,15 @@ 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 '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';
import '../navigation/client_create_order_navigator.dart'; import '../navigation/client_create_order_navigator.dart';
import '../widgets/order_type_card.dart';
/// One-time helper to map keys to translations since they are dynamic in BLoC state /// Helper to map keys to localized strings.
String _getTranslation(String key) { String _getTranslation({required 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') {
@@ -31,7 +32,10 @@ String _getTranslation(String key) {
return key; return key;
} }
/// Main entry page for the client create order flow.
/// Allows the user to select the type of order they want to create.
class ClientCreateOrderPage extends StatelessWidget { class ClientCreateOrderPage extends StatelessWidget {
/// Creates a [ClientCreateOrderPage].
const ClientCreateOrderPage({super.key}); const ClientCreateOrderPage({super.key});
@override @override
@@ -50,22 +54,10 @@ class _CreateOrderView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: UiColors.background, backgroundColor: UiColors.bgPrimary,
appBar: AppBar( appBar: UiAppBar(
backgroundColor: UiColors.white, title: t.client_create_order.title,
elevation: 0, onLeadingPressed: () => Modular.to.pop(),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: Container(color: UiColors.border, height: 1.0),
),
leading: IconButton(
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary),
onPressed: () => Modular.to.pop(),
),
title: Text(
t.client_create_order.title,
style: UiTypography.headline3m.textPrimary,
),
), ),
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
@@ -75,7 +67,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(
@@ -102,19 +94,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];
return _OrderTypeCard( final _OrderTypeUiMetadata ui =
icon: type.icon, _OrderTypeUiMetadata.fromId(id: type.id);
title: _getTranslation(type.titleKey),
return OrderTypeCard(
icon: ui.icon,
title: _getTranslation(key: type.titleKey),
description: _getTranslation( description: _getTranslation(
type.descriptionKey, key: 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':
@@ -147,68 +142,92 @@ class _CreateOrderView extends StatelessWidget {
} }
} }
class _OrderTypeCard extends StatelessWidget { /// Metadata for styling order type cards based on their ID.
final IconData icon; class _OrderTypeUiMetadata {
final String title; const _OrderTypeUiMetadata({
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({
required this.icon, required this.icon,
required this.title,
required this.description,
required this.backgroundColor, required this.backgroundColor,
required this.borderColor, required this.borderColor,
required this.iconBackgroundColor, required this.iconBackgroundColor,
required this.iconColor, required this.iconColor,
required this.textColor, required this.textColor,
required this.descriptionColor, required this.descriptionColor,
required this.onTap,
}); });
@override /// Factory to get metadata based on order type ID.
Widget build(BuildContext context) { factory _OrderTypeUiMetadata.fromId({required String id}) {
return GestureDetector( switch (id) {
onTap: onTap, case 'rapid':
child: Container( return const _OrderTypeUiMetadata(
padding: const EdgeInsets.all(UiConstants.space5), icon: UiIcons.zap,
decoration: BoxDecoration( backgroundColor: UiColors.tagPending,
color: backgroundColor, borderColor: UiColors.separatorSpecial,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), iconBackgroundColor: UiColors.textWarning,
border: Border.all(color: borderColor, width: 2), iconColor: UiColors.white,
), textColor: UiColors.textWarning,
child: Column( descriptionColor: UiColors.textWarning,
crossAxisAlignment: CrossAxisAlignment.start, );
mainAxisAlignment: MainAxisAlignment.start, case 'one-time':
children: [ return const _OrderTypeUiMetadata(
Container( icon: UiIcons.calendar,
width: 48, backgroundColor: UiColors.tagInProgress,
height: 48, borderColor: UiColors.primaryInverse,
margin: const EdgeInsets.only(bottom: UiConstants.space3), iconBackgroundColor: UiColors.primary,
decoration: BoxDecoration( iconColor: UiColors.white,
color: iconBackgroundColor, textColor: UiColors.textLink,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), descriptionColor: UiColors.textLink,
), );
child: Icon(icon, color: iconColor, size: 24), case 'recurring':
), return const _OrderTypeUiMetadata(
Text( icon: UiIcons.rotateCcw,
title, backgroundColor: UiColors.tagSuccess,
style: UiTypography.body2b.copyWith(color: textColor), borderColor: UiColors.switchActive,
), iconBackgroundColor: UiColors.textSuccess,
const SizedBox(height: UiConstants.space1), iconColor: UiColors.white,
Text( textColor: UiColors.textSuccess,
description, descriptionColor: UiColors.textSuccess,
style: UiTypography.footnote1r.copyWith(color: descriptionColor), );
), case 'permanent':
], return const _OrderTypeUiMetadata(
), icon: UiIcons.briefcase,
), backgroundColor: UiColors.tagRefunded,
); borderColor: UiColors.primaryInverse,
iconBackgroundColor: UiColors.primary,
iconColor: UiColors.white,
textColor: UiColors.textLink,
descriptionColor: UiColors.textLink,
);
default:
return const _OrderTypeUiMetadata(
icon: UiIcons.help,
backgroundColor: UiColors.bgSecondary,
borderColor: UiColors.border,
iconBackgroundColor: UiColors.iconSecondary,
iconColor: UiColors.white,
textColor: UiColors.textPrimary,
descriptionColor: UiColors.textSecondary,
);
}
} }
/// Icon for the order type.
final IconData icon;
/// Background color for the card.
final Color backgroundColor;
/// Border color for the card.
final Color borderColor;
/// Background color for the icon.
final Color iconBackgroundColor;
/// Color for the icon.
final Color iconColor;
/// Color for the title text.
final Color textColor;
/// Color for the description text.
final Color descriptionColor;
} }

View File

@@ -1,32 +1,208 @@
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: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';
import '../widgets/one_time_order/one_time_order_date_picker.dart';
import '../widgets/one_time_order/one_time_order_location_input.dart';
import '../widgets/one_time_order/one_time_order_position_card.dart';
import '../widgets/one_time_order/one_time_order_section_header.dart';
import '../widgets/one_time_order/one_time_order_success_view.dart';
/// Page for creating a one-time staffing order.
/// Users can specify the date, location, and multiple staff positions required.
class OneTimeOrderPage extends StatelessWidget { class OneTimeOrderPage extends StatelessWidget {
/// Creates a [OneTimeOrderPage].
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(),
),
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('One-Time Order Flow (WIP)',
style: UiTypography.body1r.textSecondary),
),
); );
} }
} }
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 OneTimeOrderSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: 'Done',
onDone: () => Modular.to.pop(),
);
}
return Scaffold(
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: labels.title,
onLeadingPressed: () => Modular.to.pop(),
),
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>[
OneTimeOrderSectionHeader(title: labels.create_your_order),
const SizedBox(height: UiConstants.space4),
OneTimeOrderDatePicker(
label: labels.date_label,
value: state.date,
onChanged: (DateTime date) =>
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderDateChanged(date)),
),
const SizedBox(height: UiConstants.space4),
OneTimeOrderLocationInput(
label: labels.location_label,
value: state.location,
onChanged: (String location) =>
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderLocationChanged(location)),
),
const SizedBox(height: UiConstants.space6),
OneTimeOrderSectionHeader(
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: OneTimeOrderPositionCard(
index: index,
position: position,
isRemovable: state.positions.length > 1,
positionLabel: labels.positions_title,
roleLabel: labels.select_role,
workersLabel: labels.workers_label,
startLabel: labels.start_label,
endLabel: labels.end_label,
lunchLabel: labels.lunch_break_label,
onUpdated: (OneTimeOrderPosition updated) {
BlocProvider.of<OneTimeOrderBloc>(context).add(
OneTimeOrderPositionUpdated(index, updated),
);
},
onRemoved: () {
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderPositionRemoved(index));
},
),
);
}),
const SizedBox(height: 100), // Space for bottom button
],
);
}
}
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: isLoading
? const UiButton(
buttonBuilder: _dummyBuilder,
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: UiColors.primary, strokeWidth: 2),
),
)
: UiButton.primary(
text: label,
onPressed: onPressed,
size: UiButtonSize.large,
),
);
}
static Widget _dummyBuilder(
BuildContext context,
VoidCallback? onPressed,
ButtonStyle? style,
Widget child,
) {
return Center(child: child);
}
}

View File

@@ -1,31 +1,39 @@
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.
/// Placeholder for future implementation.
class PermanentOrderPage extends StatelessWidget { class PermanentOrderPage extends StatelessWidget {
/// Creates a [PermanentOrderPage].
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.bgPrimary,
appBar: AppBar( appBar: UiAppBar(
title: title: labels.title,
Text('Permanent Order', style: UiTypography.headline3m.textPrimary), onLeadingPressed: () => Modular.to.pop(),
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: Center( body: Center(
child: Text('Permanent Order Flow (WIP)', child: Padding(
style: UiTypography.body1r.textSecondary), padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
labels.subtitle,
style: UiTypography.body1r.textSecondary,
textAlign: TextAlign.center,
),
],
),
),
), ),
); );
} }

View File

@@ -1,31 +1,326 @@
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';
import '../widgets/rapid_order/rapid_order_example_card.dart';
import '../widgets/rapid_order/rapid_order_header.dart';
import '../widgets/rapid_order/rapid_order_success_view.dart';
/// Rapid Order Flow Page - Emergency staffing requests.
/// Features voice recognition simulation and quick example selection.
class RapidOrderPage extends StatelessWidget { class RapidOrderPage extends StatelessWidget {
/// Creates a [RapidOrderPage].
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) {
final TranslationsClientCreateOrderRapidEn labels =
t.client_create_order.rapid;
return BlocBuilder<RapidOrderBloc, RapidOrderState>(
builder: (BuildContext context, RapidOrderState state) {
if (state is RapidOrderSuccess) {
return RapidOrderSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.pop(),
);
}
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.bgPrimary,
body: Column(
children: <Widget>[
RapidOrderHeader(
title: labels.title,
subtitle: labels.subtitle,
date: dateStr,
time: timeStr,
onBack: () => Modular.to.pop(),
),
// 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
_AnimatedZapIcon(),
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 isHighlighted = index == 0;
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space2),
child: RapidOrderExampleCard(
example: example,
isHighlighted: isHighlighted,
label: labels.example,
onTap: () =>
BlocProvider.of<RapidOrderBloc>(
context)
.add(
RapidOrderExampleSelected(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.radiusLg,
borderSide: const BorderSide(
color: UiColors.border,
),
),
contentPadding:
const EdgeInsets.all(UiConstants.space4),
),
),
const SizedBox(height: UiConstants.space4),
// Actions
_RapidOrderActions(
labels: labels,
isSubmitting: isSubmitting,
isListening: initialState?.isListening ?? false,
isMessageEmpty: initialState != null &&
initialState.message.trim().isEmpty,
),
],
);
},
),
),
],
),
),
),
],
), ),
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),
), ),
); );
} }
} }
class _AnimatedZapIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return 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,
),
);
}
}
class _RapidOrderActions extends StatelessWidget {
const _RapidOrderActions({
required this.labels,
required this.isSubmitting,
required this.isListening,
required this.isMessageEmpty,
});
final TranslationsClientCreateOrderRapidEn labels;
final bool isSubmitting;
final bool isListening;
final bool isMessageEmpty;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: UiButton.secondary(
text: isListening ? labels.listening : labels.speak,
leadingIcon: UiIcons.bell, // Placeholder for mic
onPressed: () => BlocProvider.of<RapidOrderBloc>(context).add(
const RapidOrderVoiceToggled(),
),
style: OutlinedButton.styleFrom(
backgroundColor: isListening
? UiColors.destructive.withValues(alpha: 0.05)
: null,
side: isListening
? const BorderSide(color: UiColors.destructive)
: null,
),
),
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: UiButton.primary(
text: isSubmitting ? labels.sending : labels.send,
trailingIcon: UiIcons.arrowRight,
onPressed: isSubmitting || isMessageEmpty
? null
: () => BlocProvider.of<RapidOrderBloc>(context).add(
const RapidOrderSubmitted(),
),
),
),
],
);
}
}

View File

@@ -1,31 +1,39 @@
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.
/// Placeholder for future implementation.
class RecurringOrderPage extends StatelessWidget { class RecurringOrderPage extends StatelessWidget {
/// Creates a [RecurringOrderPage].
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.bgPrimary,
appBar: AppBar( appBar: UiAppBar(
title: title: labels.title,
Text('Recurring Order', style: UiTypography.headline3m.textPrimary), onLeadingPressed: () => Modular.to.pop(),
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: Center( body: Center(
child: Text('Recurring Order Flow (WIP)', child: Padding(
style: UiTypography.body1r.textSecondary), padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
labels.subtitle,
style: UiTypography.body1r.textSecondary,
textAlign: TextAlign.center,
),
],
),
),
), ),
); );
} }

View File

@@ -0,0 +1,68 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
/// A date picker field for the one-time order form.
class OneTimeOrderDatePicker extends StatelessWidget {
/// The label text to display above the field.
final String label;
/// The currently selected date.
final DateTime value;
/// Callback when a new date is selected.
final ValueChanged<DateTime> onChanged;
/// Creates a [OneTimeOrderDatePicker].
const OneTimeOrderDatePicker({
required this.label,
required this.value,
required this.onChanged,
super.key,
});
@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,
),
],
),
),
),
],
);
}
}

View File

@@ -0,0 +1,34 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A location input field for the one-time order form.
class OneTimeOrderLocationInput extends StatelessWidget {
/// The label text to display above the field.
final String label;
/// The current location value.
final String value;
/// Callback when the location text changes.
final ValueChanged<String> onChanged;
/// Creates a [OneTimeOrderLocationInput].
const OneTimeOrderLocationInput({
required this.label,
required this.value,
required this.onChanged,
super.key,
});
@override
Widget build(BuildContext context) {
return UiTextField(
label: label,
hintText: 'Select Branch/Location',
controller: TextEditingController(text: value)
..selection = TextSelection.collapsed(offset: value.length),
onChanged: onChanged,
prefixIcon: UiIcons.mapPin,
);
}
}

View File

@@ -0,0 +1,294 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
/// A card widget for editing a specific position in a one-time order.
class OneTimeOrderPositionCard extends StatelessWidget {
/// The index of the position in the list.
final int index;
/// The position entity data.
final OneTimeOrderPosition position;
/// Whether this position can be removed (usually if there's more than one).
final bool isRemovable;
/// Callback when the position data is updated.
final ValueChanged<OneTimeOrderPosition> onUpdated;
/// Callback when the position is removed.
final VoidCallback onRemoved;
/// Label for positions (e.g., "Position").
final String positionLabel;
/// Label for the role selection.
final String roleLabel;
/// Label for the worker count.
final String workersLabel;
/// Label for the start time.
final String startLabel;
/// Label for the end time.
final String endLabel;
/// Label for the lunch break.
final String lunchLabel;
/// Creates a [OneTimeOrderPositionCard].
const OneTimeOrderPositionCard({
required this.index,
required this.position,
required this.isRemovable,
required this.onUpdated,
required this.onRemoved,
required this.positionLabel,
required this.roleLabel,
required this.workersLabel,
required this.startLabel,
required this.endLabel,
required this.lunchLabel,
super.key,
});
@override
Widget build(BuildContext context) {
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),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'$positionLabel #${index + 1}',
style: UiTypography.body1b.textPrimary,
),
if (isRemovable)
IconButton(
icon: const Icon(UiIcons.delete,
size: 20, color: UiColors.destructive),
onPressed: onRemoved,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
),
],
),
const Divider(height: UiConstants.space6),
// Role (Dropdown)
_LabelField(
label: roleLabel,
child: DropdownButtonFormField<String>(
value: 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) {
onUpdated(position.copyWith(role: val));
}
},
decoration: _inputDecoration(UiIcons.briefcase),
),
),
const SizedBox(height: UiConstants.space4),
// Count (Counter)
_LabelField(
label: workersLabel,
child: Row(
children: <Widget>[
_CounterButton(
icon: UiIcons.minus,
onPressed: position.count > 1
? () => onUpdated(
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: () =>
onUpdated(position.copyWith(count: position.count + 1)),
),
],
),
),
const SizedBox(height: UiConstants.space4),
// Start/End Time
Row(
children: <Widget>[
Expanded(
child: _LabelField(
label: startLabel,
child: InkWell(
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 9, minute: 0),
);
if (picked != null) {
onUpdated(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: endLabel,
child: InkWell(
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 17, minute: 0),
);
if (picked != null) {
onUpdated(
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: lunchLabel,
child: DropdownButtonFormField<int>(
value: 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) {
onUpdated(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),
),
),
);
}
}

View File

@@ -0,0 +1,42 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A header widget for sections in the one-time order form.
class OneTimeOrderSectionHeader extends StatelessWidget {
/// The title text for the section.
final String title;
/// Optional label for an action button on the right.
final String? actionLabel;
/// Callback when the action button is tapped.
final VoidCallback? onAction;
/// Creates a [OneTimeOrderSectionHeader].
const OneTimeOrderSectionHeader({
required this.title,
this.actionLabel,
this.onAction,
super.key,
});
@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),
),
),
],
);
}
}

View File

@@ -0,0 +1,71 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A view to display when a one-time order has been successfully created.
class OneTimeOrderSuccessView extends StatelessWidget {
/// The title of the success message.
final String title;
/// The body of the success message.
final String message;
/// Label for the completion button.
final String buttonLabel;
/// Callback when the completion button is tapped.
final VoidCallback onDone;
/// Creates a [OneTimeOrderSuccessView].
const OneTimeOrderSuccessView({
required this.title,
required this.message,
required this.buttonLabel,
required this.onDone,
super.key,
});
@override
Widget build(BuildContext context) {
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(
title,
style: UiTypography.headline2m.textPrimary,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space4),
Text(
message,
style: UiTypography.body1r.textSecondary,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space10),
UiButton.primary(
text: buttonLabel,
onPressed: onDone,
size: UiButtonSize.large,
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,95 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A card widget representing an order type in the creation flow.
class OrderTypeCard extends StatelessWidget {
/// Icon to display at the top of the card.
final IconData icon;
/// Main title of the order type.
final String title;
/// Brief description of what this order type entails.
final String description;
/// Background color of the card.
final Color backgroundColor;
/// Color of the card's border.
final Color borderColor;
/// Background color for the icon container.
final Color iconBackgroundColor;
/// Color of the icon itself.
final Color iconColor;
/// Color of the title text.
final Color textColor;
/// Color of the description text.
final Color descriptionColor;
/// Callback when the card is tapped.
final VoidCallback onTap;
/// Creates an [OrderTypeCard].
const OrderTypeCard({
required this.icon,
required this.title,
required this.description,
required this.backgroundColor,
required this.borderColor,
required this.iconBackgroundColor,
required this.iconColor,
required this.textColor,
required this.descriptionColor,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: borderColor, width: 2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
width: 48,
height: 48,
margin: const EdgeInsets.only(bottom: UiConstants.space3),
decoration: BoxDecoration(
color: iconBackgroundColor,
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
child: Icon(icon, color: iconColor, size: 24),
),
Text(
title,
style: UiTypography.body2b.copyWith(color: textColor),
),
const SizedBox(height: UiConstants.space1),
Expanded(
child: Text(
description,
style:
UiTypography.footnote1r.copyWith(color: descriptionColor),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A card displaying an example message for a rapid order.
class RapidOrderExampleCard extends StatelessWidget {
/// The example text.
final String example;
/// Whether this is the first (highlighted) example.
final bool isHighlighted;
/// The label for the example prefix (e.g., "Example:").
final String label;
/// Callback when the card is tapped.
final VoidCallback onTap;
/// Creates a [RapidOrderExampleCard].
const RapidOrderExampleCard({
required this.example,
required this.isHighlighted,
required this.label,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3,
),
decoration: BoxDecoration(
color: isHighlighted
? UiColors.accent.withValues(alpha: 0.15)
: UiColors.white,
borderRadius: UiConstants.radiusMd,
border: Border.all(
color: isHighlighted ? UiColors.accent : UiColors.border,
),
),
child: RichText(
text: TextSpan(
style: UiTypography.body2r.textPrimary,
children: <InlineSpan>[
TextSpan(
text: label,
style: UiTypography.body2b.textPrimary,
),
TextSpan(text: ' $example'),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,122 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A header widget for the rapid order flow with a gradient background.
class RapidOrderHeader extends StatelessWidget {
/// The title of the page.
final String title;
/// The subtitle or description.
final String subtitle;
/// The formatted current date.
final String date;
/// The formatted current time.
final String time;
/// Callback when the back button is pressed.
final VoidCallback onBack;
/// Creates a [RapidOrderHeader].
const RapidOrderHeader({
required this.title,
required this.subtitle,
required this.date,
required this.time,
required this.onBack,
super.key,
});
@override
Widget build(BuildContext context) {
return 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: onBack,
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(
title,
style: UiTypography.headline3m.copyWith(
color: UiColors.white,
),
),
],
),
Text(
subtitle,
style: UiTypography.footnote2r.copyWith(
color: UiColors.white.withValues(alpha: 0.8),
),
),
],
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text(
date,
style: UiTypography.footnote2r.copyWith(
color: UiColors.white.withValues(alpha: 0.9),
),
),
Text(
time,
style: UiTypography.footnote2r.copyWith(
color: UiColors.white.withValues(alpha: 0.9),
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,108 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A view to display when a rapid order has been successfully created.
class RapidOrderSuccessView extends StatelessWidget {
/// The title of the success message.
final String title;
/// The body of the success message.
final String message;
/// Label for the completion button.
final String buttonLabel;
/// Callback when the completion button is tapped.
final VoidCallback onDone;
/// Creates a [RapidOrderSuccessView].
const RapidOrderSuccessView({
required this.title,
required this.message,
required this.buttonLabel,
required this.onDone,
super.key,
});
@override
Widget build(BuildContext context) {
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(
title,
style: UiTypography.headline1m.textPrimary,
),
const SizedBox(height: UiConstants.space3),
Text(
message,
textAlign: TextAlign.center,
style: UiTypography.body2r.copyWith(
color: UiColors.textSecondary,
height: 1.5,
),
),
const SizedBox(height: UiConstants.space8),
SizedBox(
width: double.infinity,
child: UiButton.primary(
text: buttonLabel,
onPressed: onDone,
size: UiButtonSize.large,
),
),
],
),
),
),
),
),
);
}
}

View File

@@ -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:

View File

@@ -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: