feat: introduce TaxForm entity and adapter, refactor tax forms repository and use cases for improved data handling
This commit is contained in:
@@ -54,6 +54,7 @@ export 'src/entities/profile/staff_document.dart';
|
|||||||
export 'src/entities/profile/attire_item.dart';
|
export 'src/entities/profile/attire_item.dart';
|
||||||
export 'src/entities/profile/relationship_type.dart';
|
export 'src/entities/profile/relationship_type.dart';
|
||||||
export 'src/entities/profile/industry.dart';
|
export 'src/entities/profile/industry.dart';
|
||||||
|
export 'src/entities/profile/tax_form.dart';
|
||||||
|
|
||||||
// Ratings & Penalties
|
// Ratings & Penalties
|
||||||
export 'src/entities/ratings/staff_rating.dart';
|
export 'src/entities/ratings/staff_rating.dart';
|
||||||
@@ -85,3 +86,4 @@ export 'src/adapters/profile/emergency_contact_adapter.dart';
|
|||||||
export 'src/adapters/profile/experience_adapter.dart';
|
export 'src/adapters/profile/experience_adapter.dart';
|
||||||
export 'src/entities/profile/experience_skill.dart';
|
export 'src/entities/profile/experience_skill.dart';
|
||||||
export 'src/adapters/profile/bank_account_adapter.dart';
|
export 'src/adapters/profile/bank_account_adapter.dart';
|
||||||
|
export 'src/adapters/profile/tax_form_adapter.dart';
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import '../../entities/profile/tax_form.dart';
|
||||||
|
|
||||||
|
/// Adapter for [TaxForm] to map data layer values to domain entity.
|
||||||
|
class TaxFormAdapter {
|
||||||
|
/// Maps primitive values to [TaxForm].
|
||||||
|
static TaxForm fromPrimitives({
|
||||||
|
required String id,
|
||||||
|
required String type,
|
||||||
|
required String title,
|
||||||
|
String? subtitle,
|
||||||
|
String? description,
|
||||||
|
required String status,
|
||||||
|
String? staffId,
|
||||||
|
dynamic formData,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
}) {
|
||||||
|
return TaxForm(
|
||||||
|
id: id,
|
||||||
|
type: _stringToType(type),
|
||||||
|
title: title,
|
||||||
|
subtitle: subtitle,
|
||||||
|
description: description,
|
||||||
|
status: _stringToStatus(status),
|
||||||
|
staffId: staffId,
|
||||||
|
formData: formData is Map ? Map<String, dynamic>.from(formData) : null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TaxFormType _stringToType(String? value) {
|
||||||
|
if (value == null) return TaxFormType.i9;
|
||||||
|
try {
|
||||||
|
return TaxFormType.values.firstWhere(
|
||||||
|
(TaxFormType e) => e.name.toLowerCase() == value.toLowerCase(),
|
||||||
|
orElse: () => TaxFormType.i9,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return TaxFormType.i9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TaxFormStatus _stringToStatus(String? value) {
|
||||||
|
if (value == null) return TaxFormStatus.notStarted;
|
||||||
|
try {
|
||||||
|
final String normalizedValue = value.replaceAll('_', '').toLowerCase();
|
||||||
|
// map DRAFT to inProgress
|
||||||
|
if (normalizedValue == 'draft') return TaxFormStatus.inProgress;
|
||||||
|
|
||||||
|
return TaxFormStatus.values.firstWhere(
|
||||||
|
(TaxFormStatus e) {
|
||||||
|
// Handle differences like not_started vs notStarted if any,
|
||||||
|
// but standardizing to lowercase is a good start.
|
||||||
|
// The enum names are camelCase in Dart, but might be SNAKE_CASE from backend.
|
||||||
|
final String normalizedEnum = e.name.toLowerCase();
|
||||||
|
return normalizedValue == normalizedEnum;
|
||||||
|
},
|
||||||
|
orElse: () => TaxFormStatus.notStarted,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return TaxFormStatus.notStarted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts domain [TaxFormType] to string for backend.
|
||||||
|
static String typeToString(TaxFormType type) {
|
||||||
|
return type.name.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts domain [TaxFormStatus] to string for backend.
|
||||||
|
static String statusToString(TaxFormStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case TaxFormStatus.notStarted:
|
||||||
|
return 'NOT_STARTED';
|
||||||
|
case TaxFormStatus.inProgress:
|
||||||
|
return 'DRAFT';
|
||||||
|
case TaxFormStatus.submitted:
|
||||||
|
return 'SUBMITTED';
|
||||||
|
case TaxFormStatus.approved:
|
||||||
|
return 'APPROVED';
|
||||||
|
case TaxFormStatus.rejected:
|
||||||
|
return 'REJECTED';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
enum TaxFormType { i9, w4 }
|
||||||
|
|
||||||
|
enum TaxFormStatus { notStarted, inProgress, submitted, approved, rejected }
|
||||||
|
|
||||||
|
class TaxForm extends Equatable {
|
||||||
|
final String id;
|
||||||
|
final TaxFormType type;
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
final String? description;
|
||||||
|
final TaxFormStatus status;
|
||||||
|
final String? staffId;
|
||||||
|
final Map<String, dynamic>? formData;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
|
const TaxForm({
|
||||||
|
required this.id,
|
||||||
|
required this.type,
|
||||||
|
required this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.description,
|
||||||
|
this.status = TaxFormStatus.notStarted,
|
||||||
|
this.staffId,
|
||||||
|
this.formData,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
description,
|
||||||
|
status,
|
||||||
|
staffId,
|
||||||
|
formData,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart' hide User;
|
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../../domain/entities/tax_form_entity.dart';
|
|
||||||
import '../../domain/repositories/tax_forms_repository.dart';
|
import '../../domain/repositories/tax_forms_repository.dart';
|
||||||
|
|
||||||
class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
||||||
@@ -13,42 +13,34 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
required this.dataConnect,
|
required this.dataConnect,
|
||||||
});
|
});
|
||||||
|
|
||||||
final FirebaseAuth firebaseAuth;
|
final auth.FirebaseAuth firebaseAuth;
|
||||||
final dc.ExampleConnector dataConnect;
|
final dc.ExampleConnector dataConnect;
|
||||||
|
|
||||||
String? _staffId;
|
/// Helper to get the logged-in staff ID.
|
||||||
|
String _getStaffId() {
|
||||||
Future<String> _getStaffId() async {
|
final auth.User? user = firebaseAuth.currentUser;
|
||||||
if (_staffId != null) return _staffId!;
|
|
||||||
|
|
||||||
final user = firebaseAuth.currentUser;
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw Exception('User not logged in');
|
throw Exception('User not authenticated');
|
||||||
}
|
}
|
||||||
|
|
||||||
final result =
|
final String? staffId = dc.StaffSessionStore.instance.session?.staff?.id;
|
||||||
await dataConnect.getStaffByUserId(userId: user.uid).execute();
|
if (staffId == null || staffId.isEmpty) {
|
||||||
final staffs = result.data.staffs;
|
throw Exception('Staff profile is missing or session not initialized.');
|
||||||
if (staffs.isEmpty) {
|
|
||||||
// This might happen if the user is logged in but staff profile isn't created yet or not synced.
|
|
||||||
// For now, fail hard. Code can be improved to handle this case properly.
|
|
||||||
throw Exception('No staff profile found for user');
|
|
||||||
}
|
}
|
||||||
|
return staffId;
|
||||||
_staffId = staffs.first.id;
|
|
||||||
return _staffId!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<TaxFormEntity>> getTaxForms() async {
|
Future<List<TaxForm>> getTaxForms() async {
|
||||||
final staffId = await _getStaffId();
|
final String staffId = _getStaffId();
|
||||||
final result =
|
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
|
||||||
|
result =
|
||||||
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
|
|
||||||
final forms = result.data.taxForms.map((e) => _mapToEntity(e)).toList();
|
final List<TaxForm> forms = result.data.taxForms.map((dc.GetTaxFormsBystaffIdTaxForms e) => _mapToEntity(e)).toList();
|
||||||
|
|
||||||
// Check if required forms exist, create if not.
|
// Check if required forms exist, create if not.
|
||||||
final typesPresent = forms.map((f) => f.type).toSet();
|
final Set<TaxFormType> typesPresent = forms.map((TaxForm f) => f.type).toSet();
|
||||||
bool createdNew = false;
|
bool createdNew = false;
|
||||||
|
|
||||||
if (!typesPresent.contains(TaxFormType.i9)) {
|
if (!typesPresent.contains(TaxFormType.i9)) {
|
||||||
@@ -61,9 +53,10 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (createdNew) {
|
if (createdNew) {
|
||||||
final result2 =
|
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
|
||||||
|
result2 =
|
||||||
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
return result2.data.taxForms.map((e) => _mapToEntity(e)).toList();
|
return result2.data.taxForms.map((dc.GetTaxFormsBystaffIdTaxForms e) => _mapToEntity(e)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return forms;
|
return forms;
|
||||||
@@ -87,7 +80,7 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
await dataConnect
|
await dataConnect
|
||||||
.createTaxForm(
|
.createTaxForm(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
formType: _mapTypeToGenerated(type),
|
formType: dc.TaxFormType.values.byName(TaxFormAdapter.typeToString(type)),
|
||||||
title: title,
|
title: title,
|
||||||
)
|
)
|
||||||
.subtitle(subtitle)
|
.subtitle(subtitle)
|
||||||
@@ -98,13 +91,14 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data) async {
|
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data) async {
|
||||||
final staffId = await _getStaffId();
|
final String staffId = _getStaffId();
|
||||||
final result =
|
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
|
||||||
|
result =
|
||||||
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
final targetTypeString = _mapTypeToGenerated(type).name;
|
final String targetTypeString = TaxFormAdapter.typeToString(type);
|
||||||
|
|
||||||
final form = result.data.taxForms.firstWhere(
|
final dc.GetTaxFormsBystaffIdTaxForms form = result.data.taxForms.firstWhere(
|
||||||
(e) => e.formType.stringValue == targetTypeString,
|
(dc.GetTaxFormsBystaffIdTaxForms e) => e.formType.stringValue == targetTypeString,
|
||||||
orElse: () => throw Exception('Form not found for submission'),
|
orElse: () => throw Exception('Form not found for submission'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -120,13 +114,14 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status) async {
|
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status) async {
|
||||||
final staffId = await _getStaffId();
|
final String staffId = _getStaffId();
|
||||||
final result =
|
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
|
||||||
|
result =
|
||||||
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
final targetTypeString = _mapTypeToGenerated(type).name;
|
final String targetTypeString = TaxFormAdapter.typeToString(type);
|
||||||
|
|
||||||
final form = result.data.taxForms.firstWhere(
|
final dc.GetTaxFormsBystaffIdTaxForms form = result.data.taxForms.firstWhere(
|
||||||
(e) => e.formType.stringValue == targetTypeString,
|
(dc.GetTaxFormsBystaffIdTaxForms e) => e.formType.stringValue == targetTypeString,
|
||||||
orElse: () => throw Exception('Form not found for update'),
|
orElse: () => throw Exception('Form not found for update'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -134,73 +129,22 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
.updateTaxForm(
|
.updateTaxForm(
|
||||||
id: form.id,
|
id: form.id,
|
||||||
)
|
)
|
||||||
.status(_mapStatusToGenerated(status))
|
.status(dc.TaxFormStatus.values.byName(TaxFormAdapter.statusToString(status)))
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
TaxFormEntity _mapToEntity(dc.GetTaxFormsBystaffIdTaxForms form) {
|
TaxForm _mapToEntity(dc.GetTaxFormsBystaffIdTaxForms form) {
|
||||||
return TaxFormEntity(
|
return TaxFormAdapter.fromPrimitives(
|
||||||
type: _mapTypeFromString(form.formType.stringValue),
|
id: form.id,
|
||||||
|
type: form.formType.stringValue,
|
||||||
title: form.title,
|
title: form.title,
|
||||||
subtitle: form.subtitle ?? '',
|
subtitle: form.subtitle,
|
||||||
description: form.description ?? '',
|
description: form.description,
|
||||||
status: _mapStatusFromString(form.status.stringValue),
|
status: form.status.stringValue,
|
||||||
lastUpdated: form.updatedAt?.toDateTime(),
|
staffId: form.staffId,
|
||||||
|
formData: form.formData, // Adapter expects dynamic
|
||||||
|
updatedAt: form.updatedAt?.toDateTime(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TaxFormType _mapTypeFromString(String value) {
|
|
||||||
switch (value) {
|
|
||||||
case 'I9':
|
|
||||||
return TaxFormType.i9;
|
|
||||||
case 'W4':
|
|
||||||
return TaxFormType.w4;
|
|
||||||
default:
|
|
||||||
// Handle unexpected types gracefully if needed, or throw.
|
|
||||||
// Assuming database integrity for now.
|
|
||||||
if (value == 'I-9') return TaxFormType.i9; // Fallback just in case
|
|
||||||
throw Exception('Unknown TaxFormType string: $value');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TaxFormStatus _mapStatusFromString(String value) {
|
|
||||||
switch (value) {
|
|
||||||
case 'NOT_STARTED':
|
|
||||||
return TaxFormStatus.notStarted;
|
|
||||||
case 'DRAFT':
|
|
||||||
return TaxFormStatus.inProgress;
|
|
||||||
case 'SUBMITTED':
|
|
||||||
return TaxFormStatus.submitted;
|
|
||||||
case 'APPROVED':
|
|
||||||
return TaxFormStatus.approved;
|
|
||||||
case 'REJECTED':
|
|
||||||
return TaxFormStatus.rejected;
|
|
||||||
default:
|
|
||||||
return TaxFormStatus.notStarted; // Default fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.TaxFormType _mapTypeToGenerated(TaxFormType type) {
|
|
||||||
switch (type) {
|
|
||||||
case TaxFormType.i9:
|
|
||||||
return dc.TaxFormType.I9;
|
|
||||||
case TaxFormType.w4:
|
|
||||||
return dc.TaxFormType.W4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.TaxFormStatus _mapStatusToGenerated(TaxFormStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case TaxFormStatus.notStarted:
|
|
||||||
return dc.TaxFormStatus.NOT_STARTED;
|
|
||||||
case TaxFormStatus.inProgress:
|
|
||||||
return dc.TaxFormStatus.DRAFT;
|
|
||||||
case TaxFormStatus.submitted:
|
|
||||||
return dc.TaxFormStatus.SUBMITTED;
|
|
||||||
case TaxFormStatus.approved:
|
|
||||||
return dc.TaxFormStatus.APPROVED;
|
|
||||||
case TaxFormStatus.rejected:
|
|
||||||
return dc.TaxFormStatus.REJECTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import '../entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
abstract class TaxFormsRepository {
|
abstract class TaxFormsRepository {
|
||||||
Future<List<TaxFormEntity>> getTaxForms();
|
Future<List<TaxForm>> getTaxForms();
|
||||||
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data);
|
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data);
|
||||||
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status);
|
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import '../entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../repositories/tax_forms_repository.dart';
|
import '../repositories/tax_forms_repository.dart';
|
||||||
|
|
||||||
class GetTaxFormsUseCase {
|
class GetTaxFormsUseCase {
|
||||||
@@ -6,7 +6,7 @@ class GetTaxFormsUseCase {
|
|||||||
|
|
||||||
GetTaxFormsUseCase(this._repository);
|
GetTaxFormsUseCase(this._repository);
|
||||||
|
|
||||||
Future<List<TaxFormEntity>> call() async {
|
Future<List<TaxForm>> call() async {
|
||||||
return _repository.getTaxForms();
|
return _repository.getTaxForms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import '../entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../repositories/tax_forms_repository.dart';
|
import '../repositories/tax_forms_repository.dart';
|
||||||
|
|
||||||
class SubmitTaxFormUseCase {
|
class SubmitTaxFormUseCase {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../../../domain/usecases/submit_tax_form_usecase.dart';
|
import '../../../domain/usecases/submit_tax_form_usecase.dart';
|
||||||
import 'form_i9_state.dart';
|
import 'form_i9_state.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../../domain/usecases/get_tax_forms_usecase.dart';
|
import '../../../domain/usecases/get_tax_forms_usecase.dart';
|
||||||
import 'tax_forms_state.dart';
|
import 'tax_forms_state.dart';
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ class TaxFormsCubit extends Cubit<TaxFormsState> {
|
|||||||
Future<void> loadTaxForms() async {
|
Future<void> loadTaxForms() async {
|
||||||
emit(state.copyWith(status: TaxFormsStatus.loading));
|
emit(state.copyWith(status: TaxFormsStatus.loading));
|
||||||
try {
|
try {
|
||||||
final List<TaxFormEntity> forms = await _getTaxFormsUseCase();
|
final List<TaxForm> forms = await _getTaxFormsUseCase();
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
status: TaxFormsStatus.success,
|
status: TaxFormsStatus.success,
|
||||||
forms: forms,
|
forms: forms,
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
enum TaxFormsStatus { initial, loading, success, failure }
|
enum TaxFormsStatus { initial, loading, success, failure }
|
||||||
|
|
||||||
class TaxFormsState extends Equatable {
|
class TaxFormsState extends Equatable {
|
||||||
final TaxFormsStatus status;
|
final TaxFormsStatus status;
|
||||||
final List<TaxFormEntity> forms;
|
final List<TaxForm> forms;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const TaxFormsState({
|
const TaxFormsState({
|
||||||
this.status = TaxFormsStatus.initial,
|
this.status = TaxFormsStatus.initial,
|
||||||
this.forms = const <TaxFormEntity>[],
|
this.forms = const <TaxForm>[],
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
TaxFormsState copyWith({
|
TaxFormsState copyWith({
|
||||||
TaxFormsStatus? status,
|
TaxFormsStatus? status,
|
||||||
List<TaxFormEntity>? forms,
|
List<TaxForm>? forms,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return TaxFormsState(
|
return TaxFormsState(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../../../domain/usecases/submit_tax_form_usecase.dart';
|
import '../../../domain/usecases/submit_tax_form_usecase.dart';
|
||||||
import 'form_w4_state.dart';
|
import 'form_w4_state.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import '../../domain/entities/tax_form_entity.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../blocs/tax_forms/tax_forms_cubit.dart';
|
import '../blocs/tax_forms/tax_forms_cubit.dart';
|
||||||
import '../blocs/tax_forms/tax_forms_state.dart';
|
import '../blocs/tax_forms/tax_forms_state.dart';
|
||||||
|
|
||||||
@@ -11,13 +11,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TaxFormsCubit cubit = Modular.get<TaxFormsCubit>();
|
|
||||||
|
|
||||||
if (cubit.state.status == TaxFormsStatus.initial) {
|
|
||||||
cubit.loadTaxForms();
|
|
||||||
}
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.background,
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: UiColors.primary,
|
backgroundColor: UiColors.primary,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@@ -27,9 +21,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Tax Documents',
|
'Tax Documents',
|
||||||
style: UiTypography.headline3m.copyWith(
|
style: UiTypography.headline3m.copyWith(color: UiColors.bgPopup),
|
||||||
color: UiColors.bgPopup,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(24),
|
preferredSize: const Size.fromHeight(24),
|
||||||
@@ -54,40 +46,53 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: BlocBuilder<TaxFormsCubit, TaxFormsState>(
|
body: BlocProvider<TaxFormsCubit>(
|
||||||
bloc: Modular.get<TaxFormsCubit>(),
|
create: (BuildContext context) {
|
||||||
builder: (BuildContext context, TaxFormsState state) {
|
final TaxFormsCubit cubit = Modular.get<TaxFormsCubit>();
|
||||||
if (state.status == TaxFormsStatus.loading) {
|
if (cubit.state.status == TaxFormsStatus.initial) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
cubit.loadTaxForms();
|
||||||
}
|
}
|
||||||
|
return cubit;
|
||||||
if (state.status == TaxFormsStatus.failure) {
|
|
||||||
return Center(child: Text(state.errorMessage ?? 'Error loading forms'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space5,
|
|
||||||
vertical: UiConstants.space6,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
_buildProgressOverview(state.forms),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
...state.forms.map(_buildFormCard),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
_buildInfoCard(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
child: BlocBuilder<TaxFormsCubit, TaxFormsState>(
|
||||||
|
builder: (BuildContext context, TaxFormsState state) {
|
||||||
|
if (state.status == TaxFormsStatus.loading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.status == TaxFormsStatus.failure) {
|
||||||
|
return Center(
|
||||||
|
child: Text(state.errorMessage ?? 'Error loading forms'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space5,
|
||||||
|
vertical: UiConstants.space6,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: UiConstants.space6,
|
||||||
|
children: <Widget>[
|
||||||
|
_buildProgressOverview(state.forms),
|
||||||
|
...state.forms.map(_buildFormCard),
|
||||||
|
_buildInfoCard(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProgressOverview(List<TaxFormEntity> forms) {
|
Widget _buildProgressOverview(List<TaxForm> forms) {
|
||||||
final int completedCount = forms
|
final int completedCount = forms
|
||||||
.where((TaxFormEntity f) => f.status == TaxFormStatus.submitted || f.status == TaxFormStatus.approved)
|
.where(
|
||||||
|
(TaxForm f) =>
|
||||||
|
f.status == TaxFormStatus.submitted ||
|
||||||
|
f.status == TaxFormStatus.approved,
|
||||||
|
)
|
||||||
.length;
|
.length;
|
||||||
final int totalCount = forms.length;
|
final int totalCount = forms.length;
|
||||||
final double progress = totalCount > 0 ? completedCount / totalCount : 0.0;
|
final double progress = totalCount > 0 ? completedCount / totalCount : 0.0;
|
||||||
@@ -112,8 +117,9 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'$completedCount/$totalCount',
|
'$completedCount/$totalCount',
|
||||||
style:
|
style: UiTypography.body2m.copyWith(
|
||||||
UiTypography.body2m.copyWith(color: UiColors.textSecondary),
|
color: UiColors.textSecondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -124,9 +130,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
value: progress,
|
value: progress,
|
||||||
minHeight: 8,
|
minHeight: 8,
|
||||||
backgroundColor: UiColors.background,
|
backgroundColor: UiColors.background,
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
valueColor: const AlwaysStoppedAnimation<Color>(UiColors.primary),
|
||||||
UiColors.primary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -134,7 +138,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFormCard(TaxFormEntity form) {
|
Widget _buildFormCard(TaxForm form) {
|
||||||
// Helper to get icon based on type (could be in entity or a mapper)
|
// Helper to get icon based on type (could be in entity or a mapper)
|
||||||
final String icon = form.type == TaxFormType.i9 ? '🛂' : '📋';
|
final String icon = form.type == TaxFormType.i9 ? '🛂' : '📋';
|
||||||
|
|
||||||
@@ -164,9 +168,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
color: UiColors.primary.withOpacity(0.1),
|
color: UiColors.primary.withOpacity(0.1),
|
||||||
borderRadius: UiConstants.radiusLg,
|
borderRadius: UiConstants.radiusLg,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(child: Text(icon, style: UiTypography.headline1m)),
|
||||||
child: Text(icon, style: UiTypography.headline1m),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: UiConstants.space4),
|
const SizedBox(width: UiConstants.space4),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -187,7 +189,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space1),
|
const SizedBox(height: UiConstants.space1),
|
||||||
Text(
|
Text(
|
||||||
form.subtitle,
|
form.subtitle ?? '',
|
||||||
style: UiTypography.body2m.copyWith(
|
style: UiTypography.body2m.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: UiColors.textSecondary,
|
color: UiColors.textSecondary,
|
||||||
@@ -195,7 +197,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space1),
|
const SizedBox(height: UiConstants.space1),
|
||||||
Text(
|
Text(
|
||||||
form.description,
|
form.description ?? '',
|
||||||
style: UiTypography.body3r.copyWith(
|
style: UiTypography.body3r.copyWith(
|
||||||
color: UiColors.textSecondary,
|
color: UiColors.textSecondary,
|
||||||
),
|
),
|
||||||
@@ -231,7 +233,11 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const Icon(UiIcons.success, size: 12, color: UiColors.textSuccess),
|
const Icon(
|
||||||
|
UiIcons.success,
|
||||||
|
size: 12,
|
||||||
|
color: UiColors.textSuccess,
|
||||||
|
),
|
||||||
const SizedBox(width: UiConstants.space1),
|
const SizedBox(width: UiConstants.space1),
|
||||||
Text(
|
Text(
|
||||||
'Completed',
|
'Completed',
|
||||||
@@ -311,7 +317,9 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
const SizedBox(height: UiConstants.space1),
|
const SizedBox(height: UiConstants.space1),
|
||||||
Text(
|
Text(
|
||||||
'I-9 and W-4 forms are required by federal law to verify your employment eligibility and set up correct tax withholding.',
|
'I-9 and W-4 forms are required by federal law to verify your employment eligibility and set up correct tax withholding.',
|
||||||
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary),
|
style: UiTypography.body3r.copyWith(
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user