Merge pull request #579 from Oloodi/493-implement-rapid-order-creation-voice-text-in-client-mobile-app
Prepare & Handover M4 Mobile Applications for Client Testing
This commit is contained in:
@@ -43,7 +43,6 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.krowwithus.client"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
@@ -51,18 +50,17 @@ android {
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
||||
manifestPlaceholders["GOOGLE_MAPS_API_KEY"] = dartEnvironmentVariables["GOOGLE_MAPS_API_KEY"] ?: ""
|
||||
manifestPlaceholders["GOOGLE_MAPS_API_KEY"] = dartEnvironmentVariables["GOOGLE_MAPS_API_KEY"] ?: ""
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
if (System.getenv()["CI"] == "true") {
|
||||
// CodeMagic CI environment
|
||||
storeFile = file(System.getenv()["CM_KEYSTORE_PATH_CLIENT"] ?: "")
|
||||
storePassword = System.getenv()["CM_KEYSTORE_PASSWORD_CLIENT"]
|
||||
keyAlias = System.getenv()["CM_KEY_ALIAS_CLIENT"]
|
||||
keyPassword = System.getenv()["CM_KEY_PASSWORD_CLIENT"]
|
||||
storeFile = file(System.getenv()["CM_KEYSTORE_PATH"] ?: "")
|
||||
storePassword = System.getenv()["CM_KEYSTORE_PASSWORD"]
|
||||
keyAlias = System.getenv()["CM_KEY_ALIAS"]
|
||||
keyPassword = System.getenv()["CM_KEY_PASSWORD"]
|
||||
} else {
|
||||
// Local development environment
|
||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
||||
|
||||
@@ -104,7 +104,7 @@ class _SessionListenerState extends State<SessionListener> {
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Modular.to.popSafe();
|
||||
_proceedToLogin();
|
||||
},
|
||||
child: const Text('Log In'),
|
||||
@@ -134,7 +134,7 @@ class _SessionListenerState extends State<SessionListener> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Modular.to.popSafe();;
|
||||
_proceedToLogin();
|
||||
},
|
||||
child: const Text('Log Out'),
|
||||
|
||||
@@ -43,7 +43,6 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.krowwithus.staff"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
@@ -59,10 +58,10 @@ android {
|
||||
create("release") {
|
||||
if (System.getenv()["CI"] == "true") {
|
||||
// CodeMagic CI environment
|
||||
storeFile = file(System.getenv()["CM_KEYSTORE_PATH_STAFF"] ?: "")
|
||||
storePassword = System.getenv()["CM_KEYSTORE_PASSWORD_STAFF"]
|
||||
keyAlias = System.getenv()["CM_KEY_ALIAS_STAFF"]
|
||||
keyPassword = System.getenv()["CM_KEY_PASSWORD_STAFF"]
|
||||
storeFile = file(System.getenv()["CM_KEYSTORE_PATH"] ?: "")
|
||||
storePassword = System.getenv()["CM_KEYSTORE_PASSWORD"]
|
||||
keyAlias = System.getenv()["CM_KEY_ALIAS"]
|
||||
keyPassword = System.getenv()["CM_KEY_PASSWORD"]
|
||||
} else {
|
||||
// Local development environment
|
||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "933560802882",
|
||||
"project_id": "krow-workforce-dev",
|
||||
"storage_bucket": "krow-workforce-dev.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.krow.app.business.dev"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:d49b8c0f4d19e95e7757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.krow.app.staff.dev"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:da13569105659ead7757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.krowwithus.client"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.krowwithus.staff"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-ikdfv3o5f47g36qqgvfq55o4m19n7gk4.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.krowwithus.staff",
|
||||
"certificate_hash": "ac917ae8470ab29f1107c773c6017ff5ea5d102d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -104,7 +104,7 @@ class _SessionListenerState extends State<SessionListener> {
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Modular.to.popSafe();;
|
||||
_proceedToLogin();
|
||||
},
|
||||
child: const Text('Log In'),
|
||||
@@ -134,7 +134,7 @@ class _SessionListenerState extends State<SessionListener> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Modular.to.popSafe();;
|
||||
_proceedToLogin();
|
||||
},
|
||||
child: const Text('Log Out'),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:core_localization/src/l10n/strings.g.dart';
|
||||
|
||||
import '../../domain/repositories/locale_repository_interface.dart';
|
||||
@@ -31,12 +32,10 @@ class LocaleRepositoryImpl implements LocaleRepositoryInterface {
|
||||
return getDefaultLocale();
|
||||
}
|
||||
|
||||
/// We can hardcode this to english based on customer requirements,
|
||||
/// but in a more dynamic app this should be the device locale or a fallback to english.
|
||||
@override
|
||||
Locale getDefaultLocale() {
|
||||
final Locale deviceLocale = AppLocaleUtils.findDeviceLocale().flutterLocale;
|
||||
if (getSupportedLocales().contains(deviceLocale)) {
|
||||
return deviceLocale;
|
||||
}
|
||||
return const Locale('en');
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
|
||||
@override
|
||||
Future<bool> getProfileCompletion() async {
|
||||
return true;
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@ class StaffPayment extends Equatable {
|
||||
required this.amount,
|
||||
required this.status,
|
||||
this.paidAt,
|
||||
this.shiftTitle,
|
||||
this.shiftLocation,
|
||||
this.locationAddress,
|
||||
this.hoursWorked,
|
||||
this.hourlyRate,
|
||||
this.workedTime,
|
||||
});
|
||||
/// Unique identifier.
|
||||
final String id;
|
||||
@@ -47,6 +53,24 @@ class StaffPayment extends Equatable {
|
||||
/// When the payment was successfully processed.
|
||||
final DateTime? paidAt;
|
||||
|
||||
/// Title of the shift worked.
|
||||
final String? shiftTitle;
|
||||
|
||||
/// Location/hub name of the shift.
|
||||
final String? shiftLocation;
|
||||
|
||||
/// Address of the shift location.
|
||||
final String? locationAddress;
|
||||
|
||||
/// Number of hours worked.
|
||||
final double? hoursWorked;
|
||||
|
||||
/// Hourly rate for the shift.
|
||||
final double? hourlyRate;
|
||||
|
||||
/// Work session duration or status.
|
||||
final String? workedTime;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[id, staffId, assignmentId, amount, status, paidAt];
|
||||
List<Object?> get props => <Object?>[id, staffId, assignmentId, amount, status, paidAt, shiftTitle, shiftLocation, locationAddress, hoursWorked, hourlyRate, workedTime];
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
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';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
import '../blocs/coverage_bloc.dart';
|
||||
import '../blocs/coverage_event.dart';
|
||||
import '../blocs/coverage_state.dart';
|
||||
|
||||
import '../widgets/coverage_calendar_selector.dart';
|
||||
import '../widgets/coverage_quick_stats.dart';
|
||||
import '../widgets/coverage_shift_list.dart';
|
||||
@@ -78,21 +77,6 @@ class _CoveragePageState extends State<CoveragePage> {
|
||||
pinned: true,
|
||||
expandedHeight: 300.0,
|
||||
backgroundColor: UiColors.primary,
|
||||
leading: IconButton(
|
||||
onPressed: () => Modular.to.toClientHome(),
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space2),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primaryForeground.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
UiIcons.arrowLeft,
|
||||
color: UiColors.primaryForeground,
|
||||
size: UiConstants.space4,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Text(
|
||||
|
||||
@@ -506,21 +506,21 @@ class _WorkerRow extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (worker.status == CoverageWorkerStatus.checkedIn)
|
||||
UiButton.primary(
|
||||
text: context.t.client_coverage.worker_row.verify,
|
||||
size: UiButtonSize.small,
|
||||
onPressed: () {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message:
|
||||
context.t.client_coverage.worker_row.verified_message(
|
||||
name: worker.name,
|
||||
),
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
},
|
||||
),
|
||||
// if (worker.status == CoverageWorkerStatus.checkedIn)
|
||||
// UiButton.primary(
|
||||
// text: context.t.client_coverage.worker_row.verify,
|
||||
// size: UiButtonSize.small,
|
||||
// onPressed: () {
|
||||
// UiSnackbar.show(
|
||||
// context,
|
||||
// message:
|
||||
// context.t.client_coverage.worker_row.verified_message(
|
||||
// name: worker.name,
|
||||
// ),
|
||||
// type: UiSnackbarType.success,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -161,14 +161,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
@@ -249,9 +242,12 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
size: 14,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
Text(
|
||||
order.eventName,
|
||||
style: UiTypography.headline5m.textSecondary,
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.eventName,
|
||||
style: UiTypography.headline5m.textSecondary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -313,7 +309,8 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.hubManagerName!,
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
style:
|
||||
UiTypography.footnote2r.textSecondary,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -335,7 +332,8 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
bgColor: UiColors.primary.withValues(alpha: 0.08),
|
||||
onTap: () => _openEditSheet(order: order),
|
||||
),
|
||||
if (_canEditOrder(order)) const SizedBox(width: UiConstants.space2),
|
||||
if (_canEditOrder(order))
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
if (order.confirmedApps.isNotEmpty)
|
||||
_buildHeaderIconButton(
|
||||
icon: _expanded
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/daily_ops_report.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'daily_ops_event.dart';
|
||||
import 'daily_ops_state.dart';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_state.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
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';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
class CoverageReportPage extends StatefulWidget {
|
||||
const CoverageReportPage({super.key});
|
||||
@@ -23,11 +23,10 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
return BlocProvider<CoverageBloc>(
|
||||
create: (BuildContext context) => Modular.get<CoverageBloc>()
|
||||
..add(LoadCoverageReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<CoverageBloc, CoverageState>(
|
||||
builder: (BuildContext context, CoverageState state) {
|
||||
if (state is CoverageLoading) {
|
||||
@@ -64,7 +63,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onTap: () => Modular.to.popSafe(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_state.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
@@ -8,7 +7,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/src/entities/reports/daily_ops_report.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
class DailyOpsReportPage extends StatefulWidget {
|
||||
const DailyOpsReportPage({super.key});
|
||||
@@ -50,11 +50,10 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
return BlocProvider<DailyOpsBloc>(
|
||||
create: (BuildContext context) => Modular.get<DailyOpsBloc>()
|
||||
..add(LoadDailyOpsReport(date: _selectedDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<DailyOpsBloc, DailyOpsState>(
|
||||
builder: (BuildContext context, DailyOpsState state) {
|
||||
if (state is DailyOpsLoading) {
|
||||
@@ -94,7 +93,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onTap: () => Modular.to.popSafe(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
@@ -245,6 +244,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 1.2,
|
||||
@@ -318,7 +318,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Text(
|
||||
context.t.client_reports.daily_ops_report
|
||||
.all_shifts_title
|
||||
@@ -398,14 +398,8 @@ class _OpsStatCard extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -440,7 +434,8 @@ class _OpsStatCard extends StatelessWidget {
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
//UiChip(label: subValue),
|
||||
// Colored pill badge (matches prototype)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -449,12 +444,12 @@ class _OpsStatCard extends StatelessWidget {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
subValue,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import 'package:client_reports/src/presentation/blocs/forecast/forecast_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/forecast/forecast_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/forecast/forecast_state.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
@@ -28,7 +29,6 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
create: (BuildContext context) => Modular.get<ForecastBloc>()
|
||||
..add(LoadForecastReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<ForecastBloc, ForecastState>(
|
||||
builder: (BuildContext context, ForecastState state) {
|
||||
if (state is ForecastLoading) {
|
||||
@@ -86,7 +86,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
(ForecastWeek week) => _WeeklyBreakdownItem(week: week),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: UiConstants.space24),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -124,7 +124,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onTap: () => Modular.to.popSafe(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
@@ -184,6 +184,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
final TranslationsClientReportsForecastReportEn t = context.t.client_reports.forecast_report;
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: 12,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/no_show/no_show_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/no_show/no_show_event.dart';
|
||||
@@ -27,7 +28,6 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
create: (BuildContext context) => Modular.get<NoShowBloc>()
|
||||
..add(LoadNoShowReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<NoShowBloc, NoShowState>(
|
||||
builder: (BuildContext context, NoShowState state) {
|
||||
if (state is NoShowLoading) {
|
||||
@@ -67,7 +67,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onTap: () => Modular.to.popSafe(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
import 'package:client_reports/src/presentation/blocs/performance/performance_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/performance/performance_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/performance/performance_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/performance/performance_state.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
@@ -7,7 +6,8 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/src/entities/reports/performance_report.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
class PerformanceReportPage extends StatefulWidget {
|
||||
const PerformanceReportPage({super.key});
|
||||
@@ -26,7 +26,6 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
create: (BuildContext context) => Modular.get<PerformanceBloc>()
|
||||
..add(LoadPerformanceReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<PerformanceBloc, PerformanceState>(
|
||||
builder: (BuildContext context, PerformanceState state) {
|
||||
if (state is PerformanceLoading) {
|
||||
@@ -143,7 +142,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onTap: () => Modular.to.popSafe(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_event.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
@@ -80,10 +78,9 @@ class _ReportsPageState extends State<ReportsPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
return BlocProvider<ReportsSummaryBloc>.value(
|
||||
value: _summaryBloc,
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_state.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
@@ -9,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
class SpendReportPage extends StatefulWidget {
|
||||
@@ -35,11 +35,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
return BlocProvider<SpendBloc>(
|
||||
create: (BuildContext context) => Modular.get<SpendBloc>()
|
||||
..add(LoadSpendReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<SpendBloc, SpendState>(
|
||||
builder: (BuildContext context, SpendState state) {
|
||||
if (state is SpendLoading) {
|
||||
@@ -72,7 +71,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
onTap: () => Modular.to.popSafe(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
|
||||
@@ -155,7 +155,7 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthResetRequested(mode: widget.mode));
|
||||
Navigator.of(context).pop();
|
||||
Modular.to.popSafe();;
|
||||
},
|
||||
),
|
||||
body: SafeArea(
|
||||
|
||||
@@ -11,16 +11,16 @@ class FullWidthDivider extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
//final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
Transform.translate(
|
||||
offset: const Offset(-UiConstants.space4, 0),
|
||||
child: SizedBox(width: screenWidth, child: const Divider()),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
// Transform.translate(
|
||||
// offset: const Offset(-UiConstants.space4, 0),
|
||||
// child: SizedBox(width: screenWidth, child: const Divider()),
|
||||
// ),
|
||||
// const SizedBox(height: UiConstants.space10),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,16 @@ class PaymentsRepositoryImpl
|
||||
.execute();
|
||||
|
||||
return response.data.recentPayments.map((dc.ListRecentPaymentsByStaffIdRecentPayments payment) {
|
||||
// Extract shift details from nested application structure
|
||||
final String? shiftTitle = payment.application.shiftRole.shift.title;
|
||||
final String? locationAddress = payment.application.shiftRole.shift.locationAddress;
|
||||
final double? hoursWorked = payment.application.shiftRole.hours;
|
||||
final double? hourlyRate = payment.application.shiftRole.role.costPerHour;
|
||||
// Extract hub details from order
|
||||
final String? locationHub = payment.invoice.order.teamHub.hubName;
|
||||
final String? hubAddress = payment.invoice.order.teamHub.address;
|
||||
final String? shiftLocation = locationAddress ?? hubAddress;
|
||||
|
||||
return StaffPayment(
|
||||
id: payment.id,
|
||||
staffId: payment.staffId,
|
||||
@@ -74,6 +84,12 @@ class PaymentsRepositoryImpl
|
||||
amount: payment.invoice.amount,
|
||||
status: PaymentAdapter.toPaymentStatus(payment.status?.stringValue ?? 'UNKNOWN'),
|
||||
paidAt: _service.toDateTime(payment.invoice.issueDate),
|
||||
shiftTitle: shiftTitle,
|
||||
shiftLocation: locationHub,
|
||||
locationAddress: shiftLocation,
|
||||
hoursWorked: hoursWorked,
|
||||
hourlyRate: hourlyRate,
|
||||
workedTime: payment.workedTime,
|
||||
);
|
||||
}).toList();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
@@ -10,7 +9,6 @@ import '../blocs/payments/payments_bloc.dart';
|
||||
import '../blocs/payments/payments_event.dart';
|
||||
import '../blocs/payments/payments_state.dart';
|
||||
import '../widgets/payment_stats_card.dart';
|
||||
import '../widgets/pending_pay_card.dart';
|
||||
import '../widgets/payment_history_item.dart';
|
||||
import '../widgets/earnings_graph.dart';
|
||||
|
||||
@@ -172,17 +170,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Pending Pay
|
||||
if (state.summary.pendingEarnings > 0)
|
||||
PendingPayCard(
|
||||
amount: state.summary.pendingEarnings,
|
||||
onCashOut: () {
|
||||
Modular.to.pushNamed('${StaffPaths.payments}early-pay');
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Recent Payments
|
||||
if (state.history.isNotEmpty)
|
||||
@@ -191,7 +179,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"Recent Payments",
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
style: UiTypography.body1b,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Column(
|
||||
@@ -201,16 +189,16 @@ class _PaymentsPageState extends State<PaymentsPage> {
|
||||
bottom: UiConstants.space2),
|
||||
child: PaymentHistoryItem(
|
||||
amount: payment.amount,
|
||||
title: "Shift Payment",
|
||||
location: "Varies",
|
||||
address: "Payment ID: ${payment.id}",
|
||||
title: payment.shiftTitle ?? "Shift Payment",
|
||||
location: payment.shiftLocation ?? "Varies",
|
||||
address: payment.locationAddress ?? payment.id,
|
||||
date: payment.paidAt != null
|
||||
? DateFormat('E, MMM d')
|
||||
.format(payment.paidAt!)
|
||||
: 'Pending',
|
||||
workedTime: "Completed",
|
||||
hours: 0,
|
||||
rate: 0.0,
|
||||
workedTime: payment.workedTime ?? "Completed",
|
||||
hours: (payment.hoursWorked ?? 0).toInt(),
|
||||
rate: payment.hourlyRate ?? 0.0,
|
||||
status: payment.status.name.toUpperCase(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -32,13 +32,7 @@ class PaymentHistoryItem extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -77,7 +71,7 @@ class PaymentHistoryItem extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
),
|
||||
child: const Icon(
|
||||
UiIcons.chart,
|
||||
UiIcons.dollar,
|
||||
color: UiColors.mutedForeground,
|
||||
size: 24,
|
||||
),
|
||||
@@ -98,7 +92,7 @@ class PaymentHistoryItem extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
Text(
|
||||
location,
|
||||
@@ -112,7 +106,7 @@ class PaymentHistoryItem extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"\$${amount.toStringAsFixed(0)}",
|
||||
style: UiTypography.headline4m.textPrimary,
|
||||
style: UiTypography.headline4b,
|
||||
),
|
||||
Text(
|
||||
"\$${rate.toStringAsFixed(0)}/hr · ${hours}h",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import '../../../domain/usecases/apply_for_shift_usecase.dart';
|
||||
import '../../../domain/usecases/decline_shift_usecase.dart';
|
||||
import '../../../domain/usecases/get_shift_details_usecase.dart';
|
||||
@@ -12,11 +13,13 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState>
|
||||
final GetShiftDetailsUseCase getShiftDetails;
|
||||
final ApplyForShiftUseCase applyForShift;
|
||||
final DeclineShiftUseCase declineShift;
|
||||
final GetProfileCompletionUseCase getProfileCompletion;
|
||||
|
||||
ShiftDetailsBloc({
|
||||
required this.getShiftDetails,
|
||||
required this.applyForShift,
|
||||
required this.declineShift,
|
||||
required this.getProfileCompletion,
|
||||
}) : super(ShiftDetailsInitial()) {
|
||||
on<LoadShiftDetailsEvent>(_onLoadDetails);
|
||||
on<BookShiftDetailsEvent>(_onBookShift);
|
||||
@@ -34,8 +37,9 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState>
|
||||
final shift = await getShiftDetails(
|
||||
GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
|
||||
);
|
||||
final isProfileComplete = await getProfileCompletion();
|
||||
if (shift != null) {
|
||||
emit(ShiftDetailsLoaded(shift));
|
||||
emit(ShiftDetailsLoaded(shift, isProfileComplete: isProfileComplete));
|
||||
} else {
|
||||
emit(const ShiftDetailsError("Shift not found"));
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ class ShiftDetailsLoading extends ShiftDetailsState {}
|
||||
|
||||
class ShiftDetailsLoaded extends ShiftDetailsState {
|
||||
final Shift shift;
|
||||
const ShiftDetailsLoaded(this.shift);
|
||||
final bool isProfileComplete;
|
||||
const ShiftDetailsLoaded(this.shift, {this.isProfileComplete = false});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [shift];
|
||||
List<Object?> get props => [shift, isProfileComplete];
|
||||
}
|
||||
|
||||
class ShiftDetailsError extends ShiftDetailsState {
|
||||
|
||||
@@ -125,6 +125,9 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
|
||||
final Shift displayShift = widget.shift;
|
||||
final i18n = Translations.of(context).staff_shifts.shift_details;
|
||||
final isProfileComplete = state is ShiftDetailsLoaded
|
||||
? state.isProfileComplete
|
||||
: false;
|
||||
|
||||
final duration = _calculateDuration(displayShift);
|
||||
final estimatedTotal =
|
||||
@@ -142,6 +145,16 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isProfileComplete)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space6),
|
||||
child: UiNoticeBanner(
|
||||
title: 'Complete Your Account',
|
||||
description:
|
||||
'Complete your account to book this shift and start earning',
|
||||
icon: UiIcons.sparkles,
|
||||
),
|
||||
),
|
||||
ShiftDetailsHeader(shift: displayShift),
|
||||
const Divider(height: 1, thickness: 0.5),
|
||||
ShiftStatsRow(
|
||||
@@ -194,20 +207,21 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
ShiftDetailsBottomBar(
|
||||
shift: displayShift,
|
||||
onApply: () => _bookShift(context, displayShift),
|
||||
onDecline: () => BlocProvider.of<ShiftDetailsBloc>(
|
||||
context,
|
||||
).add(DeclineShiftDetailsEvent(displayShift.id)),
|
||||
onAccept: () =>
|
||||
BlocProvider.of<ShiftDetailsBloc>(context).add(
|
||||
BookShiftDetailsEvent(
|
||||
displayShift.id,
|
||||
roleId: displayShift.roleId,
|
||||
if (isProfileComplete)
|
||||
ShiftDetailsBottomBar(
|
||||
shift: displayShift,
|
||||
onApply: () => _bookShift(context, displayShift),
|
||||
onDecline: () => BlocProvider.of<ShiftDetailsBloc>(
|
||||
context,
|
||||
).add(DeclineShiftDetailsEvent(displayShift.id)),
|
||||
onAccept: () =>
|
||||
BlocProvider.of<ShiftDetailsBloc>(context).add(
|
||||
BookShiftDetailsEvent(
|
||||
displayShift.id,
|
||||
roleId: displayShift.roleId,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'domain/repositories/shifts_repository_interface.dart';
|
||||
import 'data/repositories_impl/shifts_repository_impl.dart';
|
||||
import 'domain/usecases/get_shift_details_usecase.dart';
|
||||
@@ -14,11 +15,21 @@ class ShiftDetailsModule extends Module {
|
||||
// Repository
|
||||
i.add<ShiftsRepositoryInterface>(ShiftsRepositoryImpl.new);
|
||||
|
||||
// StaffConnectorRepository for profile completion
|
||||
i.addLazySingleton<StaffConnectorRepository>(
|
||||
() => StaffConnectorRepositoryImpl(),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
i.add(GetShiftDetailsUseCase.new);
|
||||
i.add(AcceptShiftUseCase.new);
|
||||
i.add(DeclineShiftUseCase.new);
|
||||
i.add(ApplyForShiftUseCase.new);
|
||||
i.addLazySingleton<GetProfileCompletionUseCase>(
|
||||
() => GetProfileCompletionUseCase(
|
||||
repository: i.get<StaffConnectorRepository>(),
|
||||
),
|
||||
);
|
||||
|
||||
// Bloc
|
||||
i.add(ShiftDetailsBloc.new);
|
||||
|
||||
@@ -32,18 +32,18 @@ class StaffShiftsModule extends Module {
|
||||
);
|
||||
|
||||
// Repository
|
||||
i.add<ShiftsRepositoryInterface>(ShiftsRepositoryImpl.new);
|
||||
i.addLazySingleton<ShiftsRepositoryInterface>(ShiftsRepositoryImpl.new);
|
||||
|
||||
// UseCases
|
||||
i.add(GetMyShiftsUseCase.new);
|
||||
i.add(GetAvailableShiftsUseCase.new);
|
||||
i.add(GetPendingAssignmentsUseCase.new);
|
||||
i.add(GetCancelledShiftsUseCase.new);
|
||||
i.add(GetHistoryShiftsUseCase.new);
|
||||
i.add(AcceptShiftUseCase.new);
|
||||
i.add(DeclineShiftUseCase.new);
|
||||
i.add(ApplyForShiftUseCase.new);
|
||||
i.add(GetShiftDetailsUseCase.new);
|
||||
i.addLazySingleton(GetMyShiftsUseCase.new);
|
||||
i.addLazySingleton(GetAvailableShiftsUseCase.new);
|
||||
i.addLazySingleton(GetPendingAssignmentsUseCase.new);
|
||||
i.addLazySingleton(GetCancelledShiftsUseCase.new);
|
||||
i.addLazySingleton(GetHistoryShiftsUseCase.new);
|
||||
i.addLazySingleton(AcceptShiftUseCase.new);
|
||||
i.addLazySingleton(DeclineShiftUseCase.new);
|
||||
i.addLazySingleton(ApplyForShiftUseCase.new);
|
||||
i.addLazySingleton(GetShiftDetailsUseCase.new);
|
||||
|
||||
// Bloc
|
||||
i.add(
|
||||
|
||||
3
apps/web/pnpm-lock.yaml
generated
3
apps/web/pnpm-lock.yaml
generated
@@ -4,9 +4,6 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
'@dataconnect/generated': link:src/dataconnect-generated
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
overrides:
|
||||
'@dataconnect/generated': link:src/dataconnect-generated
|
||||
|
||||
@@ -62,12 +62,6 @@ export const NAV_CONFIG: NavGroup[] = [
|
||||
icon: LayoutDashboard,
|
||||
allowedRoles: ['Vendor'],
|
||||
},
|
||||
{
|
||||
label: 'Savings Engine',
|
||||
path: '/savings',
|
||||
icon: PiggyBank,
|
||||
allowedRoles: ALL_ROLES,
|
||||
},
|
||||
{
|
||||
label: 'Vendor Performance',
|
||||
path: '/performance',
|
||||
@@ -117,23 +111,6 @@ export const NAV_CONFIG: NavGroup[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Marketplace',
|
||||
items: [
|
||||
{
|
||||
label: 'Discovery',
|
||||
path: '/marketplace',
|
||||
icon: ShoppingBag,
|
||||
allowedRoles: ['Client', 'Admin'],
|
||||
},
|
||||
{
|
||||
label: 'Compare Rates',
|
||||
path: '/marketplace/compare',
|
||||
icon: Scale,
|
||||
allowedRoles: ['Client', 'Admin'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Workforce',
|
||||
items: [
|
||||
@@ -143,18 +120,6 @@ export const NAV_CONFIG: NavGroup[] = [
|
||||
icon: Users,
|
||||
allowedRoles: ALL_ROLES,
|
||||
},
|
||||
{
|
||||
label: 'Onboarding',
|
||||
path: '/onboarding',
|
||||
icon: UserPlus,
|
||||
allowedRoles: ALL_ROLES,
|
||||
},
|
||||
{
|
||||
label: 'Teams',
|
||||
path: '/teams',
|
||||
icon: Users2,
|
||||
allowedRoles: ALL_ROLES,
|
||||
},
|
||||
{
|
||||
label: 'Compliance',
|
||||
path: '/compliance',
|
||||
@@ -197,44 +162,4 @@ export const NAV_CONFIG: NavGroup[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Analytics & Comm',
|
||||
items: [
|
||||
{
|
||||
label: 'Reports',
|
||||
path: '/reports',
|
||||
icon: PieChart,
|
||||
allowedRoles: ALL_ROLES,
|
||||
},
|
||||
{
|
||||
label: 'Activity Log',
|
||||
path: '/activity',
|
||||
icon: History,
|
||||
allowedRoles: ['Vendor', 'Admin'],
|
||||
},
|
||||
{
|
||||
label: 'Messages',
|
||||
path: '/messages',
|
||||
icon: MessageSquare,
|
||||
allowedRoles: ALL_ROLES,
|
||||
},
|
||||
{
|
||||
label: 'Tutorials',
|
||||
path: '/tutorials',
|
||||
icon: BookOpen,
|
||||
allowedRoles: ['Client', 'Admin'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
items: [
|
||||
{
|
||||
label: 'Help Center',
|
||||
path: '/support',
|
||||
icon: HelpCircle,
|
||||
allowedRoles: ['Client', 'Admin'],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/common/components/ui/dialog";
|
||||
import EventFormWizard from "./EventFormWizard";
|
||||
import { useCreateOrder, useListBusinesses, useListHubs } from "@/dataconnect-generated/react";
|
||||
import { useCreateOrder, useListBusinesses, useListTeamHubs } from "@/dataconnect-generated/react";
|
||||
import { OrderType, OrderStatus } from "@/dataconnect-generated";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import { useToast } from "@/common/components/ui/use-toast";
|
||||
@@ -26,7 +26,7 @@ export default function CreateOrderDialog({ open, onOpenChange }: CreateOrderDia
|
||||
const [selectedHubId, setSelectedHubId] = React.useState<string>("");
|
||||
|
||||
const { data: businessesData } = useListBusinesses(dataConnect);
|
||||
const { data: hubsData } = useListHubs(dataConnect);
|
||||
const { data: hubsData } = useListTeamHubs(dataConnect);
|
||||
|
||||
const createOrderMutation = useCreateOrder(dataConnect, {
|
||||
onSuccess: () => {
|
||||
@@ -109,9 +109,9 @@ export default function CreateOrderDialog({ open, onOpenChange }: CreateOrderDia
|
||||
<SelectValue placeholder="Select Hub" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{hubsData?.hubs.map((h) => (
|
||||
{hubsData?.teamHubs.map((h) => (
|
||||
<SelectItem key={h.id} value={h.id}>
|
||||
{h.name}
|
||||
{h.hubName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
# Reusable script for building the Flutter app
|
||||
client-app-android-apk-build-script: &client-app-android-apk-build-script
|
||||
name: 👷🤖 Build Client App APK (Android)
|
||||
name: 👷 🤖 Build Client App APK (Android)
|
||||
script: |
|
||||
dart pub global activate melos
|
||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
||||
@@ -12,7 +12,7 @@ client-app-android-apk-build-script: &client-app-android-apk-build-script
|
||||
make mobile-client-build PLATFORM=apk MODE=release
|
||||
|
||||
client-app-ios-build-script: &client-app-ios-build-script
|
||||
name: 👷🍎 Build Client App (iOS)
|
||||
name: 👷 🍎 Build Client App (iOS)
|
||||
script: |
|
||||
dart pub global activate melos
|
||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
||||
@@ -20,7 +20,7 @@ client-app-ios-build-script: &client-app-ios-build-script
|
||||
make mobile-client-build PLATFORM=ios MODE=release
|
||||
|
||||
staff-app-android-apk-build-script: &staff-app-android-apk-build-script
|
||||
name: 👷🤖 Build Staff App APK (Android)
|
||||
name: 👷 🤖 Build Staff App APK (Android)
|
||||
script: |
|
||||
dart pub global activate melos
|
||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
||||
@@ -28,7 +28,7 @@ staff-app-android-apk-build-script: &staff-app-android-apk-build-script
|
||||
make mobile-staff-build PLATFORM=apk MODE=release
|
||||
|
||||
staff-app-ios-build-script: &staff-app-ios-build-script
|
||||
name: 👷🍎 Build Staff App (iOS)
|
||||
name: 👷 🍎 Build Staff App (iOS)
|
||||
script: |
|
||||
dart pub global activate melos
|
||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
||||
@@ -37,7 +37,7 @@ staff-app-ios-build-script: &staff-app-ios-build-script
|
||||
|
||||
# Reusable script for distributing Android to Firebase
|
||||
distribute-android-script: &distribute-android-script
|
||||
name: 🚛🤖 Distribute Android to Firebase App Distribution
|
||||
name: 🚛 🤖 Distribute Android to Firebase App Distribution
|
||||
script: |
|
||||
# Distribute Android APK
|
||||
# Note: Using wildcards to catch app-release.apk
|
||||
@@ -167,7 +167,7 @@ workflows:
|
||||
# =================================================================================
|
||||
client-app-dev-android:
|
||||
<<: *client-app-base
|
||||
name: 🚛🤖 Client App Dev (Android App Distribution)
|
||||
name: 🚛 🤖 Client App Dev (Android App Distribution)
|
||||
environment:
|
||||
flutter: stable
|
||||
xcode: latest
|
||||
@@ -175,11 +175,7 @@ workflows:
|
||||
groups:
|
||||
- client_app_dev_credentials
|
||||
android_signing:
|
||||
- keystore: krow_client_dev
|
||||
keystore_environment_variable: CM_KEYSTORE_PATH_CLIENT
|
||||
keystore_password_environment_variable: CM_KEYSTORE_PASSWORD_CLIENT
|
||||
key_alias_environment_variable: CM_KEY_ALIAS_CLIENT
|
||||
key_password_environment_variable: CM_KEY_PASSWORD_CLIENT
|
||||
- keystore: KROW_CLIENT_DEV
|
||||
vars:
|
||||
ENV: dev
|
||||
scripts:
|
||||
@@ -196,11 +192,7 @@ workflows:
|
||||
groups:
|
||||
- client_app_staging_credentials
|
||||
android_signing:
|
||||
- keystore: krow_client_staging
|
||||
keystore_environment_variable: CM_KEYSTORE_PATH_CLIENT
|
||||
keystore_password_environment_variable: CM_KEYSTORE_PASSWORD_CLIENT
|
||||
key_alias_environment_variable: CM_KEY_ALIAS_CLIENT
|
||||
key_password_environment_variable: CM_KEY_PASSWORD_CLIENT
|
||||
- keystore: KROW_CLIENT_STAGING
|
||||
vars:
|
||||
ENV: staging
|
||||
scripts:
|
||||
@@ -209,12 +201,12 @@ workflows:
|
||||
|
||||
client-app-prod-android:
|
||||
<<: *client-app-base
|
||||
name: 🚛🤖 Client App Prod (Android App Distribution)
|
||||
name: 🚛 🤖 Client App Prod (Android App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- client_app_prod_credentials
|
||||
android_signing:
|
||||
- keystore: krow_client_prod
|
||||
- keystore: KROW_CLIENT_PROD
|
||||
keystore_environment_variable: CM_KEYSTORE_PATH_CLIENT
|
||||
keystore_password_environment_variable: CM_KEYSTORE_PASSWORD_CLIENT
|
||||
key_alias_environment_variable: CM_KEY_ALIAS_CLIENT
|
||||
@@ -230,7 +222,7 @@ workflows:
|
||||
# =================================================================================
|
||||
client-app-dev-ios:
|
||||
<<: *client-app-base
|
||||
name: 🚛🍎 Client App Dev (iOS App Distribution)
|
||||
name: 🚛 🍎 Client App Dev (iOS App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- client_app_dev_credentials
|
||||
@@ -242,7 +234,7 @@ workflows:
|
||||
|
||||
client-app-staging-ios:
|
||||
<<: *client-app-base
|
||||
name: 🚛🍎 Client App Staging (iOS App Distribution)
|
||||
name: 🚛 🍎 Client App Staging (iOS App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- client_app_staging_credentials
|
||||
@@ -254,7 +246,7 @@ workflows:
|
||||
|
||||
client-app-prod-ios:
|
||||
<<: *client-app-base
|
||||
name: 🚛🍎 Client App Prod (iOS App Distribution)
|
||||
name: 🚛 🍎 Client App Prod (iOS App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- client_app_prod_credentials
|
||||
@@ -269,7 +261,7 @@ workflows:
|
||||
# =================================================================================
|
||||
staff-app-dev-android:
|
||||
<<: *staff-app-base
|
||||
name: 🚛🤖👨🍳 Staff App Dev (Android App Distribution)
|
||||
name: 🚛 🤖 👨🍳 Staff App Dev (Android App Distribution)
|
||||
environment:
|
||||
flutter: stable
|
||||
xcode: latest
|
||||
@@ -277,11 +269,7 @@ workflows:
|
||||
groups:
|
||||
- staff_app_dev_credentials
|
||||
android_signing:
|
||||
- keystore: krow_staff_dev
|
||||
keystore_environment_variable: CM_KEYSTORE_PATH_STAFF
|
||||
keystore_password_environment_variable: CM_KEYSTORE_PASSWORD_STAFF
|
||||
key_alias_environment_variable: CM_KEY_ALIAS_STAFF
|
||||
key_password_environment_variable: CM_KEY_PASSWORD_STAFF
|
||||
- keystore: KROW_STAFF_DEV
|
||||
vars:
|
||||
ENV: dev
|
||||
scripts:
|
||||
@@ -290,7 +278,7 @@ workflows:
|
||||
|
||||
staff-app-staging-android:
|
||||
<<: *staff-app-base
|
||||
name: 🚛🤖👨🍳 Staff App Staging (Android App Distribution)
|
||||
name: 🚛 🤖 👨🍳 Staff App Staging (Android App Distribution)
|
||||
environment:
|
||||
flutter: stable
|
||||
xcode: latest
|
||||
@@ -298,11 +286,7 @@ workflows:
|
||||
groups:
|
||||
- staff_app_staging_credentials
|
||||
android_signing:
|
||||
- keystore: krow_staff_staging
|
||||
keystore_environment_variable: CM_KEYSTORE_PATH_STAFF
|
||||
keystore_password_environment_variable: CM_KEYSTORE_PASSWORD_STAFF
|
||||
key_alias_environment_variable: CM_KEY_ALIAS_STAFF
|
||||
key_password_environment_variable: CM_KEY_PASSWORD_STAFF
|
||||
- keystore: KROW_STAFF_STAGING
|
||||
vars:
|
||||
ENV: staging
|
||||
scripts:
|
||||
@@ -311,7 +295,7 @@ workflows:
|
||||
|
||||
staff-app-prod-android:
|
||||
<<: *staff-app-base
|
||||
name: 🚛🤖👨🍳 Staff App Prod (Android App Distribution)
|
||||
name: 🚛 🤖 👨🍳 Staff App Prod (Android App Distribution)
|
||||
environment:
|
||||
flutter: stable
|
||||
xcode: latest
|
||||
@@ -319,11 +303,7 @@ workflows:
|
||||
groups:
|
||||
- staff_app_prod_credentials
|
||||
android_signing:
|
||||
- keystore: krow_staff_prod
|
||||
keystore_environment_variable: CM_KEYSTORE_PATH_STAFF
|
||||
keystore_password_environment_variable: CM_KEYSTORE_PASSWORD_STAFF
|
||||
key_alias_environment_variable: CM_KEY_ALIAS_STAFF
|
||||
key_password_environment_variable: CM_KEY_PASSWORD_STAFF
|
||||
- keystore: KROW_STAFF_PROD
|
||||
vars:
|
||||
ENV: prod
|
||||
scripts:
|
||||
@@ -335,7 +315,7 @@ workflows:
|
||||
# =================================================================================
|
||||
staff-app-dev-ios:
|
||||
<<: *staff-app-base
|
||||
name: 🚛🍎👨🍳 Staff App Dev (iOS App Distribution)
|
||||
name: 🚛 🍎 👨🍳 Staff App Dev (iOS App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- staff_app_dev_credentials
|
||||
@@ -347,7 +327,7 @@ workflows:
|
||||
|
||||
staff-app-staging-ios:
|
||||
<<: *staff-app-base
|
||||
name: 🚛🍎👨🍳 Staff App Staging (iOS App Distribution)
|
||||
name: 🚛 🍎 👨🍳 Staff App Staging (iOS App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- staff_app_staging_credentials
|
||||
@@ -359,7 +339,7 @@ workflows:
|
||||
|
||||
staff-app-prod-ios:
|
||||
<<: *staff-app-base
|
||||
name: 🚛🍎👨🍳 Staff App Prod (iOS App Distribution)
|
||||
name: 🚛 🍎 👨🍳 Staff App Prod (iOS App Distribution)
|
||||
environment:
|
||||
groups:
|
||||
- staff_app_prod_credentials
|
||||
|
||||
82
docs/MILESTONES/M4/demos/m4-client-note.md
Normal file
82
docs/MILESTONES/M4/demos/m4-client-note.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# KROW Workforce Platform — M4 Guide
|
||||
|
||||
**Version:** Milestone 4 (0.0.1-IlianaStaffM4 and 0.0.1-IlianaClientM4)
|
||||
**Estimated Duration:** 25-30 minutes
|
||||
|
||||
---
|
||||
|
||||
## 📦 Deliverables
|
||||
|
||||
- **Client Mobile Application** (v0.0.1-IlianaClientM4)
|
||||
- **Staff Mobile Application** (v0.0.1-IlianaStaffM4)
|
||||
- **Full Demo Video** - Comprehensive walkthrough of all (M1 - M4) completed features of the mobile applications.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### Core Improvements
|
||||
M4 delivers three key areas of improvement:
|
||||
|
||||
1. **Overall Application Improvements**
|
||||
- Auth session persistence: Users stay signed in after reopening the app
|
||||
- Stability fixes from M3 client feedback and dev team discoveries
|
||||
- UI/UX improvements across key screens for clarity and speed
|
||||
|
||||
2. **Client App Updates**
|
||||
- Complete order creation flow (Rapid, Permanent, Recurring orders)
|
||||
- Shift manager assignment support
|
||||
- Paid/unpaid break handling in orders
|
||||
- Complete Reports section (Daily Ops, Spend, Coverage, No-show, Performance)
|
||||
- Cost centres in hubs for location/business unit tracking
|
||||
- Billing approval workflow for pending bills
|
||||
|
||||
3. **Staff App Updates**
|
||||
- Profile completion requirements gating payments and clockings
|
||||
- Worker benefits integration
|
||||
- Enhanced shift discovery with filtering by location
|
||||
- Spanish localization support
|
||||
- AI-verified document uploads (Attire, Documents, Certificates)
|
||||
- FAQ and Privacy Policy
|
||||
- Worker profile visibility controls
|
||||
|
||||
---
|
||||
|
||||
## 2. Required Test Accounts
|
||||
|
||||
**Client Account (Business User):**
|
||||
- Email: `legendary@krowd.com`
|
||||
- Password: `Demo2026!`
|
||||
- Client Name: "KROW"
|
||||
|
||||
**Staff Account (Worker):**
|
||||
- Phone: `+15557654321`
|
||||
- OTP Code: `123456` (testing mode)
|
||||
- Name: "Mariana Torres"
|
||||
|
||||
***Note on Profile Completion***
|
||||
When a staff user hasn't completed their profile, they see an empty/incomplete state on their home screen. Currently tracked sections to mark as complete:
|
||||
- Profile Information (full name, email, phone, preferred locations)
|
||||
- Emergency Contact
|
||||
|
||||
Future sections can be added as mandatory, such as Tax Forms, Bank Account, Documents, Certificates, and Attires.
|
||||
|
||||
***Profile Blocking Rules***
|
||||
When the profile is incomplete, the following features are blocked to encourage completion:
|
||||
- Clock-in page is hidden
|
||||
- Payments are blocked
|
||||
- "My Shifts" and History sections are hidden
|
||||
- Users can view available shifts but cannot book them
|
||||
|
||||
This ensures we have all necessary information for compliance and payroll before workers are allowed to work.
|
||||
|
||||
---
|
||||
|
||||
## 3. M4 Key Deliverables
|
||||
|
||||
✅ Stronger reliability and stability
|
||||
✅ Completed client ordering and reporting workflows
|
||||
✅ Better profile and shift tooling for staff
|
||||
✅ AI-assisted document verification
|
||||
✅ Localization support (Spanish)
|
||||
✅ Improved billing and cost tracking controls
|
||||
Reference in New Issue
Block a user