Add explicit types and improve type safety across codebase
This commit adds explicit type annotations to variables, function parameters, and return types throughout the codebase, particularly in widget trees, Bloc logic, and repository implementations. The changes improve code readability, maintainability, and type safety, and align with Dart best practices. No business logic was changed.
This commit is contained in:
@@ -23,7 +23,7 @@ export 'package:core_localization/core_localization.dart';
|
||||
/// A [Module] for the client authentication feature.
|
||||
class ClientAuthenticationModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [DataConnectModule()];
|
||||
List<Module> get imports => <Module>[DataConnectModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
@@ -59,7 +59,7 @@ class ClientAuthenticationModule extends Module {
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(r) {
|
||||
void routes(RouteManager r) {
|
||||
r.child('/', child: (_) => const ClientGetStartedPage());
|
||||
r.child('/client-sign-in', child: (_) => const ClientSignInPage());
|
||||
r.child('/client-sign-up', child: (_) => const ClientSignUpPage());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:firebase_data_connect/src/core/ref.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../domain/repositories/auth_repository_interface.dart';
|
||||
@@ -24,12 +25,12 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
final credential = await _firebaseAuth.signInWithEmailAndPassword(
|
||||
final firebase.UserCredential credential = await _firebaseAuth.signInWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
final firebaseUser = credential.user;
|
||||
final firebase.User? firebaseUser = credential.user;
|
||||
if (firebaseUser == null) {
|
||||
throw Exception('Sign-in failed, no Firebase user received.');
|
||||
}
|
||||
@@ -59,12 +60,12 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
final credential = await _firebaseAuth.createUserWithEmailAndPassword(
|
||||
final firebase.UserCredential credential = await _firebaseAuth.createUserWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
final firebaseUser = credential.user;
|
||||
final firebase.User? firebaseUser = credential.user;
|
||||
if (firebaseUser == null) {
|
||||
throw Exception('Sign-up failed, Firebase user could not be created.');
|
||||
}
|
||||
@@ -72,20 +73,20 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
// Client-specific business logic:
|
||||
// 1. Create a `Business` entity.
|
||||
// 2. Create a `User` entity associated with the business.
|
||||
final createBusinessResponse = await _dataConnect.createBusiness(
|
||||
final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables> createBusinessResponse = await _dataConnect.createBusiness(
|
||||
businessName: companyName,
|
||||
userId: firebaseUser.uid,
|
||||
rateGroup: dc.BusinessRateGroup.STANDARD,
|
||||
status: dc.BusinessStatus.PENDING,
|
||||
).execute();
|
||||
|
||||
final businessData = createBusinessResponse.data?.business_insert;
|
||||
final dc.CreateBusinessBusinessInsert? businessData = createBusinessResponse.data?.business_insert;
|
||||
if (businessData == null) {
|
||||
await firebaseUser.delete(); // Rollback if business creation fails
|
||||
throw Exception('Business creation failed after Firebase user registration.');
|
||||
}
|
||||
|
||||
final createUserResponse = await _dataConnect.createUser(
|
||||
final OperationResult<dc.CreateUserData, dc.CreateUserVariables> createUserResponse = await _dataConnect.createUser(
|
||||
id: firebaseUser.uid,
|
||||
role: dc.UserBaseRole.USER,
|
||||
)
|
||||
@@ -93,7 +94,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
.userRole('BUSINESS')
|
||||
.execute();
|
||||
|
||||
final newUserData = createUserResponse.data?.user_insert;
|
||||
final dc.CreateUserUserInsert? newUserData = createUserResponse.data?.user_insert;
|
||||
if (newUserData == null) {
|
||||
await firebaseUser.delete(); // Rollback if user profile creation fails
|
||||
// TO-DO: Also delete the created Business if this fails
|
||||
@@ -137,27 +138,27 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
required String firebaseUserId,
|
||||
required String? fallbackEmail,
|
||||
}) async {
|
||||
final response = await _dataConnect.getUserById(id: firebaseUserId).execute();
|
||||
final user = response.data?.user;
|
||||
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response = await _dataConnect.getUserById(id: firebaseUserId).execute();
|
||||
final dc.GetUserByIdUser? user = response.data?.user;
|
||||
if (user == null) {
|
||||
throw Exception('Authenticated user profile not found in database.');
|
||||
}
|
||||
|
||||
final email = user.email ?? fallbackEmail;
|
||||
final String? email = user.email ?? fallbackEmail;
|
||||
if (email == null || email.isEmpty) {
|
||||
throw Exception('User email is missing in profile data.');
|
||||
}
|
||||
|
||||
final domainUser = domain.User(
|
||||
final domain.User domainUser = domain.User(
|
||||
id: user.id,
|
||||
email: email,
|
||||
role: user.role.stringValue,
|
||||
);
|
||||
|
||||
final businessResponse = await _dataConnect.getBusinessesByUserId(
|
||||
final QueryResult<dc.GetBusinessesByUserIdData, dc.GetBusinessesByUserIdVariables> businessResponse = await _dataConnect.getBusinessesByUserId(
|
||||
userId: firebaseUserId,
|
||||
).execute();
|
||||
final business = businessResponse.data.businesses.isNotEmpty
|
||||
final dc.GetBusinessesByUserIdBusinesses? business = businessResponse.data.businesses.isNotEmpty
|
||||
? businessResponse.data.businesses.first
|
||||
: null;
|
||||
|
||||
|
||||
@@ -11,5 +11,5 @@ class SignInWithEmailArguments extends UseCaseArgument {
|
||||
const SignInWithEmailArguments({required this.email, required this.password});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [email, password];
|
||||
List<Object?> get props => <Object?>[email, password];
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ class SignInWithSocialArguments extends UseCaseArgument {
|
||||
const SignInWithSocialArguments({required this.provider});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [provider];
|
||||
List<Object?> get props => <Object?>[provider];
|
||||
}
|
||||
|
||||
@@ -18,5 +18,5 @@ class SignUpWithEmailArguments extends UseCaseArgument {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [companyName, email, password];
|
||||
List<Object?> get props => <Object?>[companyName, email, password];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/users/user.dart';
|
||||
import '../../domain/arguments/sign_in_with_email_arguments.dart';
|
||||
import '../../domain/arguments/sign_in_with_social_arguments.dart';
|
||||
import '../../domain/arguments/sign_up_with_email_arguments.dart';
|
||||
@@ -50,7 +51,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
try {
|
||||
final user = await _signInWithEmail(
|
||||
final User user = await _signInWithEmail(
|
||||
SignInWithEmailArguments(email: event.email, password: event.password),
|
||||
);
|
||||
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
|
||||
@@ -71,7 +72,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
try {
|
||||
final user = await _signUpWithEmail(
|
||||
final User user = await _signUpWithEmail(
|
||||
SignUpWithEmailArguments(
|
||||
companyName: event.companyName,
|
||||
email: event.email,
|
||||
@@ -96,7 +97,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
try {
|
||||
final user = await _signInWithSocial(
|
||||
final User user = await _signInWithSocial(
|
||||
SignInWithSocialArguments(provider: event.provider),
|
||||
);
|
||||
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
|
||||
|
||||
@@ -5,7 +5,7 @@ abstract class ClientAuthEvent extends Equatable {
|
||||
const ClientAuthEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
/// Event dispatched when a user attempts to sign in with email and password.
|
||||
@@ -16,7 +16,7 @@ class ClientSignInRequested extends ClientAuthEvent {
|
||||
const ClientSignInRequested({required this.email, required this.password});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [email, password];
|
||||
List<Object?> get props => <Object?>[email, password];
|
||||
}
|
||||
|
||||
/// Event dispatched when a user attempts to create a new business account.
|
||||
@@ -32,7 +32,7 @@ class ClientSignUpRequested extends ClientAuthEvent {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [companyName, email, password];
|
||||
List<Object?> get props => <Object?>[companyName, email, password];
|
||||
}
|
||||
|
||||
/// Event dispatched for third-party authentication (Google/Apple).
|
||||
@@ -42,7 +42,7 @@ class ClientSocialSignInRequested extends ClientAuthEvent {
|
||||
const ClientSocialSignInRequested({required this.provider});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [provider];
|
||||
List<Object?> get props => <Object?>[provider];
|
||||
}
|
||||
|
||||
/// Event dispatched when the user requests to terminate their session.
|
||||
|
||||
@@ -50,5 +50,5 @@ class ClientAuthState extends Equatable {
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, user, errorMessage];
|
||||
List<Object?> get props => <Object?>[status, user, errorMessage];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class ClientGetStartedPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Background Illustration/Visuals from prototype
|
||||
Positioned(
|
||||
top: -100,
|
||||
@@ -28,7 +28,7 @@ class ClientGetStartedPage extends StatelessWidget {
|
||||
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
// Logo
|
||||
Center(
|
||||
@@ -48,7 +48,7 @@ class ClientGetStartedPage extends StatelessWidget {
|
||||
horizontal: UiConstants.space6,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Representative cards from prototype
|
||||
Positioned(
|
||||
top: 20,
|
||||
@@ -76,7 +76,7 @@ class ClientGetStartedPage extends StatelessWidget {
|
||||
vertical: UiConstants.space10,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_authentication.get_started_page.title,
|
||||
textAlign: TextAlign.center,
|
||||
@@ -132,7 +132,7 @@ class _ShiftOrderCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
@@ -143,9 +143,9 @@ class _ShiftOrderCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space1),
|
||||
decoration: BoxDecoration(
|
||||
@@ -195,7 +195,7 @@ class _WorkerProfileCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
@@ -204,7 +204,7 @@ class _WorkerProfileCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: UiColors.primary.withOpacity(0.1),
|
||||
@@ -214,7 +214,7 @@ class _WorkerProfileCard extends StatelessWidget {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text('Alex Thompson', style: UiTypography.footnote1b),
|
||||
Text(
|
||||
'Professional Waiter • 4.9★',
|
||||
@@ -236,7 +236,7 @@ class _CalendarCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.accent,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
|
||||
@@ -35,13 +35,13 @@ class ClientSignInPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_authentication.sign_in_page;
|
||||
final authBloc = Modular.get<ClientAuthBloc>();
|
||||
final TranslationsClientAuthenticationSignInPageEn i18n = t.client_authentication.sign_in_page;
|
||||
final ClientAuthBloc authBloc = Modular.get<ClientAuthBloc>();
|
||||
|
||||
return BlocProvider.value(
|
||||
value: authBloc,
|
||||
child: BlocConsumer<ClientAuthBloc, ClientAuthState>(
|
||||
listener: (context, state) {
|
||||
listener: (BuildContext context, ClientAuthState state) {
|
||||
if (state.status == ClientAuthStatus.authenticated) {
|
||||
Modular.to.navigateClientHome();
|
||||
} else if (state.status == ClientAuthStatus.error) {
|
||||
@@ -52,8 +52,8 @@ class ClientSignInPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final isLoading = state.status == ClientAuthStatus.loading;
|
||||
builder: (BuildContext context, ClientAuthState state) {
|
||||
final bool isLoading = state.status == ClientAuthStatus.loading;
|
||||
|
||||
return Scaffold(
|
||||
appBar: const UiAppBar(showBackButton: true),
|
||||
@@ -69,14 +69,14 @@ class ClientSignInPage extends StatelessWidget {
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
SectionTitle(title: i18n.title, subtitle: i18n.subtitle),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Sign In Form
|
||||
ClientSignInForm(
|
||||
isLoading: isLoading,
|
||||
onSignIn: ({required email, required password}) =>
|
||||
onSignIn: ({required String email, required String password}) =>
|
||||
_handleSignIn(
|
||||
context,
|
||||
email: email,
|
||||
@@ -99,7 +99,7 @@ class ClientSignInPage extends StatelessWidget {
|
||||
// Sign Up Link
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.no_account,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
|
||||
@@ -39,13 +39,13 @@ class ClientSignUpPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_authentication.sign_up_page;
|
||||
final authBloc = Modular.get<ClientAuthBloc>();
|
||||
final TranslationsClientAuthenticationSignUpPageEn i18n = t.client_authentication.sign_up_page;
|
||||
final ClientAuthBloc authBloc = Modular.get<ClientAuthBloc>();
|
||||
|
||||
return BlocProvider.value(
|
||||
value: authBloc,
|
||||
child: BlocConsumer<ClientAuthBloc, ClientAuthState>(
|
||||
listener: (context, state) {
|
||||
listener: (BuildContext context, ClientAuthState state) {
|
||||
if (state.status == ClientAuthStatus.authenticated) {
|
||||
Modular.to.navigateClientHome();
|
||||
} else if (state.status == ClientAuthStatus.error) {
|
||||
@@ -56,8 +56,8 @@ class ClientSignUpPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final isLoading = state.status == ClientAuthStatus.loading;
|
||||
builder: (BuildContext context, ClientAuthState state) {
|
||||
final bool isLoading = state.status == ClientAuthStatus.loading;
|
||||
|
||||
return Scaffold(
|
||||
appBar: const UiAppBar(showBackButton: true),
|
||||
@@ -73,7 +73,7 @@ class ClientSignUpPage extends StatelessWidget {
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
SectionTitle(title: i18n.title, subtitle: i18n.subtitle),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
@@ -82,9 +82,9 @@ class ClientSignUpPage extends StatelessWidget {
|
||||
isLoading: isLoading,
|
||||
onSignUp:
|
||||
({
|
||||
required companyName,
|
||||
required email,
|
||||
required password,
|
||||
required String companyName,
|
||||
required String email,
|
||||
required String password,
|
||||
}) => _handleSignUp(
|
||||
context,
|
||||
companyName: companyName,
|
||||
@@ -108,7 +108,7 @@ class ClientSignUpPage extends StatelessWidget {
|
||||
// Sign In Link
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.has_account,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
|
||||
@@ -26,8 +26,8 @@ class ClientSignInForm extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ClientSignInFormState extends State<ClientSignInForm> {
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
@@ -46,10 +46,10 @@ class _ClientSignInFormState extends State<ClientSignInForm> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_authentication.sign_in_page;
|
||||
final TranslationsClientAuthenticationSignInPageEn i18n = t.client_authentication.sign_in_page;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Email Field
|
||||
UiTextField(
|
||||
label: i18n.email_label,
|
||||
|
||||
@@ -30,10 +30,10 @@ class ClientSignUpForm extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ClientSignUpFormState extends State<ClientSignUpForm> {
|
||||
final _companyController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _confirmPasswordController = TextEditingController();
|
||||
final TextEditingController _companyController = TextEditingController();
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _confirmPasswordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
@@ -62,10 +62,10 @@ class _ClientSignUpFormState extends State<ClientSignUpForm> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_authentication.sign_up_page;
|
||||
final TranslationsClientAuthenticationSignUpPageEn i18n = t.client_authentication.sign_up_page;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Company Name Field
|
||||
UiTextField(
|
||||
label: i18n.company_label,
|
||||
|
||||
@@ -15,7 +15,7 @@ class AuthDivider extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Expanded(child: Divider()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
|
||||
@@ -15,7 +15,7 @@ class SectionTitle extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(title, style: UiTypography.headline1m),
|
||||
Text(subtitle, style: UiTypography.body2r.textSecondary),
|
||||
],
|
||||
|
||||
@@ -52,18 +52,18 @@ class ClientCreateOrderRepositoryImpl
|
||||
|
||||
@override
|
||||
Future<void> createOneTimeOrder(domain.OneTimeOrder order) async {
|
||||
final businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
if (businessId == null || businessId.isEmpty) {
|
||||
await _firebaseAuth.signOut();
|
||||
throw Exception('Business is missing. Please sign in again.');
|
||||
}
|
||||
final vendorId = order.vendorId;
|
||||
final String? vendorId = order.vendorId;
|
||||
if (vendorId == null || vendorId.isEmpty) {
|
||||
throw Exception('Vendor is missing.');
|
||||
}
|
||||
|
||||
final orderTimestamp = _toTimestamp(order.date);
|
||||
final orderResult = await _dataConnect
|
||||
final fdc.Timestamp orderTimestamp = _toTimestamp(order.date);
|
||||
final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult = await _dataConnect
|
||||
.createOrder(businessId: businessId, orderType: dc.OrderType.ONE_TIME)
|
||||
.vendorId(vendorId)
|
||||
.location(order.location)
|
||||
@@ -71,19 +71,19 @@ class ClientCreateOrderRepositoryImpl
|
||||
.date(orderTimestamp)
|
||||
.execute();
|
||||
|
||||
final orderId = orderResult.data?.order_insert.id;
|
||||
final String? orderId = orderResult.data?.order_insert.id;
|
||||
if (orderId == null) {
|
||||
throw Exception('Order creation failed.');
|
||||
}
|
||||
|
||||
final workersNeeded = order.positions.fold<int>(
|
||||
final int workersNeeded = order.positions.fold<int>(
|
||||
0,
|
||||
(sum, position) => sum + position.count,
|
||||
(int sum, domain.OneTimeOrderPosition position) => sum + position.count,
|
||||
);
|
||||
final shiftTitle = 'Shift 1 ${_formatDate(order.date)}';
|
||||
final shiftCost = _calculateShiftCost(order);
|
||||
final String shiftTitle = 'Shift 1 ${_formatDate(order.date)}';
|
||||
final double shiftCost = _calculateShiftCost(order);
|
||||
|
||||
final shiftResult = await _dataConnect
|
||||
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult = await _dataConnect
|
||||
.createShift(title: shiftTitle, orderId: orderId)
|
||||
.date(orderTimestamp)
|
||||
.location(order.location)
|
||||
@@ -95,19 +95,19 @@ class ClientCreateOrderRepositoryImpl
|
||||
.cost(shiftCost)
|
||||
.execute();
|
||||
|
||||
final shiftId = shiftResult.data?.shift_insert.id;
|
||||
final String? shiftId = shiftResult.data?.shift_insert.id;
|
||||
if (shiftId == null) {
|
||||
throw Exception('Shift creation failed.');
|
||||
}
|
||||
|
||||
for (final position in order.positions) {
|
||||
final start = _parseTime(order.date, position.startTime);
|
||||
final end = _parseTime(order.date, position.endTime);
|
||||
final normalizedEnd =
|
||||
for (final domain.OneTimeOrderPosition position in order.positions) {
|
||||
final DateTime start = _parseTime(order.date, position.startTime);
|
||||
final DateTime end = _parseTime(order.date, position.endTime);
|
||||
final DateTime normalizedEnd =
|
||||
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
|
||||
final hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final rate = order.roleRates[position.role] ?? 0;
|
||||
final totalValue = rate * hours * position.count;
|
||||
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final double rate = order.roleRates[position.role] ?? 0;
|
||||
final double totalValue = rate * hours * position.count;
|
||||
|
||||
await _dataConnect
|
||||
.createShiftRole(
|
||||
@@ -136,13 +136,13 @@ class ClientCreateOrderRepositoryImpl
|
||||
|
||||
double _calculateShiftCost(domain.OneTimeOrder order) {
|
||||
double total = 0;
|
||||
for (final position in order.positions) {
|
||||
final start = _parseTime(order.date, position.startTime);
|
||||
final end = _parseTime(order.date, position.endTime);
|
||||
final normalizedEnd =
|
||||
for (final domain.OneTimeOrderPosition position in order.positions) {
|
||||
final DateTime start = _parseTime(order.date, position.startTime);
|
||||
final DateTime end = _parseTime(order.date, position.endTime);
|
||||
final DateTime normalizedEnd =
|
||||
end.isBefore(start) ? end.add(const Duration(days: 1)) : end;
|
||||
final hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final rate = order.roleRates[position.role] ?? 0;
|
||||
final double hours = normalizedEnd.difference(start).inMinutes / 60.0;
|
||||
final double rate = order.roleRates[position.role] ?? 0;
|
||||
total += rate * hours * position.count;
|
||||
}
|
||||
return total;
|
||||
@@ -170,16 +170,16 @@ class ClientCreateOrderRepositoryImpl
|
||||
}
|
||||
|
||||
fdc.Timestamp _toTimestamp(DateTime dateTime) {
|
||||
final utc = dateTime.toUtc();
|
||||
final seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
final DateTime utc = dateTime.toUtc();
|
||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
return fdc.Timestamp(nanoseconds, seconds);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime dateTime) {
|
||||
final year = dateTime.year.toString().padLeft(4, '0');
|
||||
final month = dateTime.month.toString().padLeft(2, '0');
|
||||
final day = dateTime.day.toString().padLeft(2, '0');
|
||||
final String year = dateTime.year.toString().padLeft(4, '0');
|
||||
final String month = dateTime.month.toString().padLeft(2, '0');
|
||||
final String day = dateTime.day.toString().padLeft(2, '0');
|
||||
return '$year-$month-$day';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:firebase_data_connect/src/core/ref.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
@@ -26,10 +27,10 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
|
||||
Future<void> _loadVendors() async {
|
||||
try {
|
||||
final result = await _dataConnect.listVendors().execute();
|
||||
final vendors = result.data.vendors
|
||||
final QueryResult<dc.ListVendorsData, void> result = await _dataConnect.listVendors().execute();
|
||||
final List<Vendor> vendors = result.data.vendors
|
||||
.map(
|
||||
(vendor) => Vendor(
|
||||
(dc.ListVendorsVendors vendor) => Vendor(
|
||||
id: vendor.id,
|
||||
name: vendor.companyName,
|
||||
rates: const <String, double>{},
|
||||
@@ -44,12 +45,12 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
|
||||
Future<void> _loadRolesForVendor(String vendorId) async {
|
||||
try {
|
||||
final result = await _dataConnect.listRolesByVendorId(
|
||||
final QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables> result = await _dataConnect.listRolesByVendorId(
|
||||
vendorId: vendorId,
|
||||
).execute();
|
||||
final roles = result.data.roles
|
||||
final List<OneTimeOrderRoleOption> roles = result.data.roles
|
||||
.map(
|
||||
(role) => OneTimeOrderRoleOption(
|
||||
(dc.ListRolesByVendorIdRoles role) => OneTimeOrderRoleOption(
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
costPerHour: role.costPerHour,
|
||||
@@ -146,7 +147,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.loading));
|
||||
try {
|
||||
final Map<String, double> roleRates = <String, double>{
|
||||
for (final role in state.roles) role.id: role.costPerHour,
|
||||
for (final OneTimeOrderRoleOption role in state.roles) role.id: role.costPerHour,
|
||||
};
|
||||
final OneTimeOrder order = OneTimeOrder(
|
||||
date: state.date,
|
||||
|
||||
@@ -301,9 +301,9 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<String>> _buildRoleItems() {
|
||||
final items = roles
|
||||
final List<DropdownMenuItem<String>> items = roles
|
||||
.map(
|
||||
(role) => DropdownMenuItem<String>(
|
||||
(OneTimeOrderRoleOption role) => DropdownMenuItem<String>(
|
||||
value: role.id,
|
||||
child: Text(
|
||||
'${role.name} - \$${role.costPerHour.toStringAsFixed(0)}',
|
||||
@@ -313,7 +313,7 @@ class OneTimeOrderPositionCard extends StatelessWidget {
|
||||
)
|
||||
.toList();
|
||||
|
||||
final hasSelected = roles.any((role) => role.id == position.role);
|
||||
final bool hasSelected = roles.any((OneTimeOrderRoleOption role) => role.id == position.role);
|
||||
if (position.role.isNotEmpty && !hasSelected) {
|
||||
items.add(
|
||||
DropdownMenuItem<String>(
|
||||
|
||||
@@ -18,7 +18,7 @@ export 'src/presentation/navigation/client_home_navigator.dart';
|
||||
/// including repositories, use cases, and BLoCs.
|
||||
class ClientHomeModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [DataConnectModule()];
|
||||
List<Module> get imports => <Module>[DataConnectModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
@@ -41,7 +41,7 @@ class ClientHomeModule extends Module {
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(r) {
|
||||
void routes(RouteManager r) {
|
||||
r.child('/', child: (_) => const ClientHomePage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
|
||||
|
||||
@override
|
||||
UserSessionData getUserSessionData() {
|
||||
final (businessName, photoUrl) = _mock.getUserSession();
|
||||
final (String businessName, String? photoUrl) = _mock.getUserSession();
|
||||
return UserSessionData(
|
||||
businessName: businessName,
|
||||
photoUrl: photoUrl,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:client_home/src/domain/repositories/home_repository_interface.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/home/home_dashboard_data.dart';
|
||||
import '../../domain/usecases/get_dashboard_data_usecase.dart';
|
||||
import '../../domain/usecases/get_user_session_data_usecase.dart';
|
||||
import 'client_home_event.dart';
|
||||
@@ -29,10 +31,10 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
emit(state.copyWith(status: ClientHomeStatus.loading));
|
||||
try {
|
||||
// Get session data
|
||||
final sessionData = _getUserSessionDataUseCase();
|
||||
final UserSessionData sessionData = _getUserSessionDataUseCase();
|
||||
|
||||
// Get dashboard data
|
||||
final data = await _getDashboardDataUseCase();
|
||||
final HomeDashboardData data = await _getDashboardDataUseCase();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -63,7 +65,7 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
ClientHomeWidgetVisibilityToggled event,
|
||||
Emitter<ClientHomeState> emit,
|
||||
) {
|
||||
final newVisibility = Map<String, bool>.from(state.widgetVisibility);
|
||||
final Map<String, bool> newVisibility = Map<String, bool>.from(state.widgetVisibility);
|
||||
newVisibility[event.widgetId] = !(newVisibility[event.widgetId] ?? true);
|
||||
emit(state.copyWith(widgetVisibility: newVisibility));
|
||||
}
|
||||
@@ -72,14 +74,14 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
ClientHomeWidgetReordered event,
|
||||
Emitter<ClientHomeState> emit,
|
||||
) {
|
||||
final newList = List<String>.from(state.widgetOrder);
|
||||
final List<String> newList = List<String>.from(state.widgetOrder);
|
||||
int oldIndex = event.oldIndex;
|
||||
int newIndex = event.newIndex;
|
||||
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final item = newList.removeAt(oldIndex);
|
||||
final String item = newList.removeAt(oldIndex);
|
||||
newList.insert(newIndex, item);
|
||||
|
||||
emit(state.copyWith(widgetOrder: newList));
|
||||
@@ -91,14 +93,14 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
widgetOrder: const [
|
||||
widgetOrder: const <String>[
|
||||
'actions',
|
||||
'reorder',
|
||||
'coverage',
|
||||
'spending',
|
||||
'liveActivity',
|
||||
],
|
||||
widgetVisibility: const {
|
||||
widgetVisibility: const <String, bool>{
|
||||
'actions': true,
|
||||
'reorder': true,
|
||||
'coverage': true,
|
||||
|
||||
@@ -4,7 +4,7 @@ abstract class ClientHomeEvent extends Equatable {
|
||||
const ClientHomeEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class ClientHomeStarted extends ClientHomeEvent {}
|
||||
@@ -16,7 +16,7 @@ class ClientHomeWidgetVisibilityToggled extends ClientHomeEvent {
|
||||
const ClientHomeWidgetVisibilityToggled(this.widgetId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [widgetId];
|
||||
List<Object?> get props => <Object?>[widgetId];
|
||||
}
|
||||
|
||||
class ClientHomeWidgetReordered extends ClientHomeEvent {
|
||||
@@ -25,7 +25,7 @@ class ClientHomeWidgetReordered extends ClientHomeEvent {
|
||||
const ClientHomeWidgetReordered(this.oldIndex, this.newIndex);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [oldIndex, newIndex];
|
||||
List<Object?> get props => <Object?>[oldIndex, newIndex];
|
||||
}
|
||||
|
||||
class ClientHomeLayoutReset extends ClientHomeEvent {}
|
||||
|
||||
@@ -17,10 +17,10 @@ class ClientHomeState extends Equatable {
|
||||
|
||||
const ClientHomeState({
|
||||
this.status = ClientHomeStatus.initial,
|
||||
this.widgetOrder = const [
|
||||
this.widgetOrder = const <String>[
|
||||
'actions',
|
||||
],
|
||||
this.widgetVisibility = const {
|
||||
this.widgetVisibility = const <String, bool>{
|
||||
'actions': true,
|
||||
},
|
||||
this.isEditMode = false,
|
||||
@@ -60,7 +60,7 @@ class ClientHomeState extends Equatable {
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
List<Object?> get props => <Object?>[
|
||||
status,
|
||||
widgetOrder,
|
||||
widgetVisibility,
|
||||
|
||||
@@ -31,10 +31,10 @@ class ClientHomeSheets {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
builder: (BuildContext context) {
|
||||
return ShiftOrderFormSheet(
|
||||
initialData: initialData,
|
||||
onSubmit: (data) {
|
||||
onSubmit: (Map<String, dynamic> data) {
|
||||
Navigator.pop(context);
|
||||
onSubmit(data);
|
||||
},
|
||||
|
||||
@@ -21,20 +21,20 @@ class ClientHomePage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_home;
|
||||
final TranslationsClientHomeEn i18n = t.client_home;
|
||||
|
||||
return BlocProvider<ClientHomeBloc>(
|
||||
create: (context) =>
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<ClientHomeBloc>()..add(ClientHomeStarted()),
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
ClientHomeHeader(i18n: i18n),
|
||||
ClientHomeEditBanner(i18n: i18n),
|
||||
Flexible(
|
||||
child: BlocBuilder<ClientHomeBloc, ClientHomeState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, ClientHomeState state) {
|
||||
if (state.isEditMode) {
|
||||
return _buildEditModeList(context, state);
|
||||
}
|
||||
@@ -58,12 +58,12 @@ class ClientHomePage extends StatelessWidget {
|
||||
UiConstants.space4,
|
||||
100,
|
||||
),
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
BlocProvider.of<ClientHomeBloc>(context).add(
|
||||
ClientHomeWidgetReordered(oldIndex, newIndex),
|
||||
);
|
||||
},
|
||||
children: state.widgetOrder.map((id) {
|
||||
children: state.widgetOrder.map((String id) {
|
||||
return Container(
|
||||
key: ValueKey(id),
|
||||
margin: const EdgeInsets.only(bottom: UiConstants.space4),
|
||||
@@ -86,7 +86,7 @@ class ClientHomePage extends StatelessWidget {
|
||||
UiConstants.space4,
|
||||
100,
|
||||
),
|
||||
children: state.widgetOrder.map((id) {
|
||||
children: state.widgetOrder.map((String id) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space4),
|
||||
child: DashboardWidgetBuilder(
|
||||
|
||||
@@ -20,10 +20,10 @@ class ActionsWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Check if client_home exists in t
|
||||
final i18n = t.client_home.actions;
|
||||
final TranslationsClientHomeActionsEn i18n = t.client_home.actions;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _ActionCard(
|
||||
title: i18n.rapid,
|
||||
@@ -96,7 +96,7 @@ class _ActionCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
|
||||
@@ -22,8 +22,8 @@ class ClientHomeEditBanner extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ClientHomeBloc, ClientHomeState>(
|
||||
buildWhen: (prev, curr) => prev.isEditMode != curr.isEditMode,
|
||||
builder: (context, state) {
|
||||
buildWhen: (ClientHomeState prev, ClientHomeState curr) => prev.isEditMode != curr.isEditMode,
|
||||
builder: (BuildContext context, ClientHomeState state) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: state.isEditMode ? 76 : 0,
|
||||
@@ -40,13 +40,13 @@ class ClientHomeEditBanner extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.edit, size: 16, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.dashboard.edit_mode_active,
|
||||
style: UiTypography.footnote1b.copyWith(
|
||||
|
||||
@@ -25,10 +25,10 @@ class ClientHomeHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ClientHomeBloc, ClientHomeState>(
|
||||
builder: (context, state) {
|
||||
final businessName = state.businessName;
|
||||
final photoUrl = state.photoUrl;
|
||||
final avatarLetter = businessName.trim().isNotEmpty
|
||||
builder: (BuildContext context, ClientHomeState state) {
|
||||
final String businessName = state.businessName;
|
||||
final String? photoUrl = state.photoUrl;
|
||||
final String avatarLetter = businessName.trim().isNotEmpty
|
||||
? businessName.trim()[0].toUpperCase()
|
||||
: 'C';
|
||||
|
||||
@@ -41,9 +41,9 @@ class ClientHomeHeader extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
@@ -73,7 +73,7 @@ class ClientHomeHeader extends StatelessWidget {
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.dashboard.welcome_back,
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
|
||||
@@ -23,32 +23,32 @@ class CoverageDashboard extends StatelessWidget {
|
||||
double todayCost = 0;
|
||||
|
||||
for (final s in shifts) {
|
||||
final needed = s['workersNeeded'] as int? ?? 0;
|
||||
final confirmed = s['filled'] as int? ?? 0;
|
||||
final rate = s['hourlyRate'] as double? ?? 20.0;
|
||||
final int needed = s['workersNeeded'] as int? ?? 0;
|
||||
final int confirmed = s['filled'] as int? ?? 0;
|
||||
final double rate = s['hourlyRate'] as double? ?? 20.0;
|
||||
|
||||
totalNeeded += needed;
|
||||
totalConfirmed += confirmed;
|
||||
todayCost += rate * 8 * confirmed;
|
||||
}
|
||||
|
||||
final coveragePercent = totalNeeded > 0
|
||||
final int coveragePercent = totalNeeded > 0
|
||||
? ((totalConfirmed / totalNeeded) * 100).round()
|
||||
: 100;
|
||||
final unfilledPositions = totalNeeded - totalConfirmed;
|
||||
final int unfilledPositions = totalNeeded - totalConfirmed;
|
||||
|
||||
final checkedInCount = applications
|
||||
final int checkedInCount = applications
|
||||
.where((a) => (a as Map)['checkInTime'] != null)
|
||||
.length;
|
||||
final lateWorkersCount = applications
|
||||
final int lateWorkersCount = applications
|
||||
.where((a) => (a as Map)['status'] == 'LATE')
|
||||
.length;
|
||||
|
||||
final isCoverageGood = coveragePercent >= 90;
|
||||
final coverageBadgeColor = isCoverageGood
|
||||
final bool isCoverageGood = coveragePercent >= 90;
|
||||
final Color coverageBadgeColor = isCoverageGood
|
||||
? const Color(0xFFD1FAE5) // TODO: Use design system color if available
|
||||
: const Color(0xFFFEF3C7);
|
||||
final coverageTextColor = isCoverageGood
|
||||
final Color coverageTextColor = isCoverageGood
|
||||
? const Color(0xFF047857)
|
||||
: const Color(0xFFB45309);
|
||||
|
||||
@@ -58,7 +58,7 @@ class CoverageDashboard extends StatelessWidget {
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 4,
|
||||
@@ -67,10 +67,10 @@ class CoverageDashboard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text("Today's Status", style: UiTypography.body1m.textSecondary),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -94,17 +94,17 @@ class CoverageDashboard extends StatelessWidget {
|
||||
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_StatusCard(
|
||||
label: 'Unfilled Today',
|
||||
value: '$unfilledPositions',
|
||||
icon: UiIcons.warning,
|
||||
isWarning: unfilledPositions > 0,
|
||||
),
|
||||
if (lateWorkersCount > 0) ...[
|
||||
if (lateWorkersCount > 0) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
_StatusCard(
|
||||
label: 'Running Late',
|
||||
@@ -119,7 +119,7 @@ class CoverageDashboard extends StatelessWidget {
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_StatusCard(
|
||||
label: 'Checked In',
|
||||
value: '$checkedInCount/$totalConfirmed',
|
||||
@@ -194,9 +194,9 @@ class _StatusCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 16, color: iconColor),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
|
||||
@@ -23,10 +23,10 @@ class CoverageWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"TODAY'S COVERAGE",
|
||||
style: UiTypography.footnote1b.copyWith(
|
||||
@@ -55,7 +55,7 @@ class CoverageWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _MetricCard(
|
||||
icon: UiIcons.target,
|
||||
@@ -114,7 +114,7 @@ class _MetricCard extends StatelessWidget {
|
||||
color: UiColors.cardViewBackground,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 2,
|
||||
@@ -123,9 +123,9 @@ class _MetricCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 14, color: iconColor),
|
||||
const SizedBox(width: 6), // 6px
|
||||
Text(
|
||||
|
||||
@@ -34,8 +34,8 @@ class DashboardWidgetBuilder extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_home.widgets;
|
||||
final widgetContent = _buildWidgetContent(context);
|
||||
final TranslationsClientHomeWidgetsEn i18n = t.client_home.widgets;
|
||||
final Widget widgetContent = _buildWidgetContent(context);
|
||||
|
||||
if (isEditMode) {
|
||||
return DraggableWidgetWrapper(
|
||||
@@ -64,11 +64,11 @@ class DashboardWidgetBuilder extends StatelessWidget {
|
||||
);
|
||||
case 'reorder':
|
||||
return ReorderWidget(
|
||||
onReorderPressed: (data) {
|
||||
onReorderPressed: (Map<String, dynamic> data) {
|
||||
ClientHomeSheets.showOrderFormSheet(
|
||||
context,
|
||||
data,
|
||||
onSubmit: (submittedData) {
|
||||
onSubmit: (Map<String, dynamic> submittedData) {
|
||||
// Handle form submission if needed
|
||||
},
|
||||
);
|
||||
|
||||
@@ -34,9 +34,9 @@ class DraggableWidgetWrapper extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: UiConstants.space2,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space2,
|
||||
@@ -48,7 +48,7 @@ class DraggableWidgetWrapper extends StatelessWidget {
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.gripVertical,
|
||||
size: 14,
|
||||
|
||||
@@ -33,14 +33,14 @@ class HeaderIconButton extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? UiColors.primary : UiColors.white,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 2,
|
||||
|
||||
@@ -13,18 +13,18 @@ class LiveActivityWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_home;
|
||||
final TranslationsClientHomeEn i18n = t.client_home;
|
||||
|
||||
// Mock data
|
||||
final shifts = [
|
||||
{
|
||||
final List<Map<String, Object>> shifts = <Map<String, Object>>[
|
||||
<String, Object>{
|
||||
'workersNeeded': 5,
|
||||
'filled': 4,
|
||||
'hourlyRate': 20.0,
|
||||
'status': 'OPEN',
|
||||
'date': DateTime.now().toIso8601String().split('T')[0],
|
||||
},
|
||||
{
|
||||
<String, Object>{
|
||||
'workersNeeded': 5,
|
||||
'filled': 5,
|
||||
'hourlyRate': 22.0,
|
||||
@@ -32,18 +32,18 @@ class LiveActivityWidget extends StatelessWidget {
|
||||
'date': DateTime.now().toIso8601String().split('T')[0],
|
||||
},
|
||||
];
|
||||
final applications = [
|
||||
{'status': 'CONFIRMED', 'checkInTime': '09:00'},
|
||||
{'status': 'CONFIRMED', 'checkInTime': '09:05'},
|
||||
{'status': 'CONFIRMED'},
|
||||
{'status': 'LATE'},
|
||||
final List<Map<String, String>> applications = <Map<String, String>>[
|
||||
<String, String>{'status': 'CONFIRMED', 'checkInTime': '09:00'},
|
||||
<String, String>{'status': 'CONFIRMED', 'checkInTime': '09:05'},
|
||||
<String, String>{'status': 'CONFIRMED'},
|
||||
<String, String>{'status': 'LATE'},
|
||||
];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.widgets.live_activity.toUpperCase(),
|
||||
style: UiTypography.footnote1b.textSecondary.copyWith(
|
||||
|
||||
@@ -12,11 +12,11 @@ class ReorderWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_home.reorder;
|
||||
final TranslationsClientHomeReorderEn i18n = t.client_home.reorder;
|
||||
|
||||
// Mock recent orders
|
||||
final recentOrders = [
|
||||
{
|
||||
final List<Map<String, Object>> recentOrders = <Map<String, Object>>[
|
||||
<String, Object>{
|
||||
'title': 'Server',
|
||||
'location': 'Downtown Restaurant',
|
||||
'hourlyRate': 18.0,
|
||||
@@ -24,7 +24,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
'workers': 3,
|
||||
'type': 'One Day',
|
||||
},
|
||||
{
|
||||
<String, Object>{
|
||||
'title': 'Bartender',
|
||||
'location': 'Rooftop Bar',
|
||||
'hourlyRate': 22.0,
|
||||
@@ -32,7 +32,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
'workers': 2,
|
||||
'type': 'One Day',
|
||||
},
|
||||
{
|
||||
<String, Object>{
|
||||
'title': 'Event Staff',
|
||||
'location': 'Convention Center',
|
||||
'hourlyRate': 20.0,
|
||||
@@ -44,7 +44,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.title,
|
||||
style: UiTypography.footnote1b.textSecondary.copyWith(
|
||||
@@ -57,11 +57,11 @@ class ReorderWidget extends StatelessWidget {
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: recentOrders.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
itemBuilder: (context, index) {
|
||||
final order = recentOrders[index];
|
||||
final totalCost =
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final Map<String, Object> order = recentOrders[index];
|
||||
final double totalCost =
|
||||
(order['hourlyRate'] as double) *
|
||||
(order['hours'] as int) *
|
||||
(order['workers'] as int);
|
||||
@@ -73,7 +73,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 4,
|
||||
@@ -82,13 +82,13 @@ class ReorderWidget extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
@@ -108,7 +108,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
order['title'] as String,
|
||||
style: UiTypography.body2b,
|
||||
@@ -128,7 +128,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'\$${totalCost.toStringAsFixed(0)}',
|
||||
style: UiTypography.body1b,
|
||||
@@ -146,7 +146,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_Badge(
|
||||
icon: UiIcons.success,
|
||||
text: order['type'] as String,
|
||||
@@ -222,7 +222,7 @@ class _Badge extends StatelessWidget {
|
||||
decoration: BoxDecoration(color: bg, borderRadius: UiConstants.radiusSm),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 10, color: bg == textColor ? UiColors.white : color),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(text, style: UiTypography.footnote2b.copyWith(color: textColor)),
|
||||
|
||||
@@ -34,7 +34,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
|
||||
late List<Map<String, dynamic>> _positions;
|
||||
|
||||
final List<String> _roles = [
|
||||
final List<String> _roles = <String>[
|
||||
'Server',
|
||||
'Bartender',
|
||||
'Cook',
|
||||
@@ -48,11 +48,11 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
];
|
||||
|
||||
// Vendor options
|
||||
final List<Map<String, dynamic>> _vendors = [
|
||||
{
|
||||
final List<Map<String, dynamic>> _vendors = <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'id': 'v1',
|
||||
'name': 'Elite Staffing',
|
||||
'rates': {
|
||||
'rates': <String, double>{
|
||||
'Server': 25.0,
|
||||
'Bartender': 30.0,
|
||||
'Cook': 28.0,
|
||||
@@ -63,10 +63,10 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
'Event Staff': 19.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
<String, dynamic>{
|
||||
'id': 'v2',
|
||||
'name': 'Premier Workforce',
|
||||
'rates': {
|
||||
'rates': <String, double>{
|
||||
'Server': 22.0,
|
||||
'Bartender': 28.0,
|
||||
'Cook': 25.0,
|
||||
@@ -81,7 +81,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
|
||||
String? _selectedVendorId;
|
||||
|
||||
final List<int> _lunchBreakOptions = [0, 30, 45, 60];
|
||||
final List<int> _lunchBreakOptions = <int>[0, 30, 45, 60];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -427,7 +427,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
items: _vendors
|
||||
.map(
|
||||
(vendor) => DropdownMenuItem<String>(
|
||||
(Map<String, dynamic> vendor) => DropdownMenuItem<String>(
|
||||
value: vendor['id'],
|
||||
child: Text(vendor['name']),
|
||||
),
|
||||
@@ -565,7 +565,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
items: _roles,
|
||||
itemBuilder: (dynamic role) {
|
||||
final Map<String, dynamic>? vendor = _vendors.firstWhere(
|
||||
(v) => v['id'] == _selectedVendorId,
|
||||
(Map<String, dynamic> v) => v['id'] == _selectedVendorId,
|
||||
orElse: () => _vendors.first,
|
||||
);
|
||||
final Map<String, dynamic>? rates = vendor?['rates'] as Map<String, dynamic>?;
|
||||
|
||||
@@ -27,11 +27,11 @@ class SpendingWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = t.client_home;
|
||||
final TranslationsClientHomeEn i18n = t.client_home;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.widgets.spending.toUpperCase(),
|
||||
style: UiTypography.footnote1b.textSecondary.copyWith(
|
||||
@@ -43,12 +43,12 @@ class SpendingWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [UiColors.primary, Color(0xFF0830B8)],
|
||||
colors: <Color>[UiColors.primary, Color(0xFF0830B8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.primary.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
@@ -57,13 +57,13 @@ class SpendingWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'This Week',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 9),
|
||||
@@ -89,7 +89,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'Next 7 Days',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 9),
|
||||
@@ -122,7 +122,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
@@ -142,7 +142,7 @@ class SpendingWidget extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'💡 ' +
|
||||
i18n.dashboard.insight_lightbulb(amount: '180'),
|
||||
|
||||
@@ -17,7 +17,7 @@ export 'src/presentation/pages/client_hubs_page.dart';
|
||||
/// A [Module] for the client hubs feature.
|
||||
class ClientHubsModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [DataConnectModule()];
|
||||
List<Module> get imports => <Module>[DataConnectModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:firebase_data_connect/src/core/ref.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../domain/repositories/hub_repository_interface.dart';
|
||||
@@ -16,8 +17,8 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
|
||||
@override
|
||||
Future<List<domain.Hub>> getHubs() async {
|
||||
final business = await _getBusinessForCurrentUser();
|
||||
final teamId = await _getOrCreateTeamId(business);
|
||||
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser();
|
||||
final String teamId = await _getOrCreateTeamId(business);
|
||||
return _fetchHubsForTeam(teamId: teamId, businessId: business.id);
|
||||
}
|
||||
|
||||
@@ -26,11 +27,11 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
required String name,
|
||||
required String address,
|
||||
}) async {
|
||||
final business = await _getBusinessForCurrentUser();
|
||||
final teamId = await _getOrCreateTeamId(business);
|
||||
final city = business.city;
|
||||
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser();
|
||||
final String teamId = await _getOrCreateTeamId(business);
|
||||
final String? city = business.city;
|
||||
|
||||
final result = await _dataConnect
|
||||
final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables> result = await _dataConnect
|
||||
.createTeamHub(
|
||||
teamId: teamId,
|
||||
hubName: name,
|
||||
@@ -38,17 +39,17 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
)
|
||||
.city(city?.isNotEmpty == true ? city : '')
|
||||
.execute();
|
||||
final createdId = result.data?.teamHub_insert.id;
|
||||
final String? createdId = result.data?.teamHub_insert.id;
|
||||
if (createdId == null) {
|
||||
throw Exception('Hub creation failed.');
|
||||
}
|
||||
|
||||
final hubs = await _fetchHubsForTeam(
|
||||
final List<domain.Hub> hubs = await _fetchHubsForTeam(
|
||||
teamId: teamId,
|
||||
businessId: business.id,
|
||||
);
|
||||
domain.Hub? createdHub;
|
||||
for (final hub in hubs) {
|
||||
for (final domain.Hub hub in hubs) {
|
||||
if (hub.id == createdId) {
|
||||
createdHub = hub;
|
||||
break;
|
||||
@@ -79,8 +80,8 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
}
|
||||
|
||||
Future<dc.GetBusinessesByUserIdBusinesses> _getBusinessForCurrentUser() async {
|
||||
final session = dc.ClientSessionStore.instance.session;
|
||||
final cachedBusiness = session?.business;
|
||||
final dc.ClientSession? session = dc.ClientSessionStore.instance.session;
|
||||
final dc.ClientBusinessSession? cachedBusiness = session?.business;
|
||||
if (cachedBusiness != null) {
|
||||
return dc.GetBusinessesByUserIdBusinesses(
|
||||
id: cachedBusiness.id,
|
||||
@@ -103,12 +104,12 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
);
|
||||
}
|
||||
|
||||
final user = _firebaseAuth.currentUser;
|
||||
final firebase.User? user = _firebaseAuth.currentUser;
|
||||
if (user == null) {
|
||||
throw Exception('User is not authenticated.');
|
||||
}
|
||||
|
||||
final result = await _dataConnect.getBusinessesByUserId(
|
||||
final QueryResult<dc.GetBusinessesByUserIdData, dc.GetBusinessesByUserIdVariables> result = await _dataConnect.getBusinessesByUserId(
|
||||
userId: user.uid,
|
||||
).execute();
|
||||
if (result.data.businesses.isEmpty) {
|
||||
@@ -116,7 +117,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
throw Exception('No business found for this user. Please sign in again.');
|
||||
}
|
||||
|
||||
final business = result.data.businesses.first;
|
||||
final dc.GetBusinessesByUserIdBusinesses business = result.data.businesses.first;
|
||||
if (session != null) {
|
||||
dc.ClientSessionStore.instance.setSession(
|
||||
dc.ClientSession(
|
||||
@@ -140,14 +141,14 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
Future<String> _getOrCreateTeamId(
|
||||
dc.GetBusinessesByUserIdBusinesses business,
|
||||
) async {
|
||||
final teamsResult = await _dataConnect.getTeamsByOwnerId(
|
||||
final QueryResult<dc.GetTeamsByOwnerIdData, dc.GetTeamsByOwnerIdVariables> teamsResult = await _dataConnect.getTeamsByOwnerId(
|
||||
ownerId: business.id,
|
||||
).execute();
|
||||
if (teamsResult.data.teams.isNotEmpty) {
|
||||
return teamsResult.data.teams.first.id;
|
||||
}
|
||||
|
||||
final createTeamBuilder = _dataConnect.createTeam(
|
||||
final dc.CreateTeamVariablesBuilder createTeamBuilder = _dataConnect.createTeam(
|
||||
teamName: '${business.businessName} Team',
|
||||
ownerId: business.id,
|
||||
ownerName: business.contactName ?? '',
|
||||
@@ -157,8 +158,8 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
createTeamBuilder.email(business.email);
|
||||
}
|
||||
|
||||
final createTeamResult = await createTeamBuilder.execute();
|
||||
final teamId = createTeamResult.data?.team_insert.id;
|
||||
final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables> createTeamResult = await createTeamBuilder.execute();
|
||||
final String? teamId = createTeamResult.data?.team_insert.id;
|
||||
if (teamId == null) {
|
||||
throw Exception('Team creation failed.');
|
||||
}
|
||||
@@ -170,13 +171,13 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
required String teamId,
|
||||
required String businessId,
|
||||
}) async {
|
||||
final hubsResult = await _dataConnect.getTeamHubsByTeamId(
|
||||
final QueryResult<dc.GetTeamHubsByTeamIdData, dc.GetTeamHubsByTeamIdVariables> hubsResult = await _dataConnect.getTeamHubsByTeamId(
|
||||
teamId: teamId,
|
||||
).execute();
|
||||
|
||||
return hubsResult.data.teamHubs
|
||||
.map(
|
||||
(hub) => domain.Hub(
|
||||
(dc.GetTeamHubsByTeamIdTeamHubs hub) => domain.Hub(
|
||||
id: hub.id,
|
||||
businessId: businessId,
|
||||
name: hub.hubName,
|
||||
|
||||
@@ -16,5 +16,5 @@ class AssignNfcTagArguments extends UseCaseArgument {
|
||||
const AssignNfcTagArguments({required this.hubId, required this.nfcTagId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [hubId, nfcTagId];
|
||||
List<Object?> get props => <Object?>[hubId, nfcTagId];
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ class CreateHubArguments extends UseCaseArgument {
|
||||
const CreateHubArguments({required this.name, required this.address});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, address];
|
||||
List<Object?> get props => <Object?>[name, address];
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ class DeleteHubArguments extends UseCaseArgument {
|
||||
const DeleteHubArguments({required this.hubId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [hubId];
|
||||
List<Object?> get props => <Object?>[hubId];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/src/entities/business/hub.dart';
|
||||
import '../../domain/arguments/assign_nfc_tag_arguments.dart';
|
||||
import '../../domain/arguments/create_hub_arguments.dart';
|
||||
import '../../domain/arguments/delete_hub_arguments.dart';
|
||||
@@ -64,7 +65,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientHubsStatus.loading));
|
||||
try {
|
||||
final hubs = await _getHubsUseCase();
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs));
|
||||
} catch (e) {
|
||||
emit(
|
||||
@@ -85,7 +86,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
await _createHubUseCase(
|
||||
CreateHubArguments(name: event.name, address: event.address),
|
||||
);
|
||||
final hubs = await _getHubsUseCase();
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
@@ -111,7 +112,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
try {
|
||||
await _deleteHubUseCase(DeleteHubArguments(hubId: event.hubId));
|
||||
final hubs = await _getHubsUseCase();
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
@@ -138,7 +139,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
await _assignNfcTagUseCase(
|
||||
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),
|
||||
);
|
||||
final hubs = await _getHubsUseCase();
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
|
||||
@@ -6,7 +6,7 @@ abstract class ClientHubsEvent extends Equatable {
|
||||
const ClientHubsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
/// Event triggered to fetch the list of hubs.
|
||||
@@ -22,7 +22,7 @@ class ClientHubsAddRequested extends ClientHubsEvent {
|
||||
const ClientHubsAddRequested({required this.name, required this.address});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, address];
|
||||
List<Object?> get props => <Object?>[name, address];
|
||||
}
|
||||
|
||||
/// Event triggered to delete a hub.
|
||||
@@ -32,7 +32,7 @@ class ClientHubsDeleteRequested extends ClientHubsEvent {
|
||||
const ClientHubsDeleteRequested(this.hubId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [hubId];
|
||||
List<Object?> get props => <Object?>[hubId];
|
||||
}
|
||||
|
||||
/// Event triggered to assign an NFC tag to a hub.
|
||||
@@ -46,7 +46,7 @@ class ClientHubsNfcTagAssignRequested extends ClientHubsEvent {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [hubId, nfcTagId];
|
||||
List<Object?> get props => <Object?>[hubId, nfcTagId];
|
||||
}
|
||||
|
||||
/// Event triggered to clear any error or success messages.
|
||||
@@ -61,7 +61,7 @@ class ClientHubsAddDialogToggled extends ClientHubsEvent {
|
||||
const ClientHubsAddDialogToggled({required this.visible});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [visible];
|
||||
List<Object?> get props => <Object?>[visible];
|
||||
}
|
||||
|
||||
/// Event triggered to toggle the visibility of the "Identify NFC" dialog.
|
||||
@@ -71,5 +71,5 @@ class ClientHubsIdentifyDialogToggled extends ClientHubsEvent {
|
||||
const ClientHubsIdentifyDialogToggled({this.hub});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [hub];
|
||||
List<Object?> get props => <Object?>[hub];
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class ClientHubsState extends Equatable {
|
||||
|
||||
const ClientHubsState({
|
||||
this.status = ClientHubsStatus.initial,
|
||||
this.hubs = const [],
|
||||
this.hubs = const <Hub>[],
|
||||
this.errorMessage,
|
||||
this.successMessage,
|
||||
this.showAddHubDialog = false,
|
||||
@@ -57,7 +57,7 @@ class ClientHubsState extends Equatable {
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
List<Object?> get props => <Object?>[
|
||||
status,
|
||||
hubs,
|
||||
errorMessage,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:krow_domain/src/entities/business/hub.dart';
|
||||
import '../blocs/client_hubs_bloc.dart';
|
||||
import '../blocs/client_hubs_event.dart';
|
||||
import '../blocs/client_hubs_state.dart';
|
||||
@@ -23,10 +24,10 @@ class ClientHubsPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientHubsBloc>(
|
||||
create: (context) =>
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<ClientHubsBloc>()..add(const ClientHubsFetched()),
|
||||
child: BlocConsumer<ClientHubsBloc, ClientHubsState>(
|
||||
listener: (context, state) {
|
||||
listener: (BuildContext context, ClientHubsState state) {
|
||||
if (state.errorMessage != null) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
@@ -44,7 +45,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
).add(const ClientHubsMessageCleared());
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, ClientHubsState state) {
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
@@ -67,7 +68,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
vertical: UiConstants.space5,
|
||||
).copyWith(bottom: 100),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
delegate: SliverChildListDelegate(<Widget>[
|
||||
if (state.status == ClientHubsStatus.loading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (state.hubs.isEmpty)
|
||||
@@ -79,9 +80,9 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
else ...<Widget>[
|
||||
...state.hubs.map(
|
||||
(hub) => HubCard(
|
||||
(Hub hub) => HubCard(
|
||||
hub: hub,
|
||||
onNfcPressed: () =>
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
@@ -105,7 +106,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
if (state.showAddHubDialog)
|
||||
AddHubDialog(
|
||||
onCreate: (name, address) {
|
||||
onCreate: (String name, String address) {
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
ClientHubsAddRequested(name: name, address: address),
|
||||
);
|
||||
@@ -117,7 +118,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
if (state.hubToIdentify != null)
|
||||
IdentifyNfcDialog(
|
||||
hub: state.hubToIdentify!,
|
||||
onAssign: (tagId) {
|
||||
onAssign: (String tagId) {
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
ClientHubsNfcTagAssignRequested(
|
||||
hubId: state.hubToIdentify!.id,
|
||||
@@ -159,7 +160,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Modular.to.pop(),
|
||||
child: Container(
|
||||
|
||||
@@ -51,14 +51,14 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(color: UiColors.popupShadow, blurRadius: 20),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_hubs.add_hub_dialog.title,
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
@@ -83,7 +83,7 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiButton.secondary(
|
||||
onPressed: widget.onCancel,
|
||||
|
||||
@@ -31,7 +31,7 @@ class HubCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.popupShadow,
|
||||
blurRadius: 10,
|
||||
@@ -42,7 +42,7 @@ class HubCard extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
@@ -60,13 +60,13 @@ class HubCard extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(hub.name, style: UiTypography.body1b.textPrimary),
|
||||
if (hub.address.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: UiConstants.space1),
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.mapPin,
|
||||
size: 12,
|
||||
@@ -99,7 +99,7 @@ class HubCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: onDeletePressed,
|
||||
icon: const Icon(
|
||||
|
||||
@@ -17,7 +17,7 @@ class HubEmptyState extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgBanner,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.popupShadow,
|
||||
blurRadius: 10,
|
||||
@@ -26,7 +26,7 @@ class HubEmptyState extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
|
||||
@@ -17,13 +17,13 @@ class HubInfoCard extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.nfc, size: 20, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_hubs.about_hubs.title,
|
||||
style: UiTypography.body2b.textPrimary,
|
||||
|
||||
@@ -48,13 +48,13 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const [
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(color: UiColors.popupShadow, blurRadius: 20),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_hubs.nfc_dialog.title,
|
||||
style: UiTypography.headline3m.textPrimary,
|
||||
@@ -87,7 +87,7 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
text: t.client_hubs.nfc_dialog.scan_button,
|
||||
leadingIcon: UiIcons.nfc,
|
||||
),
|
||||
if (_nfcTagId != null) ...[
|
||||
if (_nfcTagId != null) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
@@ -98,10 +98,10 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.success,
|
||||
size: 20,
|
||||
@@ -141,7 +141,7 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
],
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiButton.secondary(
|
||||
onPressed: widget.onCancel,
|
||||
|
||||
@@ -25,7 +25,7 @@ class ClientSettingsModule extends Module {
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(r) {
|
||||
void routes(RouteManager r) {
|
||||
r.child('/', child: (_) => const ClientSettingsPage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ abstract class ClientSettingsEvent extends Equatable {
|
||||
const ClientSettingsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class ClientSettingsSignOutRequested extends ClientSettingsEvent {
|
||||
|
||||
@@ -4,7 +4,7 @@ abstract class ClientSettingsState extends Equatable {
|
||||
const ClientSettingsState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class ClientSettingsInitial extends ClientSettingsState {
|
||||
@@ -25,5 +25,5 @@ class ClientSettingsError extends ClientSettingsState {
|
||||
const ClientSettingsError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
@override
|
||||
/// Builds the profile header UI.
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
final session = dc.ClientSessionStore.instance.session;
|
||||
final businessName =
|
||||
final TranslationsClientSettingsProfileEn labels = t.client_settings.profile;
|
||||
final dc.ClientSession? session = dc.ClientSessionStore.instance.session;
|
||||
final String businessName =
|
||||
session?.business?.businessName ?? 'Your Company';
|
||||
final email = session?.user.email ?? 'client@example.com';
|
||||
final photoUrl = session?.userPhotoUrl;
|
||||
final avatarLetter = businessName.trim().isNotEmpty
|
||||
final String email = session?.user.email ?? 'client@example.com';
|
||||
final String? photoUrl = session?.userPhotoUrl;
|
||||
final String avatarLetter = businessName.trim().isNotEmpty
|
||||
? businessName.trim()[0].toUpperCase()
|
||||
: 'C';
|
||||
|
||||
@@ -40,7 +40,7 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: UiConstants.space4,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
@@ -69,13 +69,13 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(businessName, style: UiTypography.body1b.textPrimary),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: UiConstants.space1,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
UiIcons.mail,
|
||||
size: 14,
|
||||
|
||||
@@ -12,7 +12,7 @@ class SettingsQuickLinks extends StatelessWidget {
|
||||
@override
|
||||
/// Builds the quick links UI.
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
final TranslationsClientSettingsProfileEn labels = t.client_settings.profile;
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
@@ -28,7 +28,7 @@ class SettingsQuickLinks extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
labels.quick_links,
|
||||
style: UiTypography.footnote1b.textPrimary,
|
||||
@@ -85,9 +85,9 @@ class _QuickLinkItem extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 20, color: UiColors.iconSecondary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Text(title, style: UiTypography.footnote1m.textPrimary),
|
||||
|
||||
@@ -21,15 +21,15 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
}) async {
|
||||
final businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
if (businessId == null || businessId.isEmpty) {
|
||||
await _firebaseAuth.signOut();
|
||||
throw Exception('Business is missing. Please sign in again.');
|
||||
}
|
||||
|
||||
final startTimestamp = _toTimestamp(_startOfDay(start));
|
||||
final endTimestamp = _toTimestamp(_endOfDay(end));
|
||||
final result = await _dataConnect
|
||||
final fdc.Timestamp startTimestamp = _toTimestamp(_startOfDay(start));
|
||||
final fdc.Timestamp endTimestamp = _toTimestamp(_endOfDay(end));
|
||||
final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData, dc.ListShiftRolesByBusinessAndDateRangeVariables> result = await _dataConnect
|
||||
.listShiftRolesByBusinessAndDateRange(
|
||||
businessId: businessId,
|
||||
start: startTimestamp,
|
||||
@@ -37,28 +37,28 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
)
|
||||
.execute();
|
||||
|
||||
final businessName =
|
||||
final String businessName =
|
||||
dc.ClientSessionStore.instance.session?.business?.businessName ??
|
||||
'Your Company';
|
||||
|
||||
return result.data.shiftRoles.map((shiftRole) {
|
||||
return result.data.shiftRoles.map((dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole) {
|
||||
print(
|
||||
'ViewOrders shiftRole: shiftId=${shiftRole.shiftId} roleId=${shiftRole.roleId} '
|
||||
'startTime=${shiftRole.startTime?.toJson()} endTime=${shiftRole.endTime?.toJson()} '
|
||||
'hours=${shiftRole.hours} totalValue=${shiftRole.totalValue}',
|
||||
);
|
||||
final shiftDate = shiftRole.shift.date?.toDateTime();
|
||||
final dateStr = shiftDate == null
|
||||
final DateTime? shiftDate = shiftRole.shift.date?.toDateTime();
|
||||
final String dateStr = shiftDate == null
|
||||
? ''
|
||||
: DateFormat('yyyy-MM-dd').format(shiftDate);
|
||||
final startTime = _formatTime(shiftRole.startTime);
|
||||
final endTime = _formatTime(shiftRole.endTime);
|
||||
final filled = shiftRole.assigned ?? 0;
|
||||
final workersNeeded = shiftRole.count;
|
||||
final hours = shiftRole.hours ?? 0;
|
||||
final totalValue = shiftRole.totalValue ?? 0;
|
||||
final hourlyRate = _hourlyRate(shiftRole.totalValue, shiftRole.hours);
|
||||
final status = filled >= workersNeeded ? 'filled' : 'open';
|
||||
final String startTime = _formatTime(shiftRole.startTime);
|
||||
final String endTime = _formatTime(shiftRole.endTime);
|
||||
final int filled = shiftRole.assigned ?? 0;
|
||||
final int workersNeeded = shiftRole.count;
|
||||
final double hours = shiftRole.hours ?? 0;
|
||||
final double totalValue = shiftRole.totalValue ?? 0;
|
||||
final double hourlyRate = _hourlyRate(shiftRole.totalValue, shiftRole.hours);
|
||||
final String status = filled >= workersNeeded ? 'filled' : 'open';
|
||||
|
||||
return domain.OrderItem(
|
||||
id: _shiftRoleKey(shiftRole.shiftId, shiftRole.roleId),
|
||||
@@ -84,15 +84,15 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
Future<Map<String, List<Map<String, dynamic>>>> getAcceptedApplicationsForDay(
|
||||
DateTime day,
|
||||
) async {
|
||||
final businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
|
||||
if (businessId == null || businessId.isEmpty) {
|
||||
await _firebaseAuth.signOut();
|
||||
throw Exception('Business is missing. Please sign in again.');
|
||||
}
|
||||
|
||||
final dayStart = _toTimestamp(_startOfDay(day));
|
||||
final dayEnd = _toTimestamp(_endOfDay(day));
|
||||
final result = await _dataConnect
|
||||
final fdc.Timestamp dayStart = _toTimestamp(_startOfDay(day));
|
||||
final fdc.Timestamp dayEnd = _toTimestamp(_endOfDay(day));
|
||||
final fdc.QueryResult<dc.ListAcceptedApplicationsByBusinessForDayData, dc.ListAcceptedApplicationsByBusinessForDayVariables> result = await _dataConnect
|
||||
.listAcceptedApplicationsByBusinessForDay(
|
||||
businessId: businessId,
|
||||
dayStart: dayStart,
|
||||
@@ -100,13 +100,13 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
)
|
||||
.execute();
|
||||
|
||||
final Map<String, List<Map<String, dynamic>>> grouped = {};
|
||||
for (final application in result.data.applications) {
|
||||
final Map<String, List<Map<String, dynamic>>> grouped = <String, List<Map<String, dynamic>>>{};
|
||||
for (final dc.ListAcceptedApplicationsByBusinessForDayApplications application in result.data.applications) {
|
||||
print(
|
||||
'ViewOrders app: shiftId=${application.shiftId} roleId=${application.roleId} '
|
||||
'checkIn=${application.checkInTime?.toJson()} checkOut=${application.checkOutTime?.toJson()}',
|
||||
);
|
||||
final key = _shiftRoleKey(application.shiftId, application.roleId);
|
||||
final String key = _shiftRoleKey(application.shiftId, application.roleId);
|
||||
grouped.putIfAbsent(key, () => <Map<String, dynamic>>[]);
|
||||
grouped[key]!.add(<String, dynamic>{
|
||||
'id': application.id,
|
||||
@@ -124,9 +124,9 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
}
|
||||
|
||||
fdc.Timestamp _toTimestamp(DateTime dateTime) {
|
||||
final utc = dateTime.toUtc();
|
||||
final seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
final DateTime utc = dateTime.toUtc();
|
||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
return fdc.Timestamp(nanoseconds, seconds);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
if (timestamp == null) {
|
||||
return '';
|
||||
}
|
||||
final dateTime = timestamp.toDateTime();
|
||||
final DateTime dateTime = timestamp.toDateTime();
|
||||
return DateFormat('HH:mm').format(dateTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
final List<OrderItem> orders = await _getOrdersUseCase(
|
||||
OrdersRangeArguments(start: rangeStart, end: rangeEnd),
|
||||
);
|
||||
final apps = await _getAcceptedAppsUseCase(
|
||||
final Map<String, List<Map<String, dynamic>>> apps = await _getAcceptedAppsUseCase(
|
||||
OrdersDayArguments(day: dayForApps),
|
||||
);
|
||||
final List<OrderItem> updatedOrders = _applyApplications(orders, apps);
|
||||
@@ -70,7 +70,7 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
final DateTime? selectedDate = state.selectedDate;
|
||||
final DateTime updatedSelectedDate =
|
||||
selectedDate != null &&
|
||||
calendarDays.any((day) => _isSameDay(day, selectedDate))
|
||||
calendarDays.any((DateTime day) => _isSameDay(day, selectedDate))
|
||||
? selectedDate
|
||||
: calendarDays.first;
|
||||
emit(
|
||||
@@ -106,7 +106,7 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
|
||||
Future<void> _refreshAcceptedApplications(DateTime day) async {
|
||||
try {
|
||||
final apps = await _getAcceptedAppsUseCase(
|
||||
final Map<String, List<Map<String, dynamic>>> apps = await _getAcceptedAppsUseCase(
|
||||
OrdersDayArguments(day: day),
|
||||
);
|
||||
final List<OrderItem> updatedOrders =
|
||||
@@ -122,14 +122,14 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
|
||||
List<OrderItem> orders,
|
||||
Map<String, List<Map<String, dynamic>>> apps,
|
||||
) {
|
||||
return orders.map((order) {
|
||||
final confirmed = apps[order.id] ?? const <Map<String, dynamic>>[];
|
||||
return orders.map((OrderItem order) {
|
||||
final List<Map<String, dynamic>> confirmed = apps[order.id] ?? const <Map<String, dynamic>>[];
|
||||
if (confirmed.isEmpty) {
|
||||
return order;
|
||||
}
|
||||
|
||||
final filled = confirmed.length;
|
||||
final status =
|
||||
final int filled = confirmed.length;
|
||||
final String status =
|
||||
filled >= order.workersNeeded ? 'filled' : order.status;
|
||||
return OrderItem(
|
||||
id: order.id,
|
||||
|
||||
Reference in New Issue
Block a user