Fix build errors: localization syntax, key paths, and ViewOrderCard widget
This commit is contained in:
@@ -148,9 +148,17 @@
|
||||
"edit_mode_active": "Edit Mode Active",
|
||||
"drag_instruction": "Drag to reorder, toggle visibility",
|
||||
"reset": "Reset",
|
||||
"todays_coverage": "TODAY'S COVERAGE",
|
||||
"percent_covered": "$percent% Covered",
|
||||
"metric_needed": "Needed",
|
||||
"metric_filled": "Filled",
|
||||
"metric_open": "Open",
|
||||
"spending": {
|
||||
"this_week": "This Week",
|
||||
"next_7_days": "Next 7 Days",
|
||||
"shifts_count": "$count shifts",
|
||||
"scheduled_count": "$count scheduled"
|
||||
},
|
||||
"view_all": "View all",
|
||||
"insight_lightbulb": "Save $amount/month",
|
||||
"insight_tip": "Book 48hrs ahead for better rates"
|
||||
@@ -237,6 +245,14 @@
|
||||
"scan_button": "Scan NFC Tag",
|
||||
"tag_identified": "Tag Identified",
|
||||
"assign_button": "Assign Tag"
|
||||
},
|
||||
"delete_dialog": {
|
||||
"title": "Confirm Hub Deletion",
|
||||
"message": "Are you sure you want to delete \"$hubName\"?",
|
||||
"undo_warning": "This action cannot be undone.",
|
||||
"dependency_warning": "Note that if there are any shifts/orders assigned to this hub we shouldn't be able to delete the hub.",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete"
|
||||
}
|
||||
},
|
||||
"client_create_order": {
|
||||
@@ -337,14 +353,26 @@
|
||||
"cancelled": "CANCELLED",
|
||||
"get_direction": "Get direction",
|
||||
"total": "Total",
|
||||
"hrs": "HRS",
|
||||
"hrs": "Hrs",
|
||||
"workers": "$count workers",
|
||||
"clock_in": "CLOCK IN",
|
||||
"clock_out": "CLOCK OUT",
|
||||
"coverage": "Coverage",
|
||||
"workers_label": "$filled/$needed Workers",
|
||||
"confirmed_workers": "Workers Confirmed",
|
||||
"no_workers": "No workers confirmed yet."
|
||||
"no_workers": "No workers confirmed yet.",
|
||||
"today": "Today",
|
||||
"tomorrow": "Tomorrow",
|
||||
"workers_needed": "$count Workers Needed",
|
||||
"all_confirmed": "All Workers Confirmed",
|
||||
"confirmed_workers_title": "CONFIRMED WORKERS",
|
||||
"message_all": "Message All",
|
||||
"show_more_workers": "Show $count more workers",
|
||||
"checked_in": "Checked In",
|
||||
"call_dialog": {
|
||||
"title": "Call",
|
||||
"message": "Do you want to call $phone?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"client_billing": {
|
||||
@@ -498,6 +526,10 @@
|
||||
"menu_items": {
|
||||
"personal_info": "Personal Info",
|
||||
"emergency_contact": "Emergency Contact",
|
||||
"emergency_contact_page": {
|
||||
"save_success": "Emergency contacts saved successfully",
|
||||
"save_continue": "Save & Continue"
|
||||
},
|
||||
"experience": "Experience",
|
||||
"attire": "Attire",
|
||||
"documents": "Documents",
|
||||
@@ -853,6 +885,7 @@
|
||||
},
|
||||
"staff_certificates": {
|
||||
"title": "Certificates",
|
||||
"error_loading": "Error loading certificates",
|
||||
"progress": {
|
||||
"title": "Your Progress",
|
||||
"verified_count": "$completed of $total verified",
|
||||
@@ -988,6 +1021,41 @@
|
||||
"applying_dialog": {
|
||||
"title": "Applying"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"just_now": "Just now",
|
||||
"assigned": "Assigned $time ago",
|
||||
"accept_shift": "Accept shift",
|
||||
"decline_shift": "Decline shift"
|
||||
},
|
||||
"my_shifts_tab": {
|
||||
"confirm_dialog": {
|
||||
"title": "Accept Shift",
|
||||
"message": "Are you sure you want to accept this shift?",
|
||||
"success": "Shift confirmed!"
|
||||
},
|
||||
"decline_dialog": {
|
||||
"title": "Decline Shift",
|
||||
"message": "Are you sure you want to decline this shift? This action cannot be undone.",
|
||||
"success": "Shift declined."
|
||||
},
|
||||
"sections": {
|
||||
"awaiting": "Awaiting Confirmation",
|
||||
"cancelled": "Cancelled Shifts",
|
||||
"confirmed": "Confirmed Shifts"
|
||||
},
|
||||
"empty": {
|
||||
"title": "No shifts this week",
|
||||
"subtitle": "Try finding new jobs in the Find tab"
|
||||
},
|
||||
"date": {
|
||||
"today": "Today",
|
||||
"tomorrow": "Tomorrow"
|
||||
},
|
||||
"card": {
|
||||
"cancelled": "CANCELLED",
|
||||
"compensation": "• 4hr compensation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"staff_time_card": {
|
||||
|
||||
@@ -148,9 +148,17 @@
|
||||
"edit_mode_active": "Modo Edición Activo",
|
||||
"drag_instruction": "Arrastra para reordenar, cambia la visibilidad",
|
||||
"reset": "Restablecer",
|
||||
"todays_coverage": "COBERTURA DE HOY",
|
||||
"percent_covered": "$percent% Cubierto",
|
||||
"metric_needed": "Necesario",
|
||||
"metric_filled": "Lleno",
|
||||
"metric_open": "Abierto",
|
||||
"spending": {
|
||||
"this_week": "Esta Semana",
|
||||
"next_7_days": "Próximos 7 Días",
|
||||
"shifts_count": "$count turnos",
|
||||
"scheduled_count": "$count programados"
|
||||
},
|
||||
"view_all": "Ver todo",
|
||||
"insight_lightbulb": "Ahorra $amount/mes",
|
||||
"insight_tip": "Reserva con 48h de antelación para mejores tarifas"
|
||||
@@ -237,6 +245,14 @@
|
||||
"scan_button": "Escanear Etiqueta NFC",
|
||||
"tag_identified": "Etiqueta Identificada",
|
||||
"assign_button": "Asignar Etiqueta"
|
||||
},
|
||||
"delete_dialog": {
|
||||
"title": "Confirmar eliminación de Hub",
|
||||
"message": "¿Estás seguro de que quieres eliminar \"$hubName\"?",
|
||||
"undo_warning": "Esta acción no se puede deshacer.",
|
||||
"dependency_warning": "Ten en cuenta que si hay turnos/órdenes asignados a este hub no deberíamos poder eliminarlo.",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar"
|
||||
}
|
||||
},
|
||||
"client_create_order": {
|
||||
@@ -337,14 +353,26 @@
|
||||
"cancelled": "CANCELADO",
|
||||
"get_direction": "Obtener dirección",
|
||||
"total": "Total",
|
||||
"hrs": "HRS",
|
||||
"hrs": "Hrs",
|
||||
"workers": "$count trabajadores",
|
||||
"clock_in": "ENTRADA",
|
||||
"clock_out": "SALIDA",
|
||||
"coverage": "Cobertura",
|
||||
"workers_label": "$filled/$needed Trabajadores",
|
||||
"confirmed_workers": "Trabajadores Confirmados",
|
||||
"no_workers": "Ningún trabajador confirmado aún."
|
||||
"no_workers": "Ningún trabajador confirmado aún.",
|
||||
"today": "Hoy",
|
||||
"tomorrow": "Mañana",
|
||||
"workers_needed": "$count Trabajadores Necesarios",
|
||||
"all_confirmed": "Todos los trabajadores confirmados",
|
||||
"confirmed_workers_title": "TRABAJADORES CONFIRMADOS",
|
||||
"message_all": "Mensaje a todos",
|
||||
"show_more_workers": "Mostrar $count trabajadores más",
|
||||
"checked_in": "Registrado",
|
||||
"call_dialog": {
|
||||
"title": "Llamar",
|
||||
"message": "¿Quieres llamar a $phone?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"client_billing": {
|
||||
@@ -498,6 +526,10 @@
|
||||
"menu_items": {
|
||||
"personal_info": "Información Personal",
|
||||
"emergency_contact": "Contacto de Emergencia",
|
||||
"emergency_contact_page": {
|
||||
"save_success": "Contactos de emergencia guardados con éxito",
|
||||
"save_continue": "Guardar y Continuar"
|
||||
},
|
||||
"experience": "Experiencia",
|
||||
"attire": "Vestimenta",
|
||||
"documents": "Documentos",
|
||||
@@ -853,6 +885,7 @@
|
||||
},
|
||||
"staff_certificates": {
|
||||
"title": "Certificados",
|
||||
"error_loading": "Error al cargar certificados",
|
||||
"progress": {
|
||||
"title": "Tu Progreso",
|
||||
"verified_count": "$completed de $total verificados",
|
||||
@@ -988,6 +1021,41 @@
|
||||
"applying_dialog": {
|
||||
"title": "Solicitando"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"just_now": "Recién",
|
||||
"assigned": "Asignado hace $time",
|
||||
"accept_shift": "Aceptar turno",
|
||||
"decline_shift": "Rechazar turno"
|
||||
},
|
||||
"my_shifts_tab": {
|
||||
"confirm_dialog": {
|
||||
"title": "Aceptar Turno",
|
||||
"message": "¿Estás seguro de que quieres aceptar este turno?",
|
||||
"success": "¡Turno confirmado!"
|
||||
},
|
||||
"decline_dialog": {
|
||||
"title": "Rechazar Turno",
|
||||
"message": "¿Estás seguro de que quieres rechazar este turno? Esta acción no se puede deshacer.",
|
||||
"success": "Turno rechazado."
|
||||
},
|
||||
"sections": {
|
||||
"awaiting": "Esperando Confirmación",
|
||||
"cancelled": "Turnos Cancelados",
|
||||
"confirmed": "Turnos Confirmados"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Sin turnos esta semana",
|
||||
"subtitle": "Intenta buscar nuevos trabajos en la pestaña Buscar"
|
||||
},
|
||||
"date": {
|
||||
"today": "Hoy",
|
||||
"tomorrow": "Mañana"
|
||||
},
|
||||
"card": {
|
||||
"cancelled": "CANCELADO",
|
||||
"compensation": "• Compensación de 4h"
|
||||
}
|
||||
}
|
||||
},
|
||||
"staff_time_card": {
|
||||
|
||||
@@ -238,7 +238,7 @@ class AuthRepositoryImpl
|
||||
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
|
||||
await executeProtected(() => _dataConnect.getUserById(id: firebaseUserId).execute());
|
||||
final dc.GetUserByIdUser? user = response.data.user;
|
||||
return user != null && user.userRole == 'BUSINESS';
|
||||
return user != null && (user.userRole == 'BUSINESS' || user.userRole == 'BOTH');
|
||||
}
|
||||
|
||||
/// Creates Business and User entities in PostgreSQL for a Firebase user.
|
||||
@@ -261,18 +261,28 @@ class AuthRepositoryImpl
|
||||
final dc.CreateBusinessBusinessInsert businessData = createBusinessResponse.data.business_insert;
|
||||
onBusinessCreated(businessData.id);
|
||||
|
||||
// Create User entity in PostgreSQL
|
||||
// Check if User entity already exists in PostgreSQL
|
||||
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> userResult =
|
||||
await executeProtected(() => _dataConnect.getUserById(id: firebaseUser.uid).execute());
|
||||
final dc.GetUserByIdUser? existingUser = userResult.data.user;
|
||||
|
||||
final OperationResult<dc.CreateUserData, dc.CreateUserVariables> createUserResponse =
|
||||
await executeProtected(() => _dataConnect.createUser(
|
||||
id: firebaseUser.uid,
|
||||
role: dc.UserBaseRole.USER,
|
||||
)
|
||||
.email(email)
|
||||
.userRole('BUSINESS')
|
||||
.execute());
|
||||
|
||||
final dc.CreateUserUserInsert newUserData = createUserResponse.data.user_insert;
|
||||
if (existingUser != null) {
|
||||
// User exists (likely in another app like STAFF). Update role to BOTH.
|
||||
await executeProtected(() => _dataConnect.updateUser(
|
||||
id: firebaseUser.uid,
|
||||
)
|
||||
.userRole('BOTH')
|
||||
.execute());
|
||||
} else {
|
||||
// Create new User entity in PostgreSQL
|
||||
await executeProtected(() => _dataConnect.createUser(
|
||||
id: firebaseUser.uid,
|
||||
role: dc.UserBaseRole.USER,
|
||||
)
|
||||
.email(email)
|
||||
.userRole('BUSINESS')
|
||||
.execute());
|
||||
}
|
||||
|
||||
return _getUserProfile(
|
||||
firebaseUserId: firebaseUser.uid,
|
||||
@@ -331,11 +341,11 @@ class AuthRepositoryImpl
|
||||
technicalMessage: 'Firebase UID $firebaseUserId not found in users table',
|
||||
);
|
||||
}
|
||||
if (requireBusinessRole && user.userRole != 'BUSINESS') {
|
||||
if (requireBusinessRole && user.userRole != 'BUSINESS' && user.userRole != 'BOTH') {
|
||||
await _firebaseAuth.signOut();
|
||||
dc.ClientSessionStore.instance.clear();
|
||||
throw UnauthorizedAppException(
|
||||
technicalMessage: 'User role is ${user.userRole}, expected BUSINESS',
|
||||
technicalMessage: 'User role is ${user.userRole}, expected BUSINESS or BOTH',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,94 +27,108 @@ class ClientGetStartedPage extends StatelessWidget {
|
||||
),
|
||||
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
// Logo
|
||||
Center(
|
||||
child: Image.asset(
|
||||
UiImageAssets.logoBlue,
|
||||
height: 40,
|
||||
fit: BoxFit.contain,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
// Logo
|
||||
Center(
|
||||
child: Image.asset(
|
||||
UiImageAssets.logoBlue,
|
||||
height: 40,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Content Cards Area (Keeping prototype layout)
|
||||
Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space6,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
// Representative cards from prototype
|
||||
Positioned(
|
||||
top: 20,
|
||||
left: 0,
|
||||
right: 20,
|
||||
child: _ShiftOrderCard(),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 40,
|
||||
right: 0,
|
||||
left: 40,
|
||||
child: _WorkerProfileCard(),
|
||||
),
|
||||
Positioned(
|
||||
top: 60,
|
||||
right: 10,
|
||||
child: _CalendarCard(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Bottom Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space6,
|
||||
vertical: UiConstants.space10,
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_authentication.get_started_page.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.displayM,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Text(
|
||||
t.client_authentication.get_started_page
|
||||
.subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Sign In Button
|
||||
UiButton.primary(
|
||||
text: t.client_authentication.get_started_page
|
||||
.sign_in_button,
|
||||
onPressed: () => Modular.to.toClientSignIn(),
|
||||
fullWidth: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Create Account Button
|
||||
UiButton.secondary(
|
||||
text: t.client_authentication.get_started_page
|
||||
.create_account_button,
|
||||
onPressed: () => Modular.to.toClientSignUp(),
|
||||
fullWidth: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Content Cards Area (Keeping prototype layout)
|
||||
Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space6,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
// Representative cards from prototype
|
||||
Positioned(
|
||||
top: 20,
|
||||
left: 0,
|
||||
right: 20,
|
||||
child: _ShiftOrderCard(),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 40,
|
||||
right: 0,
|
||||
left: 40,
|
||||
child: _WorkerProfileCard(),
|
||||
),
|
||||
Positioned(top: 60, right: 10, child: _CalendarCard()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Bottom Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space6,
|
||||
vertical: UiConstants.space10,
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_authentication.get_started_page.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.displayM,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Text(
|
||||
t.client_authentication.get_started_page.subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Sign In Button
|
||||
UiButton.primary(
|
||||
text: t
|
||||
.client_authentication
|
||||
.get_started_page
|
||||
.sign_in_button,
|
||||
onPressed: () => Modular.to.toClientSignIn(),
|
||||
fullWidth: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Create Account Button
|
||||
UiButton.secondary(
|
||||
text: t
|
||||
.client_authentication
|
||||
.get_started_page
|
||||
.create_account_button,
|
||||
onPressed: () => Modular.to.toClientSignUp(),
|
||||
fullWidth: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -151,10 +151,12 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
shiftId: app.shiftId,
|
||||
roleId: app.roleId,
|
||||
title: app.shiftRole.role.name,
|
||||
location: app.shiftRole.shift.location ?? '',
|
||||
startTime: '00:00',
|
||||
workersNeeded: 0,
|
||||
date: date,
|
||||
location: app.shiftRole.shift.location ??
|
||||
app.shiftRole.shift.locationAddress ??
|
||||
'',
|
||||
startTime: _formatTime(app.shiftRole.startTime) ?? '00:00',
|
||||
workersNeeded: app.shiftRole.count,
|
||||
date: app.shiftRole.shift.date?.toDateTime() ?? date,
|
||||
workers: <CoverageWorker>[],
|
||||
);
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ class OneTimeOrderSuccessView extends StatelessWidget {
|
||||
color: UiColors.accent,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
|
||||
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
UiIcons.check,
|
||||
|
||||
@@ -72,6 +72,12 @@ class _RapidOrderFormState extends State<_RapidOrderForm> {
|
||||
TextPosition(offset: _messageController.text.length),
|
||||
);
|
||||
}
|
||||
} else if (state is RapidOrderFailure) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: translateErrorKey(state.error),
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -47,7 +48,7 @@ class CoverageWidget extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"TODAY'S COVERAGE",
|
||||
t.client_home.dashboard.todays_coverage,
|
||||
style: UiTypography.footnote1b.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
letterSpacing: 0.5,
|
||||
@@ -64,8 +65,8 @@ class CoverageWidget extends StatelessWidget {
|
||||
color: backgroundColor,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Text(
|
||||
'$coveragePercent% Covered',
|
||||
child: Text(
|
||||
t.client_home.dashboard.percent_covered(percent: coveragePercent),
|
||||
style: UiTypography.footnote2b.copyWith(color: textColor),
|
||||
),
|
||||
),
|
||||
@@ -81,7 +82,7 @@ class CoverageWidget extends StatelessWidget {
|
||||
child: _MetricCard(
|
||||
icon: UiIcons.target,
|
||||
iconColor: UiColors.primary,
|
||||
label: 'Needed',
|
||||
label: t.client_home.dashboard.metric_needed,
|
||||
value: '$totalNeeded',
|
||||
),
|
||||
),
|
||||
@@ -91,7 +92,7 @@ class CoverageWidget extends StatelessWidget {
|
||||
child: _MetricCard(
|
||||
icon: UiIcons.success,
|
||||
iconColor: UiColors.iconSuccess,
|
||||
label: 'Filled',
|
||||
label: t.client_home.dashboard.metric_filled,
|
||||
value: '$totalConfirmed',
|
||||
valueColor: UiColors.textSuccess,
|
||||
),
|
||||
@@ -101,7 +102,7 @@ class CoverageWidget extends StatelessWidget {
|
||||
child: _MetricCard(
|
||||
icon: UiIcons.error,
|
||||
iconColor: UiColors.iconError,
|
||||
label: 'Open',
|
||||
label: t.client_home.dashboard.metric_open,
|
||||
value: '${totalNeeded - totalConfirmed}',
|
||||
valueColor: UiColors.textError,
|
||||
),
|
||||
|
||||
@@ -65,6 +65,8 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
|
||||
bool _showSuccess = false;
|
||||
Map<String, dynamic>? _submitData;
|
||||
bool _isSubmitting = false;
|
||||
String? _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -190,7 +192,25 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
}
|
||||
|
||||
Future<void> _handleSubmit() async {
|
||||
await _submitNewOrder();
|
||||
if (_isSubmitting) return;
|
||||
|
||||
setState(() {
|
||||
_isSubmitting = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
try {
|
||||
await _submitNewOrder();
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSubmitting = false;
|
||||
_errorMessage = 'Failed to create order. Please try again.';
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(_errorMessage!)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _submitNewOrder() async {
|
||||
@@ -296,6 +316,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
'date': _dateController.text,
|
||||
};
|
||||
_showSuccess = true;
|
||||
_isSubmitting = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -770,7 +791,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
|
||||
UiButton.primary(
|
||||
text: widget.initialData != null ? 'Update Order' : 'Post Order',
|
||||
onPressed: widget.isLoading ? null : _handleSubmit,
|
||||
onPressed: (widget.isLoading || _isSubmitting) ? null : _handleSubmit,
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom + UiConstants.space5),
|
||||
],
|
||||
|
||||
@@ -78,7 +78,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'This Week',
|
||||
t.client_home.dashboard.spending.this_week,
|
||||
style: UiTypography.footnote2r.white.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
fontSize: 9,
|
||||
@@ -93,7 +93,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$weeklyShifts shifts',
|
||||
t.client_home.dashboard.spending.shifts_count(count: weeklyShifts),
|
||||
style: UiTypography.footnote2r.white.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.6),
|
||||
fontSize: 9,
|
||||
@@ -107,7 +107,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Next 7 Days',
|
||||
t.client_home.dashboard.spending.next_7_days,
|
||||
style: UiTypography.footnote2r.white.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
fontSize: 9,
|
||||
@@ -122,7 +122,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$next7DaysScheduled scheduled',
|
||||
t.client_home.dashboard.spending.scheduled_count(count: next7DaysScheduled),
|
||||
style: UiTypography.footnote2r.white.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.6),
|
||||
fontSize: 9,
|
||||
|
||||
@@ -227,23 +227,23 @@ class ClientHubsPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _confirmDeleteHub(BuildContext context, Hub hub) async {
|
||||
final String hubName = hub.name.isEmpty ? 'this hub' : hub.name;
|
||||
final String hubName = hub.name.isEmpty ? t.client_hubs.title : hub.name;
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertDialog(
|
||||
title: const Text('Confirm Hub Deletion'),
|
||||
title: Text(t.client_hubs.delete_dialog.title),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text('Are you sure you want to delete "$hubName"?'),
|
||||
Text(t.client_hubs.delete_dialog.message(hubName: hubName)),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
const Text('This action cannot be undone.'),
|
||||
Text(t.client_hubs.delete_dialog.undo_warning),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
'Note that if there are any shifts/orders assigned to this hub we shouldn\'t be able to delete the hub.',
|
||||
t.client_hubs.delete_dialog.dependency_warning,
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
@@ -253,7 +253,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Modular.to.pop(),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(t.client_hubs.delete_dialog.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@@ -265,7 +265,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
),
|
||||
child: const Text('Delete'),
|
||||
child: Text(t.client_hubs.delete_dialog.delete),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -89,8 +89,8 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
final DateTime tomorrow = today.add(const Duration(days: 1));
|
||||
final DateTime checkDate = DateTime(date.year, date.month, date.day);
|
||||
|
||||
if (checkDate == today) return 'Today';
|
||||
if (checkDate == tomorrow) return 'Tomorrow';
|
||||
if (checkDate == today) return t.client_view_orders.card.today;
|
||||
if (checkDate == tomorrow) return t.client_view_orders.card.tomorrow;
|
||||
return DateFormat('EEE, MMM d').format(date);
|
||||
} catch (_) {
|
||||
return dateStr;
|
||||
@@ -279,19 +279,19 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
_buildStatItem(
|
||||
icon: UiIcons.dollar,
|
||||
value: '\$${cost.round()}',
|
||||
label: 'Total',
|
||||
label: t.client_view_orders.card.total,
|
||||
),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem(
|
||||
icon: UiIcons.clock,
|
||||
value: hours.toStringAsFixed(1),
|
||||
label: 'Hrs',
|
||||
label: t.client_view_orders.card.hrs,
|
||||
),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem(
|
||||
icon: UiIcons.users,
|
||||
value: '${order.workersNeeded}',
|
||||
label: 'Workers',
|
||||
label: t.client_create_order.one_time.workers_label,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -303,14 +303,14 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _buildTimeDisplay(
|
||||
label: 'Clock In',
|
||||
label: t.client_view_orders.card.clock_in,
|
||||
time: _formatTime(timeStr: order.startTime),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: _buildTimeDisplay(
|
||||
label: 'Clock Out',
|
||||
label: t.client_view_orders.card.clock_out,
|
||||
time: _formatTime(timeStr: order.endTime),
|
||||
),
|
||||
),
|
||||
@@ -341,8 +341,8 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
coveragePercent == 100
|
||||
? 'All Workers Confirmed'
|
||||
: '${order.workersNeeded} Workers Needed',
|
||||
? t.client_view_orders.card.all_confirmed
|
||||
: t.client_view_orders.card.workers_needed(count: order.workersNeeded),
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
],
|
||||
@@ -378,7 +378,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Text(
|
||||
'+${order.confirmedApps.length - 3} more',
|
||||
t.client_view_orders.card.show_more_workers(count: order.confirmedApps.length - 3),
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
),
|
||||
@@ -408,13 +408,13 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'CONFIRMED WORKERS',
|
||||
t.client_view_orders.card.confirmed_workers_title,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: Text(
|
||||
'Message All',
|
||||
t.client_view_orders.card.message_all,
|
||||
style: UiTypography.footnote2b.copyWith(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
@@ -433,7 +433,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
child: TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
'Show ${order.confirmedApps.length - 5} more workers',
|
||||
t.client_view_orders.card.show_more_workers(count: order.confirmedApps.length - 5),
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
@@ -569,7 +569,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
child: Text(
|
||||
'Checked In',
|
||||
t.client_view_orders.card.checked_in,
|
||||
style: UiTypography.titleUppercase4m.copyWith(
|
||||
color: UiColors.textSuccess,
|
||||
),
|
||||
@@ -615,16 +615,16 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Call'),
|
||||
content: Text('Do you want to call $phone?'),
|
||||
title: Text(t.client_view_orders.card.call_dialog.title),
|
||||
content: Text(t.client_view_orders.card.call_dialog.message(phone: phone)),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Call'),
|
||||
child: Text(t.client_view_orders.card.call_dialog.title),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -156,24 +156,30 @@ class AuthRepositoryImpl
|
||||
.userRole('STAFF')
|
||||
.execute());
|
||||
} else {
|
||||
if (user.userRole != 'STAFF') {
|
||||
await firebaseAuth.signOut();
|
||||
throw const domain.UnauthorizedAppException(
|
||||
technicalMessage: 'User is not authorized for this app.',
|
||||
);
|
||||
}
|
||||
// User exists in PostgreSQL. Check if they have a STAFF profile.
|
||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||
staffResponse = await executeProtected(() => dataConnect
|
||||
.getStaffByUserId(
|
||||
userId: firebaseUser.uid,
|
||||
)
|
||||
.execute());
|
||||
|
||||
if (staffResponse.data.staffs.isNotEmpty) {
|
||||
// If profile exists, they should use Login mode.
|
||||
await firebaseAuth.signOut();
|
||||
throw const domain.AccountExistsException(
|
||||
technicalMessage: 'This user already has a staff profile. Please log in.',
|
||||
technicalMessage:
|
||||
'This user already has a staff profile. Please log in.',
|
||||
);
|
||||
}
|
||||
|
||||
// If they don't have a staff profile but they exist as BUSINESS,
|
||||
// they are allowed to "Sign Up" for Staff.
|
||||
// We update their userRole to 'BOTH'.
|
||||
if (user.userRole == 'BUSINESS') {
|
||||
await executeProtected(() =>
|
||||
dataConnect.updateUser(id: firebaseUser.uid).userRole('BOTH').execute());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (user == null) {
|
||||
@@ -182,7 +188,8 @@ class AuthRepositoryImpl
|
||||
technicalMessage: 'Authenticated user profile not found in database.',
|
||||
);
|
||||
}
|
||||
if (user.userRole != 'STAFF') {
|
||||
// Allow STAFF or BOTH roles to log in to the Staff App
|
||||
if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
|
||||
await firebaseAuth.signOut();
|
||||
throw const domain.UnauthorizedAppException(
|
||||
technicalMessage: 'User is not authorized for this app.',
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
|
||||
abstract class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
@override
|
||||
List<Object> get props => <Object>[];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
/// Event for requesting a sign-in with a phone number.
|
||||
@@ -19,7 +19,7 @@ class AuthSignInRequested extends AuthEvent {
|
||||
const AuthSignInRequested({this.phoneNumber, required this.mode});
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[mode];
|
||||
List<Object?> get props => <Object?>[phoneNumber, mode];
|
||||
}
|
||||
|
||||
/// Event for submitting an OTP (One-Time Password) for verification.
|
||||
@@ -43,7 +43,7 @@ class AuthOtpSubmitted extends AuthEvent {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[verificationId, smsCode, mode];
|
||||
List<Object?> get props => <Object?>[verificationId, smsCode, mode];
|
||||
}
|
||||
|
||||
/// Event for clearing any authentication error in the state.
|
||||
@@ -57,7 +57,7 @@ class AuthResetRequested extends AuthEvent {
|
||||
const AuthResetRequested({required this.mode});
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[mode];
|
||||
List<Object?> get props => <Object?>[mode];
|
||||
}
|
||||
|
||||
/// Event for ticking down the resend cooldown.
|
||||
@@ -67,7 +67,7 @@ class AuthCooldownTicked extends AuthEvent {
|
||||
const AuthCooldownTicked(this.secondsRemaining);
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[secondsRemaining];
|
||||
List<Object?> get props => <Object?>[secondsRemaining];
|
||||
}
|
||||
|
||||
/// Event for updating the current draft OTP in the state.
|
||||
@@ -78,7 +78,7 @@ class AuthOtpUpdated extends AuthEvent {
|
||||
const AuthOtpUpdated(this.otp);
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[otp];
|
||||
List<Object?> get props => <Object?>[otp];
|
||||
}
|
||||
|
||||
/// Event for updating the current draft phone number in the state.
|
||||
@@ -89,5 +89,5 @@ class AuthPhoneUpdated extends AuthEvent {
|
||||
const AuthPhoneUpdated(this.phoneNumber);
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[phoneNumber];
|
||||
List<Object?> get props => <Object?>[phoneNumber];
|
||||
}
|
||||
|
||||
@@ -50,7 +50,13 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
required BuildContext context,
|
||||
required String phoneNumber,
|
||||
}) {
|
||||
final String normalized = phoneNumber.replaceAll(RegExp(r'\\D'), '');
|
||||
String normalized = phoneNumber.replaceAll(RegExp(r'\D'), '');
|
||||
|
||||
// Handle US numbers entered with a leading 1
|
||||
if (normalized.length == 11 && normalized.startsWith('1')) {
|
||||
normalized = normalized.substring(1);
|
||||
}
|
||||
|
||||
if (normalized.length == 10) {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
|
||||
@@ -88,7 +88,7 @@ class _PhoneInputFormFieldState extends State<PhoneInputFormField> {
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
LengthLimitingTextInputFormatter(11),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_authentication.phone_input.hint,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
@@ -58,9 +59,9 @@ class _ShiftCardState extends State<ShiftCard> {
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
final diff = DateTime.now().difference(date);
|
||||
if (diff.inHours < 1) return 'Just now';
|
||||
if (diff.inHours < 24) return 'Pending ${diff.inHours}h ago';
|
||||
return 'Pending ${diff.inDays}d ago';
|
||||
if (diff.inHours < 1) return t.staff_shifts.card.just_now;
|
||||
if (diff.inHours < 24) return t.staff_shifts.details.pending_time(time: '${diff.inHours}h');
|
||||
return t.staff_shifts.details.pending_time(time: '${diff.inDays}d');
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
@@ -220,7 +221,11 @@ class _ShiftCardState extends State<ShiftCard> {
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
),
|
||||
child: Text(
|
||||
'Assigned ${_getTimeAgo(widget.shift.createdDate).replaceAll('Pending ', '')}',
|
||||
t.staff_shifts.card.assigned(
|
||||
time: _getTimeAgo(widget.shift.createdDate)
|
||||
.replaceAll('Pending ', '')
|
||||
.replaceAll('Just now', 'just now'),
|
||||
),
|
||||
style: UiTypography.body3m.white,
|
||||
),
|
||||
),
|
||||
@@ -302,13 +307,13 @@ class _ShiftCardState extends State<ShiftCard> {
|
||||
children: [
|
||||
_buildTag(
|
||||
UiIcons.zap,
|
||||
'Immediate start',
|
||||
t.staff_shifts.tags.immediate_start,
|
||||
UiColors.accent.withValues(alpha: 0.3),
|
||||
UiColors.foreground,
|
||||
),
|
||||
_buildTag(
|
||||
UiIcons.timer,
|
||||
'No experience',
|
||||
t.staff_shifts.tags.no_experience,
|
||||
UiColors.tagError,
|
||||
UiColors.textError,
|
||||
),
|
||||
@@ -342,7 +347,7 @@ class _ShiftCardState extends State<ShiftCard> {
|
||||
BorderRadius.circular(UiConstants.radiusBase),
|
||||
),
|
||||
),
|
||||
child: const Text('Accept shift'),
|
||||
child: Text(t.staff_shifts.card.accept_shift),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
@@ -361,7 +366,7 @@ class _ShiftCardState extends State<ShiftCard> {
|
||||
BorderRadius.circular(UiConstants.radiusBase),
|
||||
),
|
||||
),
|
||||
child: const Text('Decline shift'),
|
||||
child: Text(t.staff_shifts.card.decline_shift),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
@@ -35,14 +35,14 @@ class CertificatesPage extends StatelessWidget {
|
||||
|
||||
if (state.status == CertificatesStatus.failure) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Certificates')),
|
||||
appBar: AppBar(title: Text(t.staff_certificates.title)),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
state.errorMessage != null
|
||||
? translateErrorKey(state.errorMessage!)
|
||||
: 'Error loading certificates',
|
||||
child: Text(
|
||||
state.errorMessage != null
|
||||
? translateErrorKey(state.errorMessage!)
|
||||
: t.staff_certificates.error_loading,
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -18,7 +19,7 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
||||
if (state.status == EmergencyContactStatus.saved) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: 'Emergency contacts saved successfully',
|
||||
message: t.staff.profile.menu_items.emergency_contact_page.save_success,
|
||||
type: UiSnackbarType.success,
|
||||
margin: const EdgeInsets.only(bottom: 150, left: 16, right: 16),
|
||||
);
|
||||
@@ -48,7 +49,7 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text('Save & Continue'),
|
||||
: Text(t.staff.profile.menu_items.emergency_contact_page.save_continue),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -105,12 +105,12 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Accept Shift'),
|
||||
content: const Text('Are you sure you want to accept this shift?'),
|
||||
title: Text(t.staff_shifts.my_shifts_tab.confirm_dialog.title),
|
||||
content: Text(t.staff_shifts.my_shifts_tab.confirm_dialog.message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@@ -118,14 +118,14 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
context.read<ShiftsBloc>().add(AcceptShiftEvent(id));
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: 'Shift confirmed!',
|
||||
message: t.staff_shifts.my_shifts_tab.confirm_dialog.success,
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.success,
|
||||
),
|
||||
child: const Text('Accept'),
|
||||
child: Text(t.staff_shifts.shift_details.accept_shift),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -136,14 +136,14 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Decline Shift'),
|
||||
content: const Text(
|
||||
'Are you sure you want to decline this shift? This action cannot be undone.',
|
||||
title: Text(t.staff_shifts.my_shifts_tab.decline_dialog.title),
|
||||
content: Text(
|
||||
t.staff_shifts.my_shifts_tab.decline_dialog.message,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@@ -151,14 +151,14 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
context.read<ShiftsBloc>().add(DeclineShiftEvent(id));
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: 'Shift declined.',
|
||||
message: t.staff_shifts.my_shifts_tab.decline_dialog.success,
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
),
|
||||
child: const Text('Decline'),
|
||||
child: Text(t.staff_shifts.shift_details.decline),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -169,9 +169,9 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
final now = DateTime.now();
|
||||
if (_isSameDay(date, now)) return "Today";
|
||||
if (_isSameDay(date, now)) return t.staff_shifts.my_shifts_tab.date.today;
|
||||
final tomorrow = now.add(const Duration(days: 1));
|
||||
if (_isSameDay(date, tomorrow)) return "Tomorrow";
|
||||
if (_isSameDay(date, tomorrow)) return t.staff_shifts.my_shifts_tab.date.tomorrow;
|
||||
return DateFormat('EEE, MMM d').format(date);
|
||||
} catch (_) {
|
||||
return dateStr;
|
||||
@@ -338,7 +338,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
if (widget.pendingAssignments.isNotEmpty) ...[
|
||||
_buildSectionHeader(
|
||||
"Awaiting Confirmation",
|
||||
t.staff_shifts.my_shifts_tab.sections.awaiting,
|
||||
UiColors.textWarning,
|
||||
),
|
||||
...widget.pendingAssignments.map(
|
||||
@@ -356,7 +356,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
],
|
||||
|
||||
if (visibleCancelledShifts.isNotEmpty) ...[
|
||||
_buildSectionHeader("Cancelled Shifts", UiColors.textSecondary),
|
||||
_buildSectionHeader(t.staff_shifts.my_shifts_tab.sections.cancelled, UiColors.textSecondary),
|
||||
...visibleCancelledShifts.map(
|
||||
(shift) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space4),
|
||||
@@ -378,7 +378,7 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
|
||||
// Confirmed Shifts
|
||||
if (visibleMyShifts.isNotEmpty) ...[
|
||||
_buildSectionHeader("Confirmed Shifts", UiColors.textSecondary),
|
||||
_buildSectionHeader(t.staff_shifts.my_shifts_tab.sections.confirmed, UiColors.textSecondary),
|
||||
...visibleMyShifts.map(
|
||||
(shift) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
@@ -390,10 +390,10 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
if (visibleMyShifts.isEmpty &&
|
||||
widget.pendingAssignments.isEmpty &&
|
||||
widget.cancelledShifts.isEmpty)
|
||||
const EmptyStateView(
|
||||
EmptyStateView(
|
||||
icon: UiIcons.calendar,
|
||||
title: "No shifts this week",
|
||||
subtitle: "Try finding new jobs in the Find tab",
|
||||
title: t.staff_shifts.my_shifts_tab.empty.title,
|
||||
subtitle: t.staff_shifts.my_shifts_tab.empty.subtitle,
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
@@ -462,13 +462,13 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"CANCELLED",
|
||||
t.staff_shifts.my_shifts_tab.card.cancelled,
|
||||
style: UiTypography.footnote2b.textError,
|
||||
),
|
||||
if (isLastMinute) ...[
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"• 4hr compensation",
|
||||
t.staff_shifts.my_shifts_tab.card.compensation,
|
||||
style: UiTypography.footnote2m.textSuccess,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -598,16 +598,19 @@ query listStaffsApplicationsByBusinessForDay(
|
||||
appliedAt
|
||||
status
|
||||
|
||||
shiftRole{
|
||||
shift{
|
||||
shiftRole {
|
||||
startTime
|
||||
shift {
|
||||
date
|
||||
location
|
||||
locationAddress
|
||||
cost
|
||||
}
|
||||
count
|
||||
assigned
|
||||
hours
|
||||
|
||||
role{
|
||||
role {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ mutation createOrder(
|
||||
detectedConflicts: $detectedConflicts
|
||||
poReference: $poReference
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
mutation updateOrder(
|
||||
@@ -92,7 +92,7 @@ mutation updateOrder(
|
||||
detectedConflicts: $detectedConflicts
|
||||
poReference: $poReference
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
mutation deleteOrder($id: UUID!) @auth(level: USER) {
|
||||
|
||||
@@ -321,7 +321,6 @@ query listShiftRolesByBusinessAndDateRange(
|
||||
shift: {
|
||||
date: { ge: $start, le: $end }
|
||||
order: { businessId: { eq: $businessId } }
|
||||
status: { eq: $status }
|
||||
}
|
||||
}
|
||||
offset: $offset
|
||||
|
||||
@@ -22,7 +22,7 @@ enum OrderDuration {
|
||||
}
|
||||
|
||||
#events
|
||||
type Order @table(name: "orders") {
|
||||
type Order @table(name: "orders", key: ["id"]) {
|
||||
id: UUID! @default(expr: "uuidV4()")
|
||||
eventName: String
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ enum ShiftStatus {
|
||||
CANCELED
|
||||
}
|
||||
|
||||
type Shift @table(name: "shifts") {
|
||||
type Shift @table(name: "shifts", key: ["id"]) {
|
||||
id: UUID! @default(expr: "uuidV4()")
|
||||
|
||||
title: String!
|
||||
|
||||
Reference in New Issue
Block a user