feat: Enhance tax forms page with progress overview and refactor components for better structure
This commit is contained in:
@@ -107,7 +107,21 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
|||||||
.getStaffTaxFormsProfileCompletion(id: staffId)
|
.getStaffTaxFormsProfileCompletion(id: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return response.data.taxForms.isNotEmpty;
|
final List<dc.GetStaffTaxFormsProfileCompletionTaxForms> taxForms =
|
||||||
|
response.data.taxForms;
|
||||||
|
|
||||||
|
// Return false if no tax forms exist
|
||||||
|
if (taxForms.isEmpty) return false;
|
||||||
|
|
||||||
|
// Return true only if all tax forms have status == "SUBMITTED"
|
||||||
|
return taxForms.every(
|
||||||
|
(dc.GetStaffTaxFormsProfileCompletionTaxForms form) {
|
||||||
|
if (form.status is dc.Unknown) return false;
|
||||||
|
final dc.TaxFormStatus status =
|
||||||
|
(form.status as dc.Known<dc.TaxFormStatus>).value;
|
||||||
|
return status == dc.TaxFormStatus.SUBMITTED;
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,14 @@ class TaxFormMapper {
|
|||||||
String subtitle = '';
|
String subtitle = '';
|
||||||
String description = '';
|
String description = '';
|
||||||
|
|
||||||
if (form.formType == dc.TaxFormType.I9) {
|
final dc.TaxFormType formType;
|
||||||
|
if (form.formType is dc.Known<dc.TaxFormType>) {
|
||||||
|
formType = (form.formType as dc.Known<dc.TaxFormType>).value;
|
||||||
|
} else {
|
||||||
|
formType = dc.TaxFormType.W4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formType == dc.TaxFormType.I9) {
|
||||||
title = 'Form I-9';
|
title = 'Form I-9';
|
||||||
subtitle = 'Employment Eligibility Verification';
|
subtitle = 'Employment Eligibility Verification';
|
||||||
description = 'Required for all new hires to verify identity.';
|
description = 'Required for all new hires to verify identity.';
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import '../../domain/repositories/tax_forms_repository.dart';
|
import '../../domain/repositories/tax_forms_repository.dart';
|
||||||
import '../mappers/tax_form_mapper.dart';
|
import '../mappers/tax_form_mapper.dart';
|
||||||
|
|
||||||
class TaxFormsRepositoryImpl
|
class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
||||||
implements TaxFormsRepository {
|
|
||||||
TaxFormsRepositoryImpl() : _service = dc.DataConnectService.instance;
|
TaxFormsRepositoryImpl() : _service = dc.DataConnectService.instance;
|
||||||
|
|
||||||
final dc.DataConnectService _service;
|
final dc.DataConnectService _service;
|
||||||
@@ -17,16 +16,22 @@ class TaxFormsRepositoryImpl
|
|||||||
Future<List<TaxForm>> getTaxForms() async {
|
Future<List<TaxForm>> getTaxForms() async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _service.getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables> response = await _service.connector
|
final QueryResult<
|
||||||
|
dc.GetTaxFormsByStaffIdData,
|
||||||
|
dc.GetTaxFormsByStaffIdVariables
|
||||||
|
>
|
||||||
|
response = await _service.connector
|
||||||
.getTaxFormsByStaffId(staffId: staffId)
|
.getTaxFormsByStaffId(staffId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
final List<TaxForm> forms =
|
final List<TaxForm> forms = response.data.taxForms
|
||||||
response.data.taxForms.map(TaxFormMapper.fromDataConnect).toList();
|
.map(TaxFormMapper.fromDataConnect)
|
||||||
|
.toList();
|
||||||
|
|
||||||
// Check if required forms exist, create if not.
|
// Check if required forms exist, create if not.
|
||||||
final Set<TaxFormType> typesPresent =
|
final Set<TaxFormType> typesPresent = forms
|
||||||
forms.map((TaxForm f) => f.type).toSet();
|
.map((TaxForm f) => f.type)
|
||||||
|
.toSet();
|
||||||
bool createdNew = false;
|
bool createdNew = false;
|
||||||
|
|
||||||
if (!typesPresent.contains(TaxFormType.i9)) {
|
if (!typesPresent.contains(TaxFormType.i9)) {
|
||||||
@@ -39,8 +44,13 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (createdNew) {
|
if (createdNew) {
|
||||||
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables> response2 =
|
final QueryResult<
|
||||||
await _service.connector.getTaxFormsByStaffId(staffId: staffId).execute();
|
dc.GetTaxFormsByStaffIdData,
|
||||||
|
dc.GetTaxFormsByStaffIdVariables
|
||||||
|
>
|
||||||
|
response2 = await _service.connector
|
||||||
|
.getTaxFormsByStaffId(staffId: staffId)
|
||||||
|
.execute();
|
||||||
return response2.data.taxForms
|
return response2.data.taxForms
|
||||||
.map(TaxFormMapper.fromDataConnect)
|
.map(TaxFormMapper.fromDataConnect)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -54,8 +64,9 @@ class TaxFormsRepositoryImpl
|
|||||||
await _service.connector
|
await _service.connector
|
||||||
.createTaxForm(
|
.createTaxForm(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
formType:
|
formType: dc.TaxFormType.values.byName(
|
||||||
dc.TaxFormType.values.byName(TaxFormAdapter.typeToString(type)),
|
TaxFormAdapter.typeToString(type),
|
||||||
|
),
|
||||||
firstName: '',
|
firstName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
socialSN: 0,
|
socialSN: 0,
|
||||||
@@ -69,8 +80,8 @@ class TaxFormsRepositoryImpl
|
|||||||
Future<void> updateI9Form(I9TaxForm form) async {
|
Future<void> updateI9Form(I9TaxForm form) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder = _service.connector
|
||||||
_service.connector.updateTaxForm(id: form.id);
|
.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapI9Fields(builder, data);
|
_mapI9Fields(builder, data);
|
||||||
await builder.execute();
|
await builder.execute();
|
||||||
@@ -81,8 +92,8 @@ class TaxFormsRepositoryImpl
|
|||||||
Future<void> submitI9Form(I9TaxForm form) async {
|
Future<void> submitI9Form(I9TaxForm form) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder = _service.connector
|
||||||
_service.connector.updateTaxForm(id: form.id);
|
.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapI9Fields(builder, data);
|
_mapI9Fields(builder, data);
|
||||||
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
||||||
@@ -93,8 +104,8 @@ class TaxFormsRepositoryImpl
|
|||||||
Future<void> updateW4Form(W4TaxForm form) async {
|
Future<void> updateW4Form(W4TaxForm form) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder = _service.connector
|
||||||
_service.connector.updateTaxForm(id: form.id);
|
.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapW4Fields(builder, data);
|
_mapW4Fields(builder, data);
|
||||||
await builder.execute();
|
await builder.execute();
|
||||||
@@ -105,8 +116,8 @@ class TaxFormsRepositoryImpl
|
|||||||
Future<void> submitW4Form(W4TaxForm form) async {
|
Future<void> submitW4Form(W4TaxForm form) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder = _service.connector
|
||||||
_service.connector.updateTaxForm(id: form.id);
|
.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapW4Fields(builder, data);
|
_mapW4Fields(builder, data);
|
||||||
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
||||||
@@ -114,7 +125,9 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _mapCommonFields(
|
void _mapCommonFields(
|
||||||
dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
|
dc.UpdateTaxFormVariablesBuilder builder,
|
||||||
|
Map<String, dynamic> data,
|
||||||
|
) {
|
||||||
if (data.containsKey('firstName')) {
|
if (data.containsKey('firstName')) {
|
||||||
builder.firstName(data['firstName'] as String?);
|
builder.firstName(data['firstName'] as String?);
|
||||||
}
|
}
|
||||||
@@ -154,8 +167,8 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
if (data.containsKey('ssn') && data['ssn']?.toString().isNotEmpty == true) {
|
if (data.containsKey('ssn') && data['ssn']?.toString().isNotEmpty == true) {
|
||||||
builder.socialSN(
|
builder.socialSN(
|
||||||
int.tryParse(data['ssn'].toString().replaceAll(RegExp(r'\D'), '')) ??
|
int.tryParse(data['ssn'].toString().replaceAll(RegExp(r'\D'), '')) ?? 0,
|
||||||
0);
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('email')) builder.email(data['email'] as String?);
|
if (data.containsKey('email')) builder.email(data['email'] as String?);
|
||||||
if (data.containsKey('phone')) builder.phone(data['phone'] as String?);
|
if (data.containsKey('phone')) builder.phone(data['phone'] as String?);
|
||||||
@@ -173,14 +186,17 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _mapI9Fields(
|
void _mapI9Fields(
|
||||||
dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
|
dc.UpdateTaxFormVariablesBuilder builder,
|
||||||
|
Map<String, dynamic> data,
|
||||||
|
) {
|
||||||
if (data.containsKey('citizenshipStatus')) {
|
if (data.containsKey('citizenshipStatus')) {
|
||||||
final String status = data['citizenshipStatus'] as String;
|
final String status = data['citizenshipStatus'] as String;
|
||||||
// Map string to enum if possible, or handle otherwise.
|
// Map string to enum if possible, or handle otherwise.
|
||||||
// Generated enum: CITIZEN, NONCITIZEN_NATIONAL, PERMANENT_RESIDENT, ALIEN_AUTHORIZED
|
// Generated enum: CITIZEN, NONCITIZEN_NATIONAL, PERMANENT_RESIDENT, ALIEN_AUTHORIZED
|
||||||
try {
|
try {
|
||||||
builder.citizen(
|
builder.citizen(
|
||||||
dc.CitizenshipStatus.values.byName(status.toUpperCase()));
|
dc.CitizenshipStatus.values.byName(status.toUpperCase()),
|
||||||
|
);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
if (data.containsKey('uscisNumber')) {
|
if (data.containsKey('uscisNumber')) {
|
||||||
@@ -202,7 +218,9 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _mapW4Fields(
|
void _mapW4Fields(
|
||||||
dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
|
dc.UpdateTaxFormVariablesBuilder builder,
|
||||||
|
Map<String, dynamic> data,
|
||||||
|
) {
|
||||||
if (data.containsKey('cityStateZip')) {
|
if (data.containsKey('cityStateZip')) {
|
||||||
final String csz = data['cityStateZip'] as String;
|
final String csz = data['cityStateZip'] as String;
|
||||||
// Extremely basic split: City, State Zip
|
// Extremely basic split: City, State Zip
|
||||||
@@ -222,10 +240,11 @@ class TaxFormsRepositoryImpl
|
|||||||
// Simple mapping assumptions:
|
// Simple mapping assumptions:
|
||||||
if (status.contains('single')) {
|
if (status.contains('single')) {
|
||||||
builder.marital(dc.MaritalStatus.SINGLE);
|
builder.marital(dc.MaritalStatus.SINGLE);
|
||||||
} else if (status.contains('married'))
|
} else if (status.contains('married')) {
|
||||||
builder.marital(dc.MaritalStatus.MARRIED);
|
builder.marital(dc.MaritalStatus.MARRIED);
|
||||||
else if (status.contains('head'))
|
} else if (status.contains('head')) {
|
||||||
builder.marital(dc.MaritalStatus.HEAD);
|
builder.marital(dc.MaritalStatus.HEAD);
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
if (data.containsKey('multipleJobs')) {
|
if (data.containsKey('multipleJobs')) {
|
||||||
@@ -245,11 +264,11 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
if (data.containsKey('extraWithholding')) {
|
if (data.containsKey('extraWithholding')) {
|
||||||
builder.extraWithholding(
|
builder.extraWithholding(
|
||||||
double.tryParse(data['extraWithholding'].toString()));
|
double.tryParse(data['extraWithholding'].toString()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('signature')) {
|
if (data.containsKey('signature')) {
|
||||||
builder.signature(data['signature'] as String?);
|
builder.signature(data['signature'] as String?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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';
|
||||||
|
import '../widgets/tax_forms_page/index.dart';
|
||||||
|
|
||||||
class TaxFormsPage extends StatelessWidget {
|
class TaxFormsPage extends StatelessWidget {
|
||||||
const TaxFormsPage({super.key});
|
const TaxFormsPage({super.key});
|
||||||
@@ -57,11 +58,16 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: UiConstants.space4,
|
spacing: UiConstants.space4,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildProgressOverview(state.forms),
|
const TaxFormsInfoCard(),
|
||||||
|
TaxFormsProgressOverview(forms: state.forms),
|
||||||
|
Column(
|
||||||
|
spacing: UiConstants.space2,
|
||||||
|
children: <Widget>[
|
||||||
...state.forms.map(
|
...state.forms.map(
|
||||||
(TaxForm form) => _buildFormCard(context, form),
|
(TaxForm form) => _buildFormCard(context, form),
|
||||||
),
|
),
|
||||||
_buildInfoCard(),
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -71,56 +77,9 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProgressOverview(List<TaxForm> forms) {
|
|
||||||
final int completedCount = forms
|
|
||||||
.where(
|
|
||||||
(TaxForm f) =>
|
|
||||||
f.status == TaxFormStatus.submitted ||
|
|
||||||
f.status == TaxFormStatus.approved,
|
|
||||||
)
|
|
||||||
.length;
|
|
||||||
final int totalCount = forms.length;
|
|
||||||
final double progress = totalCount > 0 ? completedCount / totalCount : 0.0;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.bgPopup,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Text('Document Progress', style: UiTypography.body2m.textPrimary),
|
|
||||||
Text(
|
|
||||||
'$completedCount/$totalCount',
|
|
||||||
style: UiTypography.body2m.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: UiConstants.radiusSm,
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: progress,
|
|
||||||
minHeight: 8,
|
|
||||||
backgroundColor: UiColors.background,
|
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(UiColors.primary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFormCard(BuildContext context, TaxForm form) {
|
Widget _buildFormCard(BuildContext context, TaxForm form) {
|
||||||
// Helper to get icon based on type (could be in entity or a mapper)
|
return TaxFormCard(
|
||||||
final String icon = form is I9TaxForm ? '🛂' : '📋';
|
form: form,
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (form is I9TaxForm) {
|
if (form is I9TaxForm) {
|
||||||
final Object? result = await Modular.to.pushNamed(
|
final Object? result = await Modular.to.pushNamed(
|
||||||
@@ -140,161 +99,6 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: UiConstants.space4),
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.bgPopup,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.primary.withValues(alpha: 0.1),
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
child: Center(child: Text(icon, style: UiTypography.headline1m)),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
form.title,
|
|
||||||
style: UiTypography.headline4m.textPrimary,
|
|
||||||
),
|
|
||||||
_buildStatusBadge(form.status),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Text(
|
|
||||||
form.subtitle ?? '',
|
|
||||||
style: UiTypography.body2m.textSecondary.copyWith(
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Text(
|
|
||||||
form.description ?? '',
|
|
||||||
style: UiTypography.body3r.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space2),
|
|
||||||
const Icon(
|
|
||||||
UiIcons.chevronRight,
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatusBadge(TaxFormStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case TaxFormStatus.submitted:
|
|
||||||
case TaxFormStatus.approved:
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space2,
|
|
||||||
vertical: UiConstants.space1,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.tagSuccess,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
const Icon(
|
|
||||||
UiIcons.success,
|
|
||||||
size: 12,
|
|
||||||
color: UiColors.textSuccess,
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space1),
|
|
||||||
Text('Completed', style: UiTypography.footnote2b.textSuccess),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case TaxFormStatus.inProgress:
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space2,
|
|
||||||
vertical: UiConstants.space1,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.tagPending,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
const Icon(UiIcons.clock, size: 12, color: UiColors.textWarning),
|
|
||||||
const SizedBox(width: UiConstants.space1),
|
|
||||||
Text('In Progress', style: UiTypography.footnote2b.textWarning),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space2,
|
|
||||||
vertical: UiConstants.space1,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.tagValue,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Not Started',
|
|
||||||
style: UiTypography.footnote2b.textSecondary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInfoCard() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.bgSecondary,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
const Icon(UiIcons.file, color: UiColors.primary, size: 20),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
'Why are these needed?',
|
|
||||||
style: UiTypography.headline4m.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Text(
|
|
||||||
'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.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export 'progress_overview.dart';
|
||||||
|
export 'tax_form_card.dart';
|
||||||
|
export 'tax_form_status_badge.dart';
|
||||||
|
export 'tax_forms_info_card.dart';
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
/// Widget displaying the overall progress of tax form completion.
|
||||||
|
class TaxFormsProgressOverview extends StatelessWidget {
|
||||||
|
const TaxFormsProgressOverview({required this.forms});
|
||||||
|
|
||||||
|
final List<TaxForm> forms;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final int completedCount = forms
|
||||||
|
.where(
|
||||||
|
(TaxForm f) =>
|
||||||
|
f.status == TaxFormStatus.submitted ||
|
||||||
|
f.status == TaxFormStatus.approved,
|
||||||
|
)
|
||||||
|
.length;
|
||||||
|
final int totalCount = forms.length;
|
||||||
|
final double progress = totalCount > 0 ? completedCount / totalCount : 0.0;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.bgPopup,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Document Progress', style: UiTypography.body2m.textPrimary),
|
||||||
|
Text(
|
||||||
|
'$completedCount/$totalCount',
|
||||||
|
style: UiTypography.body2m.textSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: UiConstants.radiusSm,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
minHeight: 8,
|
||||||
|
backgroundColor: UiColors.background,
|
||||||
|
valueColor: const AlwaysStoppedAnimation<Color>(UiColors.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import 'tax_form_status_badge.dart';
|
||||||
|
|
||||||
|
/// Widget displaying a single tax form card with information and navigation.
|
||||||
|
class TaxFormCard extends StatelessWidget {
|
||||||
|
const TaxFormCard({super.key, required this.form, required this.onTap});
|
||||||
|
|
||||||
|
final TaxForm form;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Helper to get icon based on type
|
||||||
|
final String icon = form is I9TaxForm ? '🛂' : '📋';
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.bgPopup,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
border: Border.all(color: UiColors.border, width: 0.5),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.primary.withValues(alpha: 0.1),
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
child: Center(child: Text(icon, style: UiTypography.headline1m)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space4),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
form.title,
|
||||||
|
style: UiTypography.headline4m.textPrimary,
|
||||||
|
),
|
||||||
|
TaxFormStatusBadge(status: form.status),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
Text(
|
||||||
|
form.subtitle ?? '',
|
||||||
|
style: UiTypography.body3r.textSecondary,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
form.description ?? '',
|
||||||
|
style: UiTypography.body3r.textSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space2),
|
||||||
|
const Icon(
|
||||||
|
UiIcons.chevronRight,
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
/// Widget displaying status badge for a tax form.
|
||||||
|
class TaxFormStatusBadge extends StatelessWidget {
|
||||||
|
const TaxFormStatusBadge({required this.status});
|
||||||
|
|
||||||
|
final TaxFormStatus status;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
switch (status) {
|
||||||
|
case TaxFormStatus.submitted:
|
||||||
|
case TaxFormStatus.approved:
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space2,
|
||||||
|
vertical: UiConstants.space1,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.tagSuccess,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(
|
||||||
|
UiIcons.success,
|
||||||
|
size: 12,
|
||||||
|
color: UiColors.textSuccess,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space1),
|
||||||
|
Text('Completed', style: UiTypography.footnote2b.textSuccess),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case TaxFormStatus.inProgress:
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space2,
|
||||||
|
vertical: UiConstants.space1,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.tagPending,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(UiIcons.clock, size: 12, color: UiColors.textWarning),
|
||||||
|
const SizedBox(width: UiConstants.space1),
|
||||||
|
Text('In Progress', style: UiTypography.footnote2b.textWarning),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space2,
|
||||||
|
vertical: UiConstants.space1,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.tagValue,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Not Started',
|
||||||
|
style: UiTypography.footnote2b.textSecondary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Information card explaining why tax forms are required.
|
||||||
|
class TaxFormsInfoCard extends StatelessWidget {
|
||||||
|
const TaxFormsInfoCard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const UiNoticeBanner(
|
||||||
|
title: 'Why are these needed?',
|
||||||
|
description:
|
||||||
|
'I-9 and W-4 forms are required by federal law to verify your employment eligibility and set up correct tax withholding.',
|
||||||
|
icon: UiIcons.file,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user