Merge branch 'dev' into Issues-on-payments-timecard-availability-screens-01-02-03-04

This commit is contained in:
Achintha Isuru
2026-01-30 11:19:07 -05:00
committed by GitHub
188 changed files with 25143 additions and 22236 deletions

View File

@@ -166,9 +166,10 @@ class BillingRepositoryImpl implements BillingRepository {
}
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final int seconds = dateTime.millisecondsSinceEpoch ~/ 1000;
final DateTime utc = dateTime.toUtc();
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
(utc.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}

View File

@@ -2,16 +2,16 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import '../blocs/billing_bloc.dart';
import '../blocs/billing_event.dart';
import '../blocs/billing_state.dart';
import '../widgets/billing_header.dart';
import '../widgets/pending_invoices_section.dart';
import '../widgets/payment_method_card.dart';
import '../widgets/spending_breakdown_card.dart';
import '../widgets/savings_card.dart';
import '../widgets/invoice_history_section.dart';
import '../widgets/export_invoices_button.dart';
import '../widgets/payment_method_card.dart';
import '../widgets/pending_invoices_section.dart';
import '../widgets/savings_card.dart';
import '../widgets/spending_breakdown_card.dart';
/// The entry point page for the client billing feature.
///
@@ -43,7 +43,6 @@ class BillingView extends StatelessWidget {
return BlocBuilder<BillingBloc, BillingState>(
builder: (BuildContext context, BillingState state) {
return Scaffold(
backgroundColor: UiColors.bgPrimary,
body: Column(
children: <Widget>[
BillingHeader(
@@ -89,9 +88,7 @@ class BillingView extends StatelessWidget {
SavingsCard(savings: state.savings),
const SizedBox(height: UiConstants.space6),
InvoiceHistorySection(invoices: state.invoiceHistory),
const SizedBox(height: UiConstants.space6),
const ExportInvoicesButton(),
const SizedBox(height: UiConstants.space6),
const SizedBox(height: UiConstants.space24),
],
),
);

View File

@@ -1,7 +1,7 @@
name: billing
description: Client Billing feature package
publish_to: 'none'
version: 1.0.0+1
version: 0.0.1
resolution: workspace
environment:

View File

@@ -100,9 +100,10 @@ class CoverageRepositoryImpl implements CoverageRepository {
}
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final int seconds = dateTime.millisecondsSinceEpoch ~/ 1000;
final DateTime utc = dateTime.toUtc();
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
(utc.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}
@@ -195,7 +196,7 @@ class CoverageRepositoryImpl implements CoverageRepository {
if (timestamp == null) {
return null;
}
final DateTime date = timestamp.toDateTime();
final DateTime date = timestamp.toDateTime().toLocal();
final String hour = date.hour.toString().padLeft(2, '0');
final String minute = date.minute.toString().padLeft(2, '0');
return '$hour:$minute';

View File

@@ -39,7 +39,7 @@ class CoverageShift extends Equatable {
/// Calculates the coverage percentage for this shift.
int get coveragePercent {
if (workersNeeded == 0) return 100;
if (workersNeeded == 0) return 0;
return ((workers.length / workersNeeded) * 100).round();
}
@@ -118,7 +118,7 @@ class CoverageStats extends Equatable {
/// Calculates the overall coverage percentage.
int get coveragePercent {
if (totalNeeded == 0) return 100;
if (totalNeeded == 0) return 0;
return ((totalConfirmed / totalNeeded) * 100).round();
}

View File

@@ -24,7 +24,6 @@ class CoveragePage extends StatelessWidget {
create: (BuildContext context) => Modular.get<CoverageBloc>()
..add(CoverageLoadRequested(date: DateTime.now())),
child: Scaffold(
backgroundColor: UiColors.background,
body: BlocBuilder<CoverageBloc, CoverageState>(
builder: (BuildContext context, CoverageState state) {
return Column(
@@ -114,13 +113,14 @@ class CoveragePage extends StatelessWidget {
const SizedBox(height: UiConstants.space5),
],
Text(
'Shifts',
'Shifts (${state.shifts.length})',
style: UiTypography.title2b.copyWith(
color: UiColors.textPrimary,
),
),
const SizedBox(height: UiConstants.space3),
CoverageShiftList(shifts: state.shifts),
const SizedBox(height: 100),
],
),
);

View File

@@ -35,24 +35,23 @@ class CoverageShiftList extends StatelessWidget {
if (shifts.isEmpty) {
return Container(
padding: const EdgeInsets.all(UiConstants.space8),
width: double.infinity,
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
child: Column(
spacing: UiConstants.space4,
children: <Widget>[
const Icon(
UiIcons.users,
size: UiConstants.space12,
color: UiColors.mutedForeground,
color: UiColors.textSecondary,
),
const SizedBox(height: UiConstants.space3),
Text(
'No shifts scheduled for this day',
style: UiTypography.body2r.copyWith(
color: UiColors.mutedForeground,
),
style: UiTypography.body2r.textSecondary,
),
],
),
@@ -160,12 +159,15 @@ class _ShiftHeader extends StatelessWidget {
),
),
child: Row(
spacing: UiConstants.space4,
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: UiConstants.space2,
children: <Widget>[
Row(
spacing: UiConstants.space2,
children: <Widget>[
Container(
width: UiConstants.space2,
@@ -175,42 +177,43 @@ class _ShiftHeader extends StatelessWidget {
shape: BoxShape.circle,
),
),
const SizedBox(width: UiConstants.space2),
Text(
title,
style: UiTypography.body1b.copyWith(
color: UiColors.textPrimary,
),
style: UiTypography.body1b.textPrimary,
),
],
),
const SizedBox(height: UiConstants.space2),
Row(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Icon(
UiIcons.mapPin,
size: UiConstants.space3,
color: UiColors.mutedForeground,
Row(
spacing: UiConstants.space1,
children: <Widget>[
const Icon(
UiIcons.mapPin,
size: UiConstants.space3,
color: UiColors.iconSecondary,
),
Expanded(child: Text(
location,
style: UiTypography.body3r.textSecondary,
overflow: TextOverflow.ellipsis,
)),
],
),
const SizedBox(width: UiConstants.space1),
Text(
location,
style: UiTypography.body3r.copyWith(
color: UiColors.mutedForeground,
),
),
const SizedBox(width: UiConstants.space3),
const Icon(
UiIcons.clock,
size: UiConstants.space3,
color: UiColors.mutedForeground,
),
const SizedBox(width: UiConstants.space1),
Text(
startTime,
style: UiTypography.body3r.copyWith(
color: UiColors.mutedForeground,
),
Row(
spacing: UiConstants.space1,
children: <Widget>[
const Icon(
UiIcons.clock,
size: UiConstants.space3,
color: UiColors.iconSecondary,
),
Text(
startTime,
style: UiTypography.body3r.textSecondary,
),
],
),
],
),

View File

@@ -1,650 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
auto_injector:
dependency: transitive
description:
name: auto_injector
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bloc:
dependency: transitive
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
core_localization:
dependency: "direct main"
description:
path: "../../../core_localization"
relative: true
source: path
version: "0.0.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csv:
dependency: transitive
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
design_system:
dependency: "direct main"
description:
path: "../../../design_system"
relative: true
source: path
version: "0.0.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.5"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.6"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_modular:
dependency: "direct main"
description:
name: flutter_modular
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
font_awesome_flutter:
dependency: transitive
description:
name: font_awesome_flutter
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
url: "https://pub.dev"
source: hosted
version: "10.12.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_fonts:
dependency: transitive
description:
name: google_fonts
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
krow_core:
dependency: "direct main"
description:
path: "../../../core"
relative: true
source: path
version: "0.0.1"
krow_data_connect:
dependency: "direct main"
description:
path: "../../../data_connect"
relative: true
source: path
version: "0.0.1"
krow_domain:
dependency: "direct main"
description:
path: "../../../domain"
relative: true
source: path
version: "0.0.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lucide_icons:
dependency: transitive
description:
name: lucide_icons
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
url: "https://pub.dev"
source: hosted
version: "0.257.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
modular_core:
dependency: transitive
description:
name: modular_core
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "7fd0c4d8ac8980011753b9bdaed2bf15111365924cdeeeaeb596214ea2b03537"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: transitive
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
result_dart:
dependency: transitive
description:
name: result_dart
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev"
source: hosted
version: "2.4.18"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
slang:
dependency: transitive
description:
name: slang
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
slang_flutter:
dependency: transitive
description:
name: slang_flutter
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.7 <4.0.0"
flutter: ">=3.38.4"

View File

@@ -1,7 +1,8 @@
name: client_coverage
description: Client coverage feature for tracking daily shift coverage and worker status
version: 1.0.0
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: ^3.6.0
@@ -26,9 +27,10 @@ dependencies:
flutter_modular: ^6.3.4
flutter_bloc: ^8.1.6
equatable: ^2.0.7
intl: ^0.20.1
intl: ^0.20.0
firebase_data_connect: ^0.2.2+1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter_lints: ^6.0.0

View File

@@ -30,7 +30,7 @@ class ClientMainPage extends StatelessWidget {
BlocProvider.of<ClientMainCubit>(context).navigateToTab(index);
},
);
},
},
),
),
);

View File

@@ -99,13 +99,6 @@ class ClientMainBottomBar extends StatelessWidget {
activeColor: activeColor,
inactiveColor: inactiveColor,
),
_buildNavItem(
index: 4,
icon: UiIcons.chart,
label: t.client_main.tabs.reports,
activeColor: activeColor,
inactiveColor: inactiveColor,
),
],
),
),

View File

@@ -27,26 +27,28 @@ class ClientCreateOrderRepositoryImpl
@override
Future<List<domain.OrderType>> getOrderTypes() {
return Future.value(const <domain.OrderType>[
domain.OrderType(
id: 'rapid',
titleKey: 'client_create_order.types.rapid',
descriptionKey: 'client_create_order.types.rapid_desc',
),
domain.OrderType(
id: 'one-time',
titleKey: 'client_create_order.types.one_time',
descriptionKey: 'client_create_order.types.one_time_desc',
),
domain.OrderType(
id: 'recurring',
titleKey: 'client_create_order.types.recurring',
descriptionKey: 'client_create_order.types.recurring_desc',
),
domain.OrderType(
id: 'permanent',
titleKey: 'client_create_order.types.permanent',
descriptionKey: 'client_create_order.types.permanent_desc',
),
/// TODO: FEATURE_NOT_YET_IMPLEMENTED
// domain.OrderType(
// id: 'rapid',
// titleKey: 'client_create_order.types.rapid',
// descriptionKey: 'client_create_order.types.rapid_desc',
// ),
// domain.OrderType(
// id: 'recurring',
// titleKey: 'client_create_order.types.recurring',
// descriptionKey: 'client_create_order.types.recurring_desc',
// ),
// domain.OrderType(
// id: 'permanent',
// titleKey: 'client_create_order.types.permanent',
// descriptionKey: 'client_create_order.types.permanent_desc',
// ),
]);
}
@@ -61,12 +63,25 @@ class ClientCreateOrderRepositoryImpl
if (vendorId == null || vendorId.isEmpty) {
throw Exception('Vendor is missing.');
}
final domain.OneTimeOrderHubDetails? hub = order.hub;
if (hub == null || hub.id.isEmpty) {
throw Exception('Hub is missing.');
}
final fdc.Timestamp orderTimestamp = _toTimestamp(order.date);
final DateTime orderDateOnly = DateTime(
order.date.year,
order.date.month,
order.date.day,
);
final fdc.Timestamp orderTimestamp = _toTimestamp(orderDateOnly);
final fdc.OperationResult<dc.CreateOrderData, dc.CreateOrderVariables> orderResult = await _dataConnect
.createOrder(businessId: businessId, orderType: dc.OrderType.ONE_TIME)
.createOrder(
businessId: businessId,
orderType: dc.OrderType.ONE_TIME,
teamHubId: hub.id,
)
.vendorId(vendorId)
.location(order.location)
.eventName(order.eventName)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.execute();
@@ -86,8 +101,15 @@ class ClientCreateOrderRepositoryImpl
final fdc.OperationResult<dc.CreateShiftData, dc.CreateShiftVariables> shiftResult = await _dataConnect
.createShift(title: shiftTitle, orderId: orderId)
.date(orderTimestamp)
.location(order.location)
.locationAddress(order.location)
.location(hub.name)
.locationAddress(hub.address)
.latitude(hub.latitude)
.longitude(hub.longitude)
.placeId(hub.placeId)
.city(hub.city)
.state(hub.state)
.street(hub.street)
.country(hub.country)
.status(dc.ShiftStatus.PENDING)
.workersNeeded(workersNeeded)
.filled(0)
@@ -109,6 +131,10 @@ class ClientCreateOrderRepositoryImpl
final double rate = order.roleRates[position.role] ?? 0;
final double totalValue = rate * hours * position.count;
print(
'CreateOneTimeOrder shiftRole: start=${start.toIso8601String()} end=${normalizedEnd.toIso8601String()}',
);
await _dataConnect
.createShiftRole(
shiftId: shiftId,
@@ -124,7 +150,7 @@ class ClientCreateOrderRepositoryImpl
}
await _dataConnect
.updateOrder(id: orderId)
.updateOrder(id: orderId, teamHubId: hub.id)
.shifts(fdc.AnyValue(<String>[shiftId]))
.execute();
}

View File

@@ -13,14 +13,17 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
: super(OneTimeOrderState.initial()) {
on<OneTimeOrderVendorsLoaded>(_onVendorsLoaded);
on<OneTimeOrderVendorChanged>(_onVendorChanged);
on<OneTimeOrderHubsLoaded>(_onHubsLoaded);
on<OneTimeOrderHubChanged>(_onHubChanged);
on<OneTimeOrderEventNameChanged>(_onEventNameChanged);
on<OneTimeOrderDateChanged>(_onDateChanged);
on<OneTimeOrderLocationChanged>(_onLocationChanged);
on<OneTimeOrderPositionAdded>(_onPositionAdded);
on<OneTimeOrderPositionRemoved>(_onPositionRemoved);
on<OneTimeOrderPositionUpdated>(_onPositionUpdated);
on<OneTimeOrderSubmitted>(_onSubmitted);
_loadVendors();
_loadHubs();
}
final CreateOneTimeOrderUseCase _createOneTimeOrderUseCase;
final dc.ExampleConnector _dataConnect;
@@ -63,6 +66,38 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
}
}
Future<void> _loadHubs() async {
try {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
add(const OneTimeOrderHubsLoaded(<OneTimeOrderHubOption>[]));
return;
}
final QueryResult<dc.ListTeamHubsByOwnerIdData, dc.ListTeamHubsByOwnerIdVariables>
result = await _dataConnect.listTeamHubsByOwnerId(ownerId: businessId).execute();
final List<OneTimeOrderHubOption> hubs = result.data.teamHubs
.map(
(dc.ListTeamHubsByOwnerIdTeamHubs hub) => OneTimeOrderHubOption(
id: hub.id,
name: hub.hubName,
address: hub.address,
placeId: hub.placeId,
latitude: hub.latitude,
longitude: hub.longitude,
city: hub.city,
state: hub.state,
street: hub.street,
country: hub.country,
zipCode: hub.zipCode,
),
)
.toList();
add(OneTimeOrderHubsLoaded(hubs));
} catch (_) {
add(const OneTimeOrderHubsLoaded(<OneTimeOrderHubOption>[]));
}
}
void _onVendorsLoaded(
OneTimeOrderVendorsLoaded event,
Emitter<OneTimeOrderState> emit,
@@ -88,6 +123,40 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
_loadRolesForVendor(event.vendor.id);
}
void _onHubsLoaded(
OneTimeOrderHubsLoaded event,
Emitter<OneTimeOrderState> emit,
) {
final OneTimeOrderHubOption? selectedHub =
event.hubs.isNotEmpty ? event.hubs.first : null;
emit(
state.copyWith(
hubs: event.hubs,
selectedHub: selectedHub,
location: selectedHub?.name ?? '',
),
);
}
void _onHubChanged(
OneTimeOrderHubChanged event,
Emitter<OneTimeOrderState> emit,
) {
emit(
state.copyWith(
selectedHub: event.hub,
location: event.hub.name,
),
);
}
void _onEventNameChanged(
OneTimeOrderEventNameChanged event,
Emitter<OneTimeOrderState> emit,
) {
emit(state.copyWith(eventName: event.eventName));
}
void _onDateChanged(
OneTimeOrderDateChanged event,
Emitter<OneTimeOrderState> emit,
@@ -95,13 +164,6 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
emit(state.copyWith(date: event.date));
}
void _onLocationChanged(
OneTimeOrderLocationChanged event,
Emitter<OneTimeOrderState> emit,
) {
emit(state.copyWith(location: event.location));
}
void _onPositionAdded(
OneTimeOrderPositionAdded event,
Emitter<OneTimeOrderState> emit,
@@ -149,10 +211,28 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
final Map<String, double> roleRates = <String, double>{
for (final OneTimeOrderRoleOption role in state.roles) role.id: role.costPerHour,
};
final OneTimeOrderHubOption? selectedHub = state.selectedHub;
if (selectedHub == null) {
throw Exception('Hub is missing.');
}
final OneTimeOrder order = OneTimeOrder(
date: state.date,
location: state.location,
location: selectedHub.name,
positions: state.positions,
hub: OneTimeOrderHubDetails(
id: selectedHub.id,
name: selectedHub.name,
address: selectedHub.address,
placeId: selectedHub.placeId,
latitude: selectedHub.latitude,
longitude: selectedHub.longitude,
city: selectedHub.city,
state: selectedHub.state,
street: selectedHub.street,
country: selectedHub.country,
zipCode: selectedHub.zipCode,
),
eventName: state.eventName,
vendorId: state.selectedVendor?.id,
roleRates: roleRates,
);

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
import 'one_time_order_state.dart';
abstract class OneTimeOrderEvent extends Equatable {
const OneTimeOrderEvent();
@@ -24,6 +25,30 @@ class OneTimeOrderVendorChanged extends OneTimeOrderEvent {
List<Object?> get props => <Object?>[vendor];
}
class OneTimeOrderHubsLoaded extends OneTimeOrderEvent {
const OneTimeOrderHubsLoaded(this.hubs);
final List<OneTimeOrderHubOption> hubs;
@override
List<Object?> get props => <Object?>[hubs];
}
class OneTimeOrderHubChanged extends OneTimeOrderEvent {
const OneTimeOrderHubChanged(this.hub);
final OneTimeOrderHubOption hub;
@override
List<Object?> get props => <Object?>[hub];
}
class OneTimeOrderEventNameChanged extends OneTimeOrderEvent {
const OneTimeOrderEventNameChanged(this.eventName);
final String eventName;
@override
List<Object?> get props => <Object?>[eventName];
}
class OneTimeOrderDateChanged extends OneTimeOrderEvent {
const OneTimeOrderDateChanged(this.date);
final DateTime date;
@@ -32,14 +57,6 @@ class OneTimeOrderDateChanged extends OneTimeOrderEvent {
List<Object?> get props => <Object?>[date];
}
class OneTimeOrderLocationChanged extends OneTimeOrderEvent {
const OneTimeOrderLocationChanged(this.location);
final String location;
@override
List<Object?> get props => <Object?>[location];
}
class OneTimeOrderPositionAdded extends OneTimeOrderEvent {
const OneTimeOrderPositionAdded();
}

View File

@@ -7,11 +7,14 @@ class OneTimeOrderState extends Equatable {
const OneTimeOrderState({
required this.date,
required this.location,
required this.eventName,
required this.positions,
this.status = OneTimeOrderStatus.initial,
this.errorMessage,
this.vendors = const <Vendor>[],
this.selectedVendor,
this.hubs = const <OneTimeOrderHubOption>[],
this.selectedHub,
this.roles = const <OneTimeOrderRoleOption>[],
});
@@ -19,40 +22,51 @@ class OneTimeOrderState extends Equatable {
return OneTimeOrderState(
date: DateTime.now(),
location: '',
eventName: '',
positions: const <OneTimeOrderPosition>[
OneTimeOrderPosition(role: '', count: 1, startTime: '', endTime: ''),
],
vendors: const <Vendor>[],
hubs: const <OneTimeOrderHubOption>[],
roles: const <OneTimeOrderRoleOption>[],
);
}
final DateTime date;
final String location;
final String eventName;
final List<OneTimeOrderPosition> positions;
final OneTimeOrderStatus status;
final String? errorMessage;
final List<Vendor> vendors;
final Vendor? selectedVendor;
final List<OneTimeOrderHubOption> hubs;
final OneTimeOrderHubOption? selectedHub;
final List<OneTimeOrderRoleOption> roles;
OneTimeOrderState copyWith({
DateTime? date,
String? location,
String? eventName,
List<OneTimeOrderPosition>? positions,
OneTimeOrderStatus? status,
String? errorMessage,
List<Vendor>? vendors,
Vendor? selectedVendor,
List<OneTimeOrderHubOption>? hubs,
OneTimeOrderHubOption? selectedHub,
List<OneTimeOrderRoleOption>? roles,
}) {
return OneTimeOrderState(
date: date ?? this.date,
location: location ?? this.location,
eventName: eventName ?? this.eventName,
positions: positions ?? this.positions,
status: status ?? this.status,
errorMessage: errorMessage ?? this.errorMessage,
vendors: vendors ?? this.vendors,
selectedVendor: selectedVendor ?? this.selectedVendor,
hubs: hubs ?? this.hubs,
selectedHub: selectedHub ?? this.selectedHub,
roles: roles ?? this.roles,
);
}
@@ -61,15 +75,61 @@ class OneTimeOrderState extends Equatable {
List<Object?> get props => <Object?>[
date,
location,
eventName,
positions,
status,
errorMessage,
vendors,
selectedVendor,
hubs,
selectedHub,
roles,
];
}
class OneTimeOrderHubOption extends Equatable {
const OneTimeOrderHubOption({
required this.id,
required this.name,
required this.address,
this.placeId,
this.latitude,
this.longitude,
this.city,
this.state,
this.street,
this.country,
this.zipCode,
});
final String id;
final String name;
final String address;
final String? placeId;
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? street;
final String? country;
final String? zipCode;
@override
List<Object?> get props => <Object?>[
id,
name,
address,
placeId,
latitude,
longitude,
city,
state,
street,
country,
zipCode,
];
}
class OneTimeOrderRoleOption extends Equatable {
const OneTimeOrderRoleOption({
required this.id,

View File

@@ -0,0 +1,56 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A text input for the order name in the one-time order form.
class OneTimeOrderEventNameInput extends StatefulWidget {
const OneTimeOrderEventNameInput({
required this.label,
required this.value,
required this.onChanged,
super.key,
});
final String label;
final String value;
final ValueChanged<String> onChanged;
@override
State<OneTimeOrderEventNameInput> createState() =>
_OneTimeOrderEventNameInputState();
}
class _OneTimeOrderEventNameInputState
extends State<OneTimeOrderEventNameInput> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.value);
}
@override
void didUpdateWidget(OneTimeOrderEventNameInput oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != _controller.text) {
_controller.text = widget.value;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return UiTextField(
label: widget.label,
controller: _controller,
onChanged: widget.onChanged,
hintText: 'Order name',
prefixIcon: UiIcons.briefcase,
);
}
}

View File

@@ -8,8 +8,8 @@ import '../../blocs/one_time_order_bloc.dart';
import '../../blocs/one_time_order_event.dart';
import '../../blocs/one_time_order_state.dart';
import 'one_time_order_date_picker.dart';
import 'one_time_order_event_name_input.dart';
import 'one_time_order_header.dart';
import 'one_time_order_location_input.dart';
import 'one_time_order_position_card.dart';
import 'one_time_order_section_header.dart';
import 'one_time_order_success_view.dart';
@@ -31,7 +31,12 @@ class OneTimeOrderView extends StatelessWidget {
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.pop(),
onDone: () => Modular.to.navigate(
'/client-main/orders/',
arguments: <String, dynamic>{
'initialDate': state.date.toIso8601String(),
},
),
);
}
@@ -129,6 +134,15 @@ class _OneTimeOrderForm extends StatelessWidget {
),
const SizedBox(height: UiConstants.space4),
OneTimeOrderEventNameInput(
label: 'ORDER NAME',
value: state.eventName,
onChanged: (String value) => BlocProvider.of<OneTimeOrderBloc>(
context,
).add(OneTimeOrderEventNameChanged(value)),
),
const SizedBox(height: UiConstants.space4),
// Vendor Selection
Text('SELECT VENDOR', style: UiTypography.footnote2r.textSecondary),
const SizedBox(height: 8),
@@ -179,12 +193,43 @@ class _OneTimeOrderForm extends StatelessWidget {
),
const SizedBox(height: UiConstants.space4),
OneTimeOrderLocationInput(
label: labels.location_label,
value: state.location,
onChanged: (String location) => BlocProvider.of<OneTimeOrderBloc>(
context,
).add(OneTimeOrderLocationChanged(location)),
Text('HUB', style: UiTypography.footnote2r.textSecondary),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
height: 48,
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusMd,
border: Border.all(color: UiColors.border),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<OneTimeOrderHubOption>(
isExpanded: true,
value: state.selectedHub,
icon: const Icon(
UiIcons.chevronDown,
size: 18,
color: UiColors.iconSecondary,
),
onChanged: (OneTimeOrderHubOption? hub) {
if (hub != null) {
BlocProvider.of<OneTimeOrderBloc>(
context,
).add(OneTimeOrderHubChanged(hub));
}
},
items: state.hubs.map((OneTimeOrderHubOption hub) {
return DropdownMenuItem<OneTimeOrderHubOption>(
value: hub,
child: Text(
hub.name,
style: UiTypography.body2m.textPrimary,
),
);
}).toList(),
),
),
),
const SizedBox(height: UiConstants.space6),

View File

@@ -25,6 +25,7 @@ dependencies:
krow_data_connect:
path: ../../../data_connect
firebase_data_connect: ^0.2.2+2
firebase_auth: ^6.1.4
dev_dependencies:
flutter_test:

View File

@@ -1,5 +1,3 @@
library client_home;
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'src/data/repositories_impl/home_repository_impl.dart';

View File

@@ -89,6 +89,14 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
end: _toTimestamp(end),
)
.execute();
print(
'Home coverage: businessId=$businessId '
'startLocal=${start.toIso8601String()} '
'endLocal=${end.toIso8601String()} '
'startUtc=${_toTimestamp(start).toJson()} '
'endUtc=${_toTimestamp(end).toJson()} '
'shiftRoles=${result.data.shiftRoles.length}',
);
int totalNeeded = 0;
int totalFilled = 0;
@@ -169,7 +177,8 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
}
fdc.Timestamp _toTimestamp(DateTime date) {
final int millis = date.millisecondsSinceEpoch;
final DateTime utc = date.toUtc();
final int millis = utc.millisecondsSinceEpoch;
final int seconds = millis ~/ 1000;
final int nanos = (millis % 1000) * 1000000;
return fdc.Timestamp(nanos, seconds);

View File

@@ -26,6 +26,8 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
on<ClientHomeWidgetVisibilityToggled>(_onWidgetVisibilityToggled);
on<ClientHomeWidgetReordered>(_onWidgetReordered);
on<ClientHomeLayoutReset>(_onLayoutReset);
add(ClientHomeStarted());
}
Future<void> _onStarted(

View File

@@ -35,12 +35,12 @@ class ClientHomeState extends Equatable {
this.isEditMode = false,
this.errorMessage,
this.dashboardData = const HomeDashboardData(
weeklySpending: 4250.0,
next7DaysSpending: 6100.0,
weeklyShifts: 12,
next7DaysScheduled: 18,
totalNeeded: 10,
totalFilled: 8,
weeklySpending: 0.0,
next7DaysSpending: 0.0,
weeklyShifts: 0,
next7DaysScheduled: 0,
totalNeeded: 0,
totalFilled: 0,
),
this.reorderItems = const <ReorderItem>[],
this.businessName = 'Your Company',

View File

@@ -34,10 +34,7 @@ class ClientHomeSheets {
builder: (BuildContext context) {
return ShiftOrderFormSheet(
initialData: initialData,
onSubmit: (Map<String, dynamic> data) {
Navigator.pop(context);
onSubmit(data);
},
onSubmit: onSubmit,
);
},
);

View File

@@ -24,8 +24,7 @@ class ClientHomePage extends StatelessWidget {
final TranslationsClientHomeEn i18n = t.client_home;
return BlocProvider<ClientHomeBloc>(
create: (BuildContext context) =>
Modular.get<ClientHomeBloc>()..add(ClientHomeStarted()),
create: (BuildContext context) => Modular.get<ClientHomeBloc>(),
child: Scaffold(
body: SafeArea(
child: Column(
@@ -59,19 +58,15 @@ class ClientHomePage extends StatelessWidget {
100,
),
onReorder: (int oldIndex, int newIndex) {
BlocProvider.of<ClientHomeBloc>(context).add(
ClientHomeWidgetReordered(oldIndex, newIndex),
);
BlocProvider.of<ClientHomeBloc>(
context,
).add(ClientHomeWidgetReordered(oldIndex, newIndex));
},
children: state.widgetOrder.map((String id) {
return Container(
key: ValueKey(id),
key: ValueKey<String>(id),
margin: const EdgeInsets.only(bottom: UiConstants.space4),
child: DashboardWidgetBuilder(
id: id,
state: state,
isEditMode: true,
),
child: DashboardWidgetBuilder(id: id, state: state, isEditMode: true),
);
}).toList(),
);

View File

@@ -24,21 +24,22 @@ class ActionsWidget extends StatelessWidget {
return Row(
children: <Widget>[
Expanded(
child: _ActionCard(
title: i18n.rapid,
subtitle: i18n.rapid_subtitle,
icon: UiIcons.zap,
color: const Color(0xFFFEF2F2),
borderColor: const Color(0xFFFECACA),
iconBgColor: const Color(0xFFFEE2E2),
iconColor: const Color(0xFFDC2626),
textColor: const Color(0xFF7F1D1D),
subtitleColor: const Color(0xFFB91C1C),
onTap: onRapidPressed,
),
),
const SizedBox(width: UiConstants.space2),
/// TODO: FEATURE_NOT_YET_IMPLEMENTED
// Expanded(
// child: _ActionCard(
// title: i18n.rapid,
// subtitle: i18n.rapid_subtitle,
// icon: UiIcons.zap,
// color: const Color(0xFFFEF2F2),
// borderColor: const Color(0xFFFECACA),
// iconBgColor: const Color(0xFFFEE2E2),
// iconColor: const Color(0xFFDC2626),
// textColor: const Color(0xFF7F1D1D),
// subtitleColor: const Color(0xFFB91C1C),
// onTap: onRapidPressed,
// ),
// ),
// const SizedBox(width: UiConstants.space2),
Expanded(
child: _ActionCard(
title: i18n.create_order,

View File

@@ -25,11 +25,12 @@ class CoverageDashboard extends StatelessWidget {
for (final s in shifts) {
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;
final double rate = s['hourlyRate'] as double? ?? 0.0;
final double hours = s['hours'] as double? ?? 0.0;
totalNeeded += needed;
totalConfirmed += confirmed;
todayCost += rate * 8 * confirmed;
todayCost += rate * hours;
}
final int coveragePercent = totalNeeded > 0
@@ -104,15 +105,13 @@ class CoverageDashboard extends StatelessWidget {
icon: UiIcons.warning,
isWarning: unfilledPositions > 0,
),
if (lateWorkersCount > 0) ...<Widget>[
const SizedBox(height: UiConstants.space2),
_StatusCard(
label: 'Running Late',
value: '$lateWorkersCount',
icon: UiIcons.error,
isError: true,
),
],
const SizedBox(height: UiConstants.space2),
_StatusCard(
label: 'Running Late',
value: '$lateWorkersCount',
icon: UiIcons.error,
isError: true,
),
],
),
),
@@ -122,7 +121,7 @@ class CoverageDashboard extends StatelessWidget {
children: <Widget>[
_StatusCard(
label: 'Checked In',
value: '$checkedInCount/$totalConfirmed',
value: '$checkedInCount/$totalNeeded',
icon: UiIcons.success,
isInfo: true,
),

View File

@@ -15,9 +15,9 @@ class CoverageWidget extends StatelessWidget {
/// Creates a [CoverageWidget].
const CoverageWidget({
super.key,
this.totalNeeded = 10,
this.totalConfirmed = 8,
this.coveragePercent = 80,
this.totalNeeded = 0,
this.totalConfirmed = 0,
this.coveragePercent = 0,
});
@override

View File

@@ -70,7 +70,21 @@ class DashboardWidgetBuilder extends StatelessWidget {
context,
data,
onSubmit: (Map<String, dynamic> submittedData) {
// Handle form submission if needed
final String? dateStr =
submittedData['date']?.toString();
if (dateStr == null || dateStr.isEmpty) {
return;
}
final DateTime? initialDate = DateTime.tryParse(dateStr);
if (initialDate == null) {
return;
}
Modular.to.navigate(
'/client-main/orders/',
arguments: <String, dynamic>{
'initialDate': initialDate.toIso8601String(),
},
);
},
);
},

View File

@@ -31,6 +31,15 @@ class _LiveActivityWidgetState extends State<LiveActivityWidget> {
final DateTime now = DateTime.now();
final DateTime start = DateTime(now.year, now.month, now.day);
final DateTime end = DateTime(now.year, now.month, now.day, 23, 59, 59, 999);
final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
await dc.ExampleConnector.instance
.listShiftRolesByBusinessAndDateRange(
businessId: businessId,
start: _toTimestamp(start),
end: _toTimestamp(end),
)
.execute();
final fdc.QueryResult<dc.ListStaffsApplicationsByBusinessForDayData,
dc.ListStaffsApplicationsByBusinessForDayVariables> result =
await dc.ExampleConnector.instance
@@ -41,33 +50,20 @@ class _LiveActivityWidgetState extends State<LiveActivityWidget> {
)
.execute();
if (result.data.applications.isEmpty) {
if (shiftRolesResult.data.shiftRoles.isEmpty &&
result.data.applications.isEmpty) {
return _LiveActivityData.empty();
}
final Map<String, _LiveShiftAggregate> aggregates =
<String, _LiveShiftAggregate>{};
for (final dc.ListStaffsApplicationsByBusinessForDayApplications app
in result.data.applications) {
final String key = '${app.shiftId}:${app.roleId}';
final _LiveShiftAggregate aggregate = aggregates[key] ??
_LiveShiftAggregate(
workersNeeded: app.shiftRole.count,
assigned: app.shiftRole.assigned ?? 0,
cost: app.shiftRole.shift.cost ?? 0,
);
aggregates[key] = aggregate;
}
int totalNeeded = 0;
int totalAssigned = 0;
double totalCost = 0;
for (final _LiveShiftAggregate aggregate in aggregates.values) {
totalNeeded += aggregate.workersNeeded;
totalAssigned += aggregate.assigned;
totalCost += aggregate.cost;
for (final dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole
in shiftRolesResult.data.shiftRoles) {
totalNeeded += shiftRole.count;
totalCost += shiftRole.totalValue ?? 0;
}
final int totalAssigned = result.data.applications.length;
int lateCount = 0;
int checkedInCount = 0;
for (final dc.ListStaffsApplicationsByBusinessForDayApplications app
@@ -92,9 +88,10 @@ class _LiveActivityWidgetState extends State<LiveActivityWidget> {
}
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final int seconds = dateTime.millisecondsSinceEpoch ~/ 1000;
final DateTime utc = dateTime.toUtc();
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
(utc.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}
@@ -136,9 +133,8 @@ class _LiveActivityWidgetState extends State<LiveActivityWidget> {
<String, Object>{
'workersNeeded': data.totalNeeded,
'filled': data.totalAssigned,
'hourlyRate': data.totalAssigned == 0
? 0.0
: data.totalCost / data.totalAssigned,
'hourlyRate': 1.0,
'hours': data.totalCost,
'status': 'OPEN',
'date': DateTime.now().toIso8601String().split('T')[0],
},
@@ -192,15 +188,3 @@ class _LiveActivityData {
);
}
}
class _LiveShiftAggregate {
_LiveShiftAggregate({
required this.workersNeeded,
required this.assigned,
required this.cost,
});
final int workersNeeded;
final int assigned;
final double cost;
}

View File

@@ -1,3 +1,4 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:flutter/material.dart';
@@ -52,6 +53,7 @@ class ShiftOrderFormSheet extends StatefulWidget {
class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
late TextEditingController _dateController;
late TextEditingController _globalLocationController;
late TextEditingController _orderNameController;
late List<Map<String, dynamic>> _positions;
@@ -59,6 +61,10 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
List<_VendorOption> _vendors = const <_VendorOption>[];
List<_RoleOption> _roles = const <_RoleOption>[];
String? _selectedVendorId;
List<dc.ListTeamHubsByOwnerIdTeamHubs> _hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
bool _showSuccess = false;
Map<String, dynamic>? _submitData;
@override
void initState() {
@@ -75,6 +81,9 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
widget.initialData?['locationAddress'] ??
'',
);
_orderNameController = TextEditingController(
text: widget.initialData?['eventName']?.toString() ?? '',
);
// Initialize positions
_positions = <Map<String, dynamic>>[
@@ -96,6 +105,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
];
_loadVendors();
_loadHubs();
_loadOrderDetails();
}
@@ -103,6 +113,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
void dispose() {
_dateController.dispose();
_globalLocationController.dispose();
_orderNameController.dispose();
super.dispose();
}
@@ -187,9 +198,14 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
if (businessId == null || businessId.isEmpty) {
return;
}
final dc.ListTeamHubsByOwnerIdTeamHubs? selectedHub = _selectedHub;
if (selectedHub == null) {
return;
}
final DateTime date = DateTime.parse(_dateController.text);
final fdc.Timestamp orderTimestamp = _toTimestamp(date);
final DateTime dateOnly = DateTime.utc(date.year, date.month, date.day);
final fdc.Timestamp orderTimestamp = _toTimestamp(dateOnly);
final dc.OrderType orderType =
_orderTypeFromValue(widget.initialData?['type']?.toString());
@@ -198,9 +214,10 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
.createOrder(
businessId: businessId,
orderType: orderType,
teamHubId: selectedHub.id,
)
.vendorId(_selectedVendorId)
.location(_globalLocationController.text)
.eventName(_orderNameController.text)
.status(dc.OrderStatus.POSTED)
.date(orderTimestamp)
.execute();
@@ -222,8 +239,15 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
shiftResult = await _dataConnect
.createShift(title: shiftTitle, orderId: orderId)
.date(orderTimestamp)
.location(_globalLocationController.text)
.locationAddress(_globalLocationController.text)
.location(selectedHub.hubName)
.locationAddress(selectedHub.address)
.latitude(selectedHub.latitude)
.longitude(selectedHub.longitude)
.placeId(selectedHub.placeId)
.city(selectedHub.city)
.state(selectedHub.state)
.street(selectedHub.street)
.country(selectedHub.country)
.status(dc.ShiftStatus.PENDING)
.workersNeeded(workersNeeded)
.filled(0)
@@ -266,12 +290,17 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
}
await _dataConnect
.updateOrder(id: orderId)
.updateOrder(id: orderId, teamHubId: selectedHub.id)
.shifts(fdc.AnyValue(<String>[shiftId]))
.execute();
widget.onSubmit(<String, dynamic>{
'orderId': orderId,
if (!mounted) return;
setState(() {
_submitData = <String, dynamic>{
'orderId': orderId,
'date': _dateController.text,
};
_showSuccess = true;
});
}
@@ -306,6 +335,35 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
}
}
Future<void> _loadHubs() async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return;
}
try {
final fdc.QueryResult<
dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables> result =
await _dataConnect.listTeamHubsByOwnerId(ownerId: businessId).execute();
final List<dc.ListTeamHubsByOwnerIdTeamHubs> hubs = result.data.teamHubs;
if (!mounted) return;
setState(() {
_hubs = hubs;
_selectedHub = hubs.isNotEmpty ? hubs.first : null;
if (_selectedHub != null) {
_globalLocationController.text = _selectedHub!.address;
}
});
} catch (_) {
if (!mounted) return;
setState(() {
_hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
_selectedHub = null;
});
}
}
Future<void> _loadRolesForVendor(String vendorId) async {
try {
final fdc.QueryResult<dc.ListRolesByVendorIdData, dc.ListRolesByVendorIdVariables>
@@ -357,10 +415,14 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShift firstShift =
shiftRoles.first.shift;
_globalLocationController.text = firstShift.order.location ??
firstShift.locationAddress ??
firstShift.location ??
_globalLocationController.text;
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub?
teamHub = firstShift.order.teamHub;
await _loadHubsAndSelect(
placeId: teamHub?.placeId,
hubName: teamHub?.hubName,
address: teamHub?.address,
);
_orderNameController.text = firstShift.order.eventName ?? '';
final String? vendorId = firstShift.order.vendorId;
if (mounted) {
@@ -394,6 +456,70 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
}
}
Future<void> _loadHubsAndSelect({
String? placeId,
String? hubName,
String? address,
}) async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return;
}
try {
final fdc.QueryResult<
dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables> result =
await _dataConnect.listTeamHubsByOwnerId(ownerId: businessId).execute();
final List<dc.ListTeamHubsByOwnerIdTeamHubs> hubs = result.data.teamHubs;
dc.ListTeamHubsByOwnerIdTeamHubs? selected;
if (placeId != null && placeId.isNotEmpty) {
for (final dc.ListTeamHubsByOwnerIdTeamHubs hub in hubs) {
if (hub.placeId == placeId) {
selected = hub;
break;
}
}
}
if (selected == null && hubName != null && hubName.isNotEmpty) {
for (final dc.ListTeamHubsByOwnerIdTeamHubs hub in hubs) {
if (hub.hubName == hubName) {
selected = hub;
break;
}
}
}
if (selected == null && address != null && address.isNotEmpty) {
for (final dc.ListTeamHubsByOwnerIdTeamHubs hub in hubs) {
if (hub.address == address) {
selected = hub;
break;
}
}
}
selected ??= hubs.isNotEmpty ? hubs.first : null;
if (!mounted) return;
setState(() {
_hubs = hubs;
_selectedHub = selected;
if (selected != null) {
_globalLocationController.text = selected.address;
}
});
} catch (_) {
if (!mounted) return;
setState(() {
_hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
_selectedHub = null;
});
}
}
String _formatTimeForField(fdc.Timestamp? value) {
if (value == null) return '';
try {
@@ -472,7 +598,8 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
}
fdc.Timestamp _toTimestamp(DateTime date) {
final int millis = date.millisecondsSinceEpoch;
final DateTime utc = date.toUtc();
final int millis = utc.millisecondsSinceEpoch;
final int seconds = millis ~/ 1000;
final int nanos = (millis % 1000) * 1000000;
return fdc.Timestamp(nanos, seconds);
@@ -480,6 +607,16 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
@override
Widget build(BuildContext context) {
if (_showSuccess) {
final TranslationsClientCreateOrderOneTimeEn labels =
t.client_create_order.one_time;
return _buildSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
);
}
return Container(
height: MediaQuery.of(context).size.height * 0.95,
decoration: const BoxDecoration(
@@ -546,12 +683,16 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
_buildVendorDropdown(),
const SizedBox(height: UiConstants.space4),
_buildSectionHeader('ORDER NAME'),
_buildOrderNameField(),
const SizedBox(height: UiConstants.space4),
_buildSectionHeader('DATE'),
_buildDateField(),
const SizedBox(height: UiConstants.space4),
_buildSectionHeader('LOCATION'),
_buildLocationField(),
_buildSectionHeader('HUB'),
_buildHubField(),
const SizedBox(height: UiConstants.space5),
Row(
@@ -783,7 +924,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
);
}
Widget _buildLocationField() {
Widget _buildHubField() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
decoration: BoxDecoration(
@@ -791,21 +932,52 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
borderRadius: UiConstants.radiusMd,
border: Border.all(color: UiColors.border),
),
child: Row(
children: <Widget>[
const Icon(UiIcons.mapPin, size: 20, color: UiColors.iconSecondary),
const SizedBox(width: UiConstants.space2),
Expanded(
child: TextField(
controller: _globalLocationController,
decoration: const InputDecoration(
hintText: 'Enter location address',
border: InputBorder.none,
),
style: UiTypography.body2r.textPrimary,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<dc.ListTeamHubsByOwnerIdTeamHubs>(
isExpanded: true,
value: _selectedHub,
icon: const Icon(
UiIcons.chevronDown,
size: 18,
color: UiColors.iconSecondary,
),
],
onChanged: (dc.ListTeamHubsByOwnerIdTeamHubs? hub) {
if (hub != null) {
setState(() {
_selectedHub = hub;
_globalLocationController.text = hub.address;
});
}
},
items: _hubs.map((dc.ListTeamHubsByOwnerIdTeamHubs hub) {
return DropdownMenuItem<dc.ListTeamHubsByOwnerIdTeamHubs>(
value: hub,
child: Text(
hub.hubName,
style: UiTypography.body2r.textPrimary,
),
);
}).toList(),
),
),
);
}
Widget _buildOrderNameField() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusMd,
border: Border.all(color: UiColors.border),
),
child: TextField(
controller: _orderNameController,
decoration: const InputDecoration(
hintText: 'Order name',
border: InputBorder.none,
),
style: UiTypography.body2r.textPrimary,
),
);
}
@@ -1109,6 +1281,90 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
);
}
Widget _buildSuccessView({
required String title,
required String message,
required String buttonLabel,
}) {
return Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.95,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[UiColors.primary, UiColors.buttonPrimaryHover],
),
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: SafeArea(
child: Center(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 40),
padding: const EdgeInsets.all(UiConstants.space8),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusLg * 1.5,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 64,
height: 64,
decoration: const BoxDecoration(
color: UiColors.accent,
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
UiIcons.check,
color: UiColors.black,
size: 32,
),
),
),
const SizedBox(height: UiConstants.space6),
Text(
title,
style: UiTypography.headline2m.textPrimary,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space3),
Text(
message,
textAlign: TextAlign.center,
style: UiTypography.body2r.textSecondary.copyWith(
height: 1.5,
),
),
const SizedBox(height: UiConstants.space8),
SizedBox(
width: double.infinity,
child: UiButton.primary(
text: buttonLabel,
onPressed: () {
widget.onSubmit(_submitData ?? <String, dynamic>{});
Navigator.pop(context);
},
size: UiButtonSize.large,
),
),
],
),
),
),
),
);
}
Widget _buildInlineTimeInput({
required String label,
required String value,

View File

@@ -22,6 +22,7 @@ dependencies:
core_localization:
path: ../../../core_localization
krow_domain: ^0.0.1
krow_data_connect: ^0.0.1
dev_dependencies:
flutter_test:

View File

@@ -1,9 +1,13 @@
import 'dart:convert';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:http/http.dart' as http;
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';
import '../../util/hubs_constants.dart';
/// Implementation of [HubRepositoryInterface] backed by Data Connect.
class HubRepositoryImpl implements HubRepositoryInterface {
@@ -27,10 +31,24 @@ class HubRepositoryImpl implements HubRepositoryInterface {
Future<domain.Hub> createHub({
required String name,
required String address,
String? placeId,
double? latitude,
double? longitude,
String? city,
String? state,
String? street,
String? country,
String? zipCode,
}) async {
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser();
final String teamId = await _getOrCreateTeamId(business);
final String? city = business.city;
final _PlaceAddress? placeAddress =
placeId == null || placeId.isEmpty ? null : await _fetchPlaceAddress(placeId);
final String? cityValue = city ?? placeAddress?.city ?? business.city;
final String? stateValue = state ?? placeAddress?.state;
final String? streetValue = street ?? placeAddress?.street;
final String? countryValue = country ?? placeAddress?.country;
final String? zipCodeValue = zipCode ?? placeAddress?.zipCode;
final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables> result = await _dataConnect
.createTeamHub(
@@ -38,7 +56,14 @@ class HubRepositoryImpl implements HubRepositoryInterface {
hubName: name,
address: address,
)
.city(city?.isNotEmpty == true ? city : '')
.placeId(placeId)
.latitude(latitude)
.longitude(longitude)
.city(cityValue?.isNotEmpty == true ? cityValue : '')
.state(stateValue)
.street(streetValue)
.country(countryValue)
.zipCode(zipCodeValue)
.execute();
final String? createdId = result.data?.teamHub_insert.id;
if (createdId == null) {
@@ -69,6 +94,25 @@ class HubRepositoryImpl implements HubRepositoryInterface {
@override
Future<void> deleteHub(String id) async {
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 QueryResult<
dc.ListOrdersByBusinessAndTeamHubData,
dc.ListOrdersByBusinessAndTeamHubVariables> result = await _dataConnect
.listOrdersByBusinessAndTeamHub(
businessId: businessId,
teamHubId: id,
)
.execute();
if (result.data.orders.isNotEmpty) {
throw Exception("Sorry this hub has orders, it can't be deleted.");
}
await _dataConnect.deleteTeamHub(id: id).execute();
}
@@ -192,4 +236,99 @@ class HubRepositoryImpl implements HubRepositoryInterface {
)
.toList();
}
Future<_PlaceAddress?> _fetchPlaceAddress(String placeId) async {
final Uri uri = Uri.https(
'maps.googleapis.com',
'/maps/api/place/details/json',
<String, String>{
'place_id': placeId,
'fields': 'address_component',
'key': HubsConstants.googlePlacesApiKey,
},
);
try {
final http.Response response = await http.get(uri);
if (response.statusCode != 200) {
return null;
}
final Map<String, dynamic> payload =
json.decode(response.body) as Map<String, dynamic>;
if (payload['status'] != 'OK') {
return null;
}
final Map<String, dynamic>? result =
payload['result'] as Map<String, dynamic>?;
final List<dynamic>? components =
result?['address_components'] as List<dynamic>?;
if (components == null || components.isEmpty) {
return null;
}
String? streetNumber;
String? route;
String? city;
String? state;
String? country;
String? zipCode;
for (final dynamic entry in components) {
final Map<String, dynamic> component = entry as Map<String, dynamic>;
final List<dynamic> types = component['types'] as List<dynamic>? ?? <dynamic>[];
final String? longName = component['long_name'] as String?;
final String? shortName = component['short_name'] as String?;
if (types.contains('street_number')) {
streetNumber = longName;
} else if (types.contains('route')) {
route = longName;
} else if (types.contains('locality')) {
city = longName;
} else if (types.contains('postal_town')) {
city ??= longName;
} else if (types.contains('administrative_area_level_2')) {
city ??= longName;
} else if (types.contains('administrative_area_level_1')) {
state = shortName ?? longName;
} else if (types.contains('country')) {
country = shortName ?? longName;
} else if (types.contains('postal_code')) {
zipCode = longName;
}
}
final String? streetValue = <String?>[streetNumber, route]
.where((String? value) => value != null && value!.isNotEmpty)
.join(' ')
.trim();
return _PlaceAddress(
street: streetValue?.isEmpty == true ? null : streetValue,
city: city,
state: state,
country: country,
zipCode: zipCode,
);
} catch (_) {
return null;
}
}
}
class _PlaceAddress {
const _PlaceAddress({
this.street,
this.city,
this.state,
this.country,
this.zipCode,
});
final String? street;
final String? city;
final String? state;
final String? country;
final String? zipCode;
}

View File

@@ -10,11 +10,42 @@ class CreateHubArguments extends UseCaseArgument {
/// The physical address of the hub.
final String address;
final String? placeId;
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? street;
final String? country;
final String? zipCode;
/// Creates a [CreateHubArguments] instance.
///
/// Both [name] and [address] are required.
const CreateHubArguments({required this.name, required this.address});
const CreateHubArguments({
required this.name,
required this.address,
this.placeId,
this.latitude,
this.longitude,
this.city,
this.state,
this.street,
this.country,
this.zipCode,
});
@override
List<Object?> get props => <Object?>[name, address];
List<Object?> get props => <Object?>[
name,
address,
placeId,
latitude,
longitude,
city,
state,
street,
country,
zipCode,
];
}

View File

@@ -15,7 +15,18 @@ abstract interface class HubRepositoryInterface {
///
/// Takes the [name] and [address] of the new hub.
/// Returns the created [Hub] entity.
Future<Hub> createHub({required String name, required String address});
Future<Hub> createHub({
required String name,
required String address,
String? placeId,
double? latitude,
double? longitude,
String? city,
String? state,
String? street,
String? country,
String? zipCode,
});
/// Deletes a hub by its [id].
Future<void> deleteHub(String id);

View File

@@ -21,6 +21,14 @@ class CreateHubUseCase implements UseCase<CreateHubArguments, Hub> {
return _repository.createHub(
name: arguments.name,
address: arguments.address,
placeId: arguments.placeId,
latitude: arguments.latitude,
longitude: arguments.longitude,
city: arguments.city,
state: arguments.state,
street: arguments.street,
country: arguments.country,
zipCode: arguments.zipCode,
);
}
}

View File

@@ -84,7 +84,18 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
try {
await _createHubUseCase(
CreateHubArguments(name: event.name, address: event.address),
CreateHubArguments(
name: event.name,
address: event.address,
placeId: event.placeId,
latitude: event.latitude,
longitude: event.longitude,
city: event.city,
state: event.state,
street: event.street,
country: event.country,
zipCode: event.zipCode,
),
);
final List<Hub> hubs = await _getHubsUseCase();
emit(

View File

@@ -18,11 +18,41 @@ class ClientHubsFetched extends ClientHubsEvent {
class ClientHubsAddRequested extends ClientHubsEvent {
final String name;
final String address;
final String? placeId;
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? street;
final String? country;
final String? zipCode;
const ClientHubsAddRequested({required this.name, required this.address});
const ClientHubsAddRequested({
required this.name,
required this.address,
this.placeId,
this.latitude,
this.longitude,
this.city,
this.state,
this.street,
this.country,
this.zipCode,
});
@override
List<Object?> get props => <Object?>[name, address];
List<Object?> get props => <Object?>[
name,
address,
placeId,
latitude,
longitude,
city,
state,
street,
country,
zipCode,
];
}
/// Event triggered to delete a hub.

View File

@@ -27,8 +27,12 @@ class ClientHubsPage extends StatelessWidget {
create: (BuildContext context) =>
Modular.get<ClientHubsBloc>()..add(const ClientHubsFetched()),
child: BlocConsumer<ClientHubsBloc, ClientHubsState>(
listenWhen: (ClientHubsState previous, ClientHubsState current) {
return previous.errorMessage != current.errorMessage ||
previous.successMessage != current.successMessage;
},
listener: (BuildContext context, ClientHubsState state) {
if (state.errorMessage != null) {
if (state.errorMessage != null && state.errorMessage!.isNotEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(state.errorMessage!)));
@@ -36,7 +40,7 @@ class ClientHubsPage extends StatelessWidget {
context,
).add(const ClientHubsMessageCleared());
}
if (state.successMessage != null) {
if (state.successMessage != null && state.successMessage!.isNotEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(state.successMessage!)));
@@ -106,9 +110,21 @@ class ClientHubsPage extends StatelessWidget {
),
if (state.showAddHubDialog)
AddHubDialog(
onCreate: (String name, String address) {
onCreate: (
String name,
String address, {
String? placeId,
double? latitude,
double? longitude,
}) {
BlocProvider.of<ClientHubsBloc>(context).add(
ClientHubsAddRequested(name: name, address: address),
ClientHubsAddRequested(
name: name,
address: address,
placeId: placeId,
latitude: latitude,
longitude: longitude,
),
);
},
onCancel: () => BlocProvider.of<ClientHubsBloc>(

View File

@@ -1,11 +1,20 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:core_localization/core_localization.dart';
import 'package:google_places_flutter/model/prediction.dart';
import 'hub_address_autocomplete.dart';
/// A dialog for adding a new hub.
class AddHubDialog extends StatefulWidget {
/// Callback when the "Create Hub" button is pressed.
final Function(String name, String address) onCreate;
final void Function(
String name,
String address, {
String? placeId,
double? latitude,
double? longitude,
}) onCreate;
/// Callback when the dialog is cancelled.
final VoidCallback onCancel;
@@ -24,18 +33,22 @@ class AddHubDialog extends StatefulWidget {
class _AddHubDialogState extends State<AddHubDialog> {
late final TextEditingController _nameController;
late final TextEditingController _addressController;
late final FocusNode _addressFocusNode;
Prediction? _selectedPrediction;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
_addressController = TextEditingController();
_addressFocusNode = FocusNode();
}
@override
void dispose() {
_nameController.dispose();
_addressController.dispose();
_addressFocusNode.dispose();
super.dispose();
}
@@ -74,12 +87,13 @@ class _AddHubDialogState extends State<AddHubDialog> {
),
const SizedBox(height: UiConstants.space4),
_buildFieldLabel(t.client_hubs.add_hub_dialog.address_label),
TextField(
HubAddressAutocomplete(
controller: _addressController,
style: UiTypography.body1r.textPrimary,
decoration: _buildInputDecoration(
t.client_hubs.add_hub_dialog.address_hint,
),
hintText: t.client_hubs.add_hub_dialog.address_hint,
focusNode: _addressFocusNode,
onSelected: (Prediction prediction) {
_selectedPrediction = prediction;
},
),
const SizedBox(height: UiConstants.space8),
Row(
@@ -98,6 +112,13 @@ class _AddHubDialogState extends State<AddHubDialog> {
widget.onCreate(
_nameController.text,
_addressController.text,
placeId: _selectedPrediction?.placeId,
latitude: double.tryParse(
_selectedPrediction?.lat ?? '',
),
longitude: double.tryParse(
_selectedPrediction?.lng ?? '',
),
);
}
},

View File

@@ -0,0 +1,61 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:google_places_flutter/google_places_flutter.dart';
import 'package:google_places_flutter/model/prediction.dart';
import '../../util/hubs_constants.dart';
class HubAddressAutocomplete extends StatelessWidget {
const HubAddressAutocomplete({
required this.controller,
required this.hintText,
this.focusNode,
this.onSelected,
super.key,
});
final TextEditingController controller;
final String hintText;
final FocusNode? focusNode;
final void Function(Prediction prediction)? onSelected;
@override
Widget build(BuildContext context) {
return GooglePlaceAutoCompleteTextField(
textEditingController: controller,
focusNode: focusNode,
googleAPIKey: HubsConstants.googlePlacesApiKey,
debounceTime: 500,
countries: HubsConstants.supportedCountries,
isLatLngRequired: true,
getPlaceDetailWithLatLng: (Prediction prediction) {
onSelected?.call(prediction);
},
itemClick: (Prediction prediction) {
controller.text = prediction.description ?? '';
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length),
);
onSelected?.call(prediction);
},
itemBuilder: (_, _, Prediction prediction) {
return Padding(
padding: const EdgeInsets.all(UiConstants.space2),
child: Row(
spacing: UiConstants.space1,
children: <Widget>[
const Icon(UiIcons.mapPin, color: UiColors.iconSecondary),
Expanded(
child: Text(
prediction.description ?? "",
style: UiTypography.body1r.textSecondary,
),
),
],
),
);
},
textStyle: UiTypography.body1r.textPrimary,
);
}
}

View File

@@ -0,0 +1,4 @@
class HubsConstants {
static const String googlePlacesApiKey = String.fromEnvironment('GOOGLE_PLACES_API_KEY');
static const List<String> supportedCountries = <String>['us'];
}

View File

@@ -11,11 +11,7 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
flutter_modular: ^6.3.2
equatable: ^2.0.5
lucide_icons: ^0.257.0
# Architecture Packages
krow_core:
path: ../../../core
@@ -27,8 +23,15 @@ dependencies:
path: ../../../design_system
core_localization:
path: ../../../core_localization
flutter_bloc: ^8.1.0
flutter_modular: ^6.3.2
equatable: ^2.0.5
lucide_icons: ^0.257.0
firebase_auth: ^6.1.4
firebase_data_connect: ^0.2.2+2
google_places_flutter: ^2.1.1
http: ^1.2.2
dev_dependencies:
flutter_test:

View File

@@ -24,8 +24,8 @@ class SettingsActions extends StatelessWidget {
delegate: SliverChildListDelegate(<Widget>[
const SizedBox(height: UiConstants.space5),
/// TODO: FEATURE_NOT_YET_IMPLEMENTED
// Edit profile is not yet implemented
/// TODO: FEATURE_NOT_YET_IMPLEMENTED
// Edit profile is not yet implemented
// Hubs button
UiButton.primary(
@@ -49,12 +49,19 @@ class SettingsActions extends StatelessWidget {
),
);
}
/// Handles the sign-out button click event.
void _onSignoutClicked(BuildContext context) {
ReadContext(
context,
).read<ClientSettingsBloc>().add(const ClientSettingsSignOutRequested());
}
/// Shows a confirmation dialog for signing out.
Future<void> _showSignOutDialog(BuildContext context) {
return showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
builder: (BuildContext dialogContext) => AlertDialog(
backgroundColor: UiColors.bgPopup,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
@@ -70,12 +77,7 @@ class SettingsActions extends StatelessWidget {
// Log out button
UiButton.secondary(
text: t.client_settings.profile.log_out,
onPressed: () {
Modular.to.pop();
BlocProvider.of<ClientSettingsBloc>(
context,
).add(const ClientSettingsSignOutRequested());
},
onPressed: () => _onSignoutClicked(context),
),
// Cancel button

View File

@@ -62,10 +62,13 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
'end=${shiftRole.endTime?.toJson()} hours=$hours totalValue=$totalValue',
);
final String eventName =
shiftRole.shift.order.eventName ?? shiftRole.shift.title;
return domain.OrderItem(
id: _shiftRoleKey(shiftRole.shiftId, shiftRole.roleId),
orderId: shiftRole.shift.order.id,
title: '${shiftRole.role.name} - ${shiftRole.shift.title}',
title: '${shiftRole.role.name} - $eventName',
clientName: businessName,
status: status,
date: dateStr,
@@ -117,6 +120,8 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
'worker_name': application.staff.fullName,
'status': 'confirmed',
'photo_url': application.staff.photoUrl,
'phone': application.staff.phone,
'rating': application.staff.averageRating,
});
}
return grouped;
@@ -145,7 +150,7 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
if (timestamp == null) {
return '';
}
final DateTime dateTime = timestamp.toDateTime();
final DateTime dateTime = timestamp.toDateTime().toLocal();
return DateFormat('HH:mm').format(dateTime);
}

View File

@@ -23,6 +23,7 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
final GetOrdersUseCase _getOrdersUseCase;
final GetAcceptedApplicationsForDayUseCase _getAcceptedAppsUseCase;
int _requestId = 0;
void _init() {
updateWeekOffset(0); // Initialize calendar days
@@ -33,6 +34,7 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
required DateTime rangeEnd,
required DateTime dayForApps,
}) async {
final int requestId = ++_requestId;
emit(state.copyWith(status: ViewOrdersStatus.loading));
try {
final List<OrderItem> orders = await _getOrdersUseCase(
@@ -42,6 +44,9 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
OrdersDayArguments(day: dayForApps),
);
final List<OrderItem> updatedOrders = _applyApplications(orders, apps);
if (requestId != _requestId) {
return;
}
emit(
state.copyWith(
status: ViewOrdersStatus.success,
@@ -50,6 +55,9 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
);
_updateDerivedState();
} catch (_) {
if (requestId != _requestId) {
return;
}
emit(state.copyWith(status: ViewOrdersStatus.failure));
}
}
@@ -88,6 +96,28 @@ class ViewOrdersCubit extends Cubit<ViewOrdersState> {
);
}
void jumpToDate(DateTime date) {
final DateTime target = DateTime(date.year, date.month, date.day);
final DateTime startDate = _calculateCalendarDays(0).first;
final int diffDays = target.difference(startDate).inDays;
final int targetOffset = (diffDays / 7).floor();
final List<DateTime> calendarDays = _calculateCalendarDays(targetOffset);
emit(
state.copyWith(
weekOffset: targetOffset,
calendarDays: calendarDays,
selectedDate: target,
),
);
_loadOrdersForRange(
rangeStart: calendarDays.first,
rangeEnd: calendarDays.last,
dayForApps: target,
);
}
void _updateDerivedState() {
final List<OrderItem> filteredOrders = _calculateFilteredOrders(state);
final int activeCount = _calculateCategoryCount('active');

View File

@@ -20,24 +20,53 @@ import '../navigation/view_orders_navigator.dart';
/// - Adhering to the project's Design System.
class ViewOrdersPage extends StatelessWidget {
/// Creates a [ViewOrdersPage].
const ViewOrdersPage({super.key});
const ViewOrdersPage({super.key, this.initialDate});
final DateTime? initialDate;
@override
Widget build(BuildContext context) {
return BlocProvider<ViewOrdersCubit>(
create: (BuildContext context) => Modular.get<ViewOrdersCubit>(),
child: const ViewOrdersView(),
child: ViewOrdersView(initialDate: initialDate),
);
}
}
/// The internal view implementation for [ViewOrdersPage].
class ViewOrdersView extends StatelessWidget {
class ViewOrdersView extends StatefulWidget {
/// Creates a [ViewOrdersView].
const ViewOrdersView({super.key});
const ViewOrdersView({super.key, this.initialDate});
final DateTime? initialDate;
@override
State<ViewOrdersView> createState() => _ViewOrdersViewState();
}
class _ViewOrdersViewState extends State<ViewOrdersView> {
bool _didInitialJump = false;
ViewOrdersCubit? _cubit;
@override
void initState() {
super.initState();
if (widget.initialDate != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
if (_didInitialJump) return;
_didInitialJump = true;
_cubit ??= BlocProvider.of<ViewOrdersCubit>(context);
_cubit!.jumpToDate(widget.initialDate!);
});
}
}
@override
Widget build(BuildContext context) {
if (_cubit == null) {
_cubit = BlocProvider.of<ViewOrdersCubit>(context);
}
return BlocBuilder<ViewOrdersCubit, ViewOrdersState>(
builder: (BuildContext context, ViewOrdersState state) {
final List<DateTime> calendarDays = state.calendarDays;
@@ -218,6 +247,7 @@ class ViewOrdersView extends StatelessWidget {
label: t.client_view_orders.tabs.up_next,
isSelected: state.filterTab == 'all',
tabId: 'all',
count: state.upNextCount,
),
const SizedBox(width: UiConstants.space6),
_buildFilterTab(
@@ -225,7 +255,7 @@ class ViewOrdersView extends StatelessWidget {
label: t.client_view_orders.tabs.active,
isSelected: state.filterTab == 'active',
tabId: 'active',
count: state.activeCount + state.upNextCount,
count: state.activeCount,
),
const SizedBox(width: UiConstants.space6),
_buildFilterTab(

View File

@@ -3,9 +3,12 @@ import 'package:design_system/design_system.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart';
import 'package:url_launcher/url_launcher.dart';
import '../blocs/view_orders_cubit.dart';
/// A rich card displaying details of a client order/shift.
///
@@ -30,7 +33,10 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) => _OrderEditSheet(order: order),
builder: (BuildContext context) => _OrderEditSheet(
order: order,
onUpdated: () => this.context.read<ViewOrdersCubit>().updateWeekOffset(0),
),
);
}
@@ -192,19 +198,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
order.clientName,
style: UiTypography.body3r.textSecondary,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6,
),
child: Text(
'',
style: UiTypography.body3r.textInactive,
),
),
Text(
_formatDate(dateStr: order.date),
style: UiTypography.body3m.textSecondary,
),
const SizedBox(width: 0),
],
),
const SizedBox(height: UiConstants.space2),
@@ -241,14 +235,16 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
onTap: () => _openEditSheet(order: order),
),
const SizedBox(width: UiConstants.space2),
_buildHeaderIconButton(
icon: _expanded
? UiIcons.chevronUp
: UiIcons.chevronDown,
color: UiColors.iconSecondary,
bgColor: UiColors.bgSecondary,
onTap: () => setState(() => _expanded = !_expanded),
),
if (order.confirmedApps.isNotEmpty)
_buildHeaderIconButton(
icon: _expanded
? UiIcons.chevronUp
: UiIcons.chevronDown,
color: UiColors.iconSecondary,
bgColor: UiColors.bgSecondary,
onTap: () =>
setState(() => _expanded = !_expanded),
),
],
),
],
@@ -276,8 +272,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
_buildStatDivider(),
_buildStatItem(
icon: UiIcons.users,
value:
'${order.filled > 0 ? order.filled : order.workersNeeded}',
value: '${order.workersNeeded}',
label: 'Workers',
),
],
@@ -320,7 +315,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
),
const SizedBox(width: 8),
Text(
'${order.filled}/${order.workersNeeded} Workers Filled',
'${order.workersNeeded} Workers Filled',
style: UiTypography.body2m.textPrimary,
),
],
@@ -492,6 +487,7 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
/// Builds a detailed row for a worker.
Widget _buildWorkerRow(Map<String, dynamic> app) {
final String? phone = app['phone'] as String?;
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
@@ -522,9 +518,19 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
const SizedBox(height: 2),
Row(
children: <Widget>[
const Icon(UiIcons.star, size: 10, color: UiColors.accent),
const SizedBox(width: 2),
Text('4.8', style: UiTypography.footnote2r.textSecondary),
if ((app['rating'] as num?) != null &&
(app['rating'] as num) > 0) ...<Widget>[
const Icon(
UiIcons.star,
size: 10,
color: UiColors.accent,
),
const SizedBox(width: 2),
Text(
(app['rating'] as num).toStringAsFixed(1),
style: UiTypography.footnote2r.textSecondary,
),
],
if (app['check_in_time'] != null) ...<Widget>[
const SizedBox(width: 8),
Container(
@@ -543,20 +549,70 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
),
),
),
] else if ((app['status'] as String?)?.isNotEmpty ?? false) ...<Widget>[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 1,
),
decoration: BoxDecoration(
color: UiColors.bgSecondary,
borderRadius: BorderRadius.circular(4),
),
child: Text(
(app['status'] as String).toUpperCase(),
style: UiTypography.titleUppercase4m.copyWith(
color: UiColors.textSecondary,
),
),
),
],
],
),
],
),
),
_buildActionIconButton(icon: UiIcons.phone, onTap: () {}),
const SizedBox(width: 8),
_buildActionIconButton(icon: UiIcons.messageCircle, onTap: () {}),
if (phone != null && phone.isNotEmpty) ...<Widget>[
_buildActionIconButton(
icon: UiIcons.phone,
onTap: () => _confirmAndCall(phone),
),
],
],
),
);
}
Future<void> _confirmAndCall(String phone) async {
final bool? shouldCall = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Call'),
content: Text('Do you want to call $phone?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Call'),
),
],
);
},
);
if (shouldCall != true) {
return;
}
final Uri uri = Uri(scheme: 'tel', path: phone);
await launchUrl(uri);
}
/// Specialized action button for worker rows.
Widget _buildActionIconButton({
required IconData icon,
@@ -644,9 +700,13 @@ class _ShiftRoleKey {
/// A sophisticated bottom sheet for editing an existing order,
/// following the Unified Order Flow prototype and matching OneTimeOrderView.
class _OrderEditSheet extends StatefulWidget {
const _OrderEditSheet({required this.order});
const _OrderEditSheet({
required this.order,
this.onUpdated,
});
final OrderItem order;
final VoidCallback? onUpdated;
@override
State<_OrderEditSheet> createState() => _OrderEditSheetState();
@@ -658,6 +718,7 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
late TextEditingController _dateController;
late TextEditingController _globalLocationController;
late TextEditingController _orderNameController;
late List<Map<String, dynamic>> _positions;
@@ -667,6 +728,8 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
List<Vendor> _vendors = const <Vendor>[];
Vendor? _selectedVendor;
List<_RoleOption> _roles = const <_RoleOption>[];
List<dc.ListTeamHubsByOwnerIdTeamHubs> _hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
dc.ListTeamHubsByOwnerIdTeamHubs? _selectedHub;
String? _shiftId;
List<_ShiftRoleKey> _originalShiftRoles = const <_ShiftRoleKey>[];
@@ -678,6 +741,7 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
_globalLocationController = TextEditingController(
text: widget.order.locationAddress,
);
_orderNameController = TextEditingController();
_positions = <Map<String, dynamic>>[
<String, dynamic>{
@@ -700,6 +764,7 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
void dispose() {
_dateController.dispose();
_globalLocationController.dispose();
_orderNameController.dispose();
super.dispose();
}
@@ -728,6 +793,7 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
final List<dc.ListShiftRolesByBusinessAndOrderShiftRoles> shiftRoles =
result.data.shiftRoles;
if (shiftRoles.isEmpty) {
await _loadHubsAndSelect();
return;
}
@@ -737,13 +803,14 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
final String dateText = orderDate == null
? widget.order.date
: DateFormat('yyyy-MM-dd').format(orderDate);
final String location = firstShift.order.location ??
final String location = firstShift.order.teamHub?.hubName ??
firstShift.locationAddress ??
firstShift.location ??
widget.order.locationAddress;
_dateController.text = dateText;
_globalLocationController.text = location;
_orderNameController.text = firstShift.order.eventName ?? '';
_shiftId = shiftRoles.first.shiftId;
final List<Map<String, dynamic>> positions =
@@ -774,6 +841,13 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
.toList();
await _loadVendorsAndSelect(firstShift.order.vendorId);
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub?
teamHub = firstShift.order.teamHub;
await _loadHubsAndSelect(
placeId: teamHub?.placeId,
hubName: teamHub?.hubName,
address: teamHub?.address,
);
if (mounted) {
setState(() {
@@ -786,6 +860,75 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
}
}
Future<void> _loadHubsAndSelect({
String? placeId,
String? hubName,
String? address,
}) async {
final String? businessId =
dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return;
}
try {
final QueryResult<
dc.ListTeamHubsByOwnerIdData,
dc.ListTeamHubsByOwnerIdVariables> result = await _dataConnect
.listTeamHubsByOwnerId(ownerId: businessId)
.execute();
final List<dc.ListTeamHubsByOwnerIdTeamHubs> hubs = result.data.teamHubs;
dc.ListTeamHubsByOwnerIdTeamHubs? selected;
if (placeId != null && placeId.isNotEmpty) {
for (final dc.ListTeamHubsByOwnerIdTeamHubs hub in hubs) {
if (hub.placeId == placeId) {
selected = hub;
break;
}
}
}
if (selected == null && hubName != null && hubName.isNotEmpty) {
for (final dc.ListTeamHubsByOwnerIdTeamHubs hub in hubs) {
if (hub.hubName == hubName) {
selected = hub;
break;
}
}
}
if (selected == null && address != null && address.isNotEmpty) {
for (final dc.ListTeamHubsByOwnerIdTeamHubs hub in hubs) {
if (hub.address == address) {
selected = hub;
break;
}
}
}
selected ??= hubs.isNotEmpty ? hubs.first : null;
if (mounted) {
setState(() {
_hubs = hubs;
_selectedHub = selected;
if (selected != null) {
_globalLocationController.text = selected.address;
}
});
}
} catch (_) {
if (mounted) {
setState(() {
_hubs = const <dc.ListTeamHubsByOwnerIdTeamHubs>[];
_selectedHub = null;
});
}
}
}
Future<void> _loadVendorsAndSelect(String? selectedVendorId) async {
try {
final QueryResult<dc.ListVendorsData, void> result =
@@ -874,7 +1017,7 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
String _formatTimeForField(Timestamp? value) {
if (value == null) return '';
try {
return DateFormat('HH:mm').format(value.toDateTime());
return DateFormat('HH:mm').format(value.toDateTime().toLocal());
} catch (_) {
return '';
}
@@ -948,7 +1091,8 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
}
Timestamp _toTimestamp(DateTime date) {
final int millis = date.millisecondsSinceEpoch;
final DateTime utc = date.toUtc();
final int millis = utc.millisecondsSinceEpoch;
final int seconds = millis ~/ 1000;
final int nanos = (millis % 1000) * 1000000;
return Timestamp(nanos, seconds);
@@ -987,7 +1131,10 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
}
final DateTime orderDate = _parseDate(_dateController.text);
final String location = _globalLocationController.text;
final dc.ListTeamHubsByOwnerIdTeamHubs? selectedHub = _selectedHub;
if (selectedHub == null) {
return;
}
int totalWorkers = 0;
double shiftCost = 0;
@@ -1071,19 +1218,32 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
.execute();
}
final DateTime orderDateOnly = DateTime.utc(
orderDate.year,
orderDate.month,
orderDate.day,
);
await _dataConnect
.updateOrder(id: widget.order.orderId)
.updateOrder(id: widget.order.orderId, teamHubId: selectedHub.id)
.vendorId(_selectedVendor?.id)
.location(location)
.date(_toTimestamp(orderDate))
.date(_toTimestamp(orderDateOnly))
.eventName(_orderNameController.text)
.execute();
await _dataConnect
.updateShift(id: _shiftId!)
.title('shift 1 ${DateFormat('yyyy-MM-dd').format(orderDate)}')
.date(_toTimestamp(orderDate))
.location(location)
.locationAddress(location)
.date(_toTimestamp(orderDateOnly))
.location(selectedHub.hubName)
.locationAddress(selectedHub.address)
.latitude(selectedHub.latitude)
.longitude(selectedHub.longitude)
.placeId(selectedHub.placeId)
.city(selectedHub.city)
.state(selectedHub.state)
.street(selectedHub.street)
.country(selectedHub.country)
.workersNeeded(totalWorkers)
.cost(shiftCost)
.durationDays(1)
@@ -1185,11 +1345,57 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
),
const SizedBox(height: UiConstants.space4),
_buildSectionHeader('LOCATION'),
_buildSectionHeader('ORDER NAME'),
UiTextField(
controller: _globalLocationController,
hintText: 'Business address',
prefixIcon: UiIcons.mapPin,
controller: _orderNameController,
hintText: 'Order name',
prefixIcon: UiIcons.briefcase,
),
const SizedBox(height: UiConstants.space4),
_buildSectionHeader('HUB'),
Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3,
),
height: 48,
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusMd,
border: Border.all(color: UiColors.border),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<dc.ListTeamHubsByOwnerIdTeamHubs>(
isExpanded: true,
value: _selectedHub,
icon: const Icon(
UiIcons.chevronDown,
size: 18,
color: UiColors.iconSecondary,
),
onChanged:
(dc.ListTeamHubsByOwnerIdTeamHubs? hub) {
if (hub != null) {
setState(() {
_selectedHub = hub;
_globalLocationController.text = hub.address;
});
}
},
items: _hubs.map(
(dc.ListTeamHubsByOwnerIdTeamHubs hub) {
return DropdownMenuItem<
dc.ListTeamHubsByOwnerIdTeamHubs>(
value: hub,
child: Text(
hub.hubName,
style: UiTypography.body2m.textPrimary,
),
);
},
).toList(),
),
),
),
const SizedBox(height: UiConstants.space6),
@@ -1370,7 +1576,19 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
child: _buildInlineTimeInput(
label: 'Start',
value: pos['start_time'],
onTap: () {},
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null && context.mounted) {
_updatePosition(
index,
'start_time',
picked.format(context),
);
}
},
),
),
const SizedBox(width: UiConstants.space2),
@@ -1378,7 +1596,19 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
child: _buildInlineTimeInput(
label: 'End',
value: pos['end_time'],
onTap: () {},
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null && context.mounted) {
_updatePosition(
index,
'end_time',
picked.format(context),
);
}
},
),
),
const SizedBox(width: UiConstants.space2),
@@ -1766,7 +1996,10 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
onPressed: () async {
setState(() => _isLoading = true);
await _saveOrderChanges();
if (mounted) Navigator.pop(context);
if (mounted) {
widget.onUpdated?.call();
Navigator.pop(context);
}
},
),
),

View File

@@ -43,6 +43,23 @@ class ViewOrdersModule extends Module {
@override
void routes(RouteManager r) {
r.child('/', child: (BuildContext context) => const ViewOrdersPage());
r.child(
'/',
child: (BuildContext context) {
final Object? args = Modular.args.data;
DateTime? initialDate;
if (args is DateTime) {
initialDate = args;
} else if (args is Map<String, dynamic>) {
final Object? rawDate = args['initialDate'];
if (rawDate is DateTime) {
initialDate = rawDate;
} else if (rawDate is String) {
initialDate = DateTime.tryParse(rawDate);
}
}
return ViewOrdersPage(initialDate: initialDate);
},
);
}
}

View File

@@ -1,7 +1,7 @@
name: view_orders
description: Client View Orders feature package
publish_to: 'none'
version: 1.0.0+1
version: 0.0.1
resolution: workspace
environment:

View File

@@ -16,6 +16,6 @@ extension AuthNavigator on IModularNavigator {
/// Navigates to the worker home (external to this module).
void pushWorkerHome() {
pushNamed('/worker-main/home/');
pushNamed('/worker-main/home');
}
}

View File

@@ -1,5 +1,3 @@
// ignore: unused_import
// import 'package:data_connect/data_connect.dart';
import '../../domain/entities/payment_summary.dart';
import '../../domain/entities/payment_transaction.dart';
import '../../domain/repositories/payments_repository.dart';

View File

@@ -3,6 +3,7 @@ import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/src/session/staff_session_store.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/payments_repository.dart';
import '../datasources/payments_remote_datasource.dart';
extension TimestampExt on Timestamp {
DateTime toDate() {
@@ -11,11 +12,6 @@ extension TimestampExt on Timestamp {
}
/// Implementation of [PaymentsRepository].
///
/// This class handles the retrieval of payment data by delegating to the
/// [FinancialRepositoryMock] from the data connect package.
///
/// It resides in the data layer and depends on the domain layer for the repository interface.
class PaymentsRepositoryImpl implements PaymentsRepository {
PaymentsRepositoryImpl();

View File

@@ -0,0 +1,23 @@
import 'package:equatable/equatable.dart';
class PaymentSummary extends Equatable {
final double weeklyEarnings;
final double monthlyEarnings;
final double pendingEarnings;
final double totalEarnings;
const PaymentSummary({
required this.weeklyEarnings,
required this.monthlyEarnings,
required this.pendingEarnings,
required this.totalEarnings,
});
@override
List<Object?> get props => [
weeklyEarnings,
monthlyEarnings,
pendingEarnings,
totalEarnings,
];
}

View File

@@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
class PaymentTransaction extends Equatable {
final String id;
final String title;
final String location;
final String address;
final String workedTime;
final double amount;
final String status;
final int hours;
final double rate;
final DateTime date;
const PaymentTransaction({
required this.id,
required this.title,
required this.location,
required this.address,
required this.workedTime,
required this.amount,
required this.status,
required this.hours,
required this.rate,
required this.date,
});
@override
List<Object?> get props => [
id,
title,
location,
address,
workedTime,
amount,
status,
hours,
rate,
date,
];
}

View File

@@ -1,10 +1,14 @@
import 'package:krow_domain/krow_domain.dart';
import '../entities/payment_summary.dart';
import '../entities/payment_transaction.dart';
/// Repository interface for Payments feature.
///
/// Defines the contract for data access related to staff payments.
/// Implementations of this interface should reside in the data layer.
abstract class PaymentsRepository {
/// Fetches the list of payments for the current staff member.
Future<List<StaffPayment>> getPayments();
/// Fetches the payment summary (earnings).
Future<PaymentSummary> getPaymentSummary();
/// Fetches the payment history for a specific period.
Future<List<PaymentTransaction>> getPaymentHistory(String period);
}

View File

@@ -1,20 +1,19 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../arguments/get_payment_history_arguments.dart';
import '../entities/payment_transaction.dart';
import '../repositories/payments_repository.dart';
/// Use case to retrieve payment history filtered by a period.
///
/// This use case delegates the data retrieval to [PaymentsRepository].
class GetPaymentHistoryUseCase extends UseCase<GetPaymentHistoryArguments, List<StaffPayment>> {
class GetPaymentHistoryUseCase extends UseCase<GetPaymentHistoryArguments, List<PaymentTransaction>> {
final PaymentsRepository repository;
/// Creates a [GetPaymentHistoryUseCase].
GetPaymentHistoryUseCase(this.repository);
@override
Future<List<StaffPayment>> call(GetPaymentHistoryArguments arguments) async {
// TODO: Implement filtering by period
return await repository.getPayments();
Future<List<PaymentTransaction>> call(GetPaymentHistoryArguments arguments) async {
return await repository.getPaymentHistory(arguments.period);
}
}

View File

@@ -1,19 +1,16 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../entities/payment_summary.dart';
import '../repositories/payments_repository.dart';
/// Use case to retrieve payment summary information.
///
/// It fetches the full list of payments, which ideally should be aggregated
/// by the presentation layer or a specific data source method.
class GetPaymentSummaryUseCase extends NoInputUseCase<List<StaffPayment>> {
class GetPaymentSummaryUseCase extends NoInputUseCase<PaymentSummary> {
final PaymentsRepository repository;
/// Creates a [GetPaymentSummaryUseCase].
GetPaymentSummaryUseCase(this.repository);
@override
Future<List<StaffPayment>> call() async {
return await repository.getPayments();
Future<PaymentSummary> call() async {
return await repository.getPaymentSummary();
}
}

View File

@@ -1,9 +1,9 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/arguments/get_payment_history_arguments.dart';
import '../../../domain/usecases/get_payment_summary_usecase.dart';
import '../../../domain/entities/payment_summary.dart';
import '../../../domain/entities/payment_transaction.dart';
import '../../../domain/usecases/get_payment_history_usecase.dart';
import '../../models/payment_stats.dart';
import '../../../domain/usecases/get_payment_summary_usecase.dart';
import 'payments_event.dart';
import 'payments_state.dart';
@@ -25,14 +25,13 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
) async {
emit(PaymentsLoading());
try {
final List<StaffPayment> allPayments = await getPaymentSummary();
final PaymentStats stats = _calculateStats(allPayments);
final PaymentSummary currentSummary = await getPaymentSummary();
final List<StaffPayment> history = await getPaymentHistory(
final List<PaymentTransaction> history = await getPaymentHistory(
const GetPaymentHistoryArguments('week'),
);
emit(PaymentsLoaded(
summary: stats,
summary: currentSummary,
history: history,
activePeriod: 'week',
));
@@ -48,7 +47,7 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
final PaymentsState currentState = state;
if (currentState is PaymentsLoaded) {
try {
final List<StaffPayment> newHistory = await getPaymentHistory(
final List<PaymentTransaction> newHistory = await getPaymentHistory(
GetPaymentHistoryArguments(event.period),
);
emit(currentState.copyWith(
@@ -60,38 +59,4 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
}
}
}
PaymentStats _calculateStats(List<StaffPayment> payments) {
double total = 0;
double pending = 0;
double weekly = 0;
double monthly = 0;
final DateTime now = DateTime.now();
for (final StaffPayment p in payments) {
// Assuming all payments count towards total history
total += p.amount;
if (p.status == PaymentStatus.pending) {
pending += p.amount;
}
if (p.paidAt != null) {
if (now.difference(p.paidAt!).inDays < 7) {
weekly += p.amount;
}
if (now.month == p.paidAt!.month && now.year == p.paidAt!.year) {
monthly += p.amount;
}
}
}
return PaymentStats(
totalEarnings: total,
pendingEarnings: pending,
weeklyEarnings: weekly,
monthlyEarnings: monthly,
);
}
}

View File

@@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../models/payment_stats.dart';
import '../../../domain/entities/payment_summary.dart';
import '../../../domain/entities/payment_transaction.dart';
abstract class PaymentsState extends Equatable {
const PaymentsState();
@@ -14,8 +14,8 @@ class PaymentsInitial extends PaymentsState {}
class PaymentsLoading extends PaymentsState {}
class PaymentsLoaded extends PaymentsState {
final PaymentStats summary;
final List<StaffPayment> history;
final PaymentSummary summary;
final List<PaymentTransaction> history;
final String activePeriod;
const PaymentsLoaded({
@@ -25,8 +25,8 @@ class PaymentsLoaded extends PaymentsState {
});
PaymentsLoaded copyWith({
PaymentStats? summary,
List<StaffPayment>? history,
PaymentSummary? summary,
List<PaymentTransaction>? history,
String? activePeriod,
}) {
return PaymentsLoaded(

View File

@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/entities/payment_transaction.dart';
import '../blocs/payments/payments_bloc.dart';
import '../blocs/payments/payments_event.dart';
import '../blocs/payments/payments_state.dart';
@@ -184,19 +184,19 @@ class _PaymentsPageState extends State<PaymentsPage> {
),
const SizedBox(height: 12),
Column(
children: state.history.map((StaffPayment payment) {
children: state.history.map((PaymentTransaction payment) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: PaymentHistoryItem(
amount: payment.amount,
title: 'Assignment ${payment.assignmentId}',
location: 'Location', // TODO: Fetch from assignment
address: '',
date: payment.paidAt != null ? DateFormat('E, MMM d').format(payment.paidAt!) : 'Pending',
workedTime: '00:00 - 00:00', // TODO: Fetch from assignment
hours: 0,
rate: 0,
status: payment.status.toString().split('.').last,
title: payment.title,
location: payment.location,
address: payment.address,
date: DateFormat('E, MMM d').format(payment.date),
workedTime: payment.workedTime,
hours: payment.hours,
rate: payment.rate,
status: payment.status,
),
);
}).toList(),

View File

@@ -32,4 +32,5 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter_lints: ^6.0.0

View File

@@ -2,9 +2,10 @@ name: staff_certificates
description: Staff certificates feature
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.10.0 <4.0.0'
dependencies:
flutter:

View File

@@ -1,778 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: cd83f7d6bd4e4c0b0b4fef802e8796784032e1cc23d7b0e982cf5d05d9bbe182
url: "https://pub.dev"
source: hosted
version: "1.3.66"
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
auto_injector:
dependency: transitive
description:
name: auto_injector
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bloc:
dependency: "direct main"
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
core_localization:
dependency: "direct main"
description:
path: "../../../../../core_localization"
relative: true
source: path
version: "0.0.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csv:
dependency: transitive
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
design_system:
dependency: "direct main"
description:
path: "../../../../../design_system"
relative: true
source: path
version: "0.0.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.5"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
firebase_app_check:
dependency: transitive
description:
name: firebase_app_check
sha256: "45f0d279ea7ae4eac1867a4c85aa225761e3ac0ccf646386a860b2bc16581f76"
url: "https://pub.dev"
source: hosted
version: "0.4.1+4"
firebase_app_check_platform_interface:
dependency: transitive
description:
name: firebase_app_check_platform_interface
sha256: e32b4e6adeaac207a6f7afe0906d97c0811de42fb200d9b6317a09155de65e2b
url: "https://pub.dev"
source: hosted
version: "0.2.1+4"
firebase_app_check_web:
dependency: transitive
description:
name: firebase_app_check_web
sha256: "2cbc8a18a34813a7e31d7b30f989973087421cd5d0e397b4dd88a90289aa2bed"
url: "https://pub.dev"
source: hosted
version: "0.2.2+2"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: b20d1540460814c5984474c1e9dd833bdbcff6ecd8d6ad86cc9da8cfd581c172
url: "https://pub.dev"
source: hosted
version: "6.1.4"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: fd0225320b6bbc92460c86352d16b60aea15f9ef88292774cca97b0522ea9f72
url: "https://pub.dev"
source: hosted
version: "8.1.6"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: be7dccb263b89fbda2a564de9d8193118196e8481ffb937222a025cdfdf82c40
url: "https://pub.dev"
source: hosted
version: "6.1.2"
firebase_core:
dependency: transitive
description:
name: firebase_core
sha256: "923085c881663ef685269b013e241b428e1fb03cdd0ebde265d9b40ff18abf80"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64
url: "https://pub.dev"
source: hosted
version: "6.0.2"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "83e7356c704131ca4d8d8dd57e360d8acecbca38b1a3705c7ae46cc34c708084"
url: "https://pub.dev"
source: hosted
version: "3.4.0"
firebase_data_connect:
dependency: "direct main"
description:
name: firebase_data_connect
sha256: "01d0f8e33c520a6e6f59cf5ac6ff281d1927f7837f094fa8eb5fdb0b1b328ad8"
url: "https://pub.dev"
source: hosted
version: "0.2.2+2"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.6"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_modular:
dependency: "direct main"
description:
name: flutter_modular
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
flutter_test:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
font_awesome_flutter:
dependency: transitive
description:
name: font_awesome_flutter
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
url: "https://pub.dev"
source: hosted
version: "10.12.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_fonts:
dependency: transitive
description:
name: google_fonts
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
google_identity_services_web:
dependency: transitive
description:
name: google_identity_services_web
sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454"
url: "https://pub.dev"
source: hosted
version: "0.3.3+1"
googleapis_auth:
dependency: transitive
description:
name: googleapis_auth
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
url: "https://pub.dev"
source: hosted
version: "1.6.0"
grpc:
dependency: transitive
description:
name: grpc
sha256: e93ee3bce45c134bf44e9728119102358c7cd69de7832d9a874e2e74eb8cab40
url: "https://pub.dev"
source: hosted
version: "3.2.4"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http2:
dependency: transitive
description:
name: http2
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: transitive
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
krow_core:
dependency: "direct main"
description:
path: "../../../../../core"
relative: true
source: path
version: "0.0.1"
krow_data_connect:
dependency: "direct main"
description:
path: "../../../../../data_connect"
relative: true
source: path
version: "0.0.1"
krow_domain:
dependency: "direct main"
description:
path: "../../../../../domain"
relative: true
source: path
version: "0.0.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lucide_icons:
dependency: "direct main"
description:
name: lucide_icons
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
url: "https://pub.dev"
source: hosted
version: "0.257.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
modular_core:
dependency: transitive
description:
name: modular_core
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "7fd0c4d8ac8980011753b9bdaed2bf15111365924cdeeeaeb596214ea2b03537"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
protobuf:
dependency: transitive
description:
name: protobuf
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
provider:
dependency: transitive
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
result_dart:
dependency: transitive
description:
name: result_dart
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev"
source: hosted
version: "2.4.18"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
slang:
dependency: transitive
description:
name: slang
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
slang_flutter:
dependency: transitive
description:
name: slang_flutter
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.7 <4.0.0"
flutter: ">=3.38.4"

View File

@@ -2,6 +2,7 @@ name: staff_documents
description: Staff Documents feature.
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: '>=3.10.0 <4.0.0'

View File

@@ -0,0 +1,76 @@
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart';
class TaxFormMapper {
static TaxForm fromDataConnect(dc.GetTaxFormsByStaffIdTaxForms form) {
// Construct the legacy map for the entity
final Map<String, dynamic> formData = {
'firstName': form.firstName,
'lastName': form.lastName,
'middleInitial': form.mInitial,
'otherLastNames': form.oLastName,
'dob': _formatDate(form.dob),
'ssn': form.socialSN.toString(),
'email': form.email,
'phone': form.phone,
'address': form.address,
'aptNumber': form.apt,
'city': form.city,
'state': form.state,
'zipCode': form.zipCode,
// I-9 Fields
'citizenshipStatus': form.citizen?.stringValue,
'uscisNumber': form.uscis,
'passportNumber': form.passportNumber,
'countryIssuance': form.countryIssue,
'preparerUsed': form.prepartorOrTranslator,
// W-4 Fields
'filingStatus': form.marital?.stringValue,
'multipleJobs': form.multipleJob,
'qualifyingChildren': form.childrens,
'otherDependents': form.otherDeps,
'otherIncome': form.otherInconme?.toString(),
'deductions': form.deductions?.toString(),
'extraWithholding': form.extraWithholding?.toString(),
'signature': form.signature,
};
String title = '';
String subtitle = '';
String description = '';
if (form.formType == dc.TaxFormType.I9) {
title = 'Form I-9';
subtitle = 'Employment Eligibility Verification';
description = 'Required for all new hires to verify identity.';
} else {
title = 'Form W-4';
subtitle = 'Employee\'s Withholding Certificate';
description = 'Determines federal income tax withholding.';
}
return TaxFormAdapter.fromPrimitives(
id: form.id,
type: form.formType.stringValue,
title: title,
subtitle: subtitle,
description: description,
status: form.status.stringValue,
staffId: form.staffId,
formData: formData,
updatedAt: form.updatedAt?.toDateTime(),
);
}
static String? _formatDate(Timestamp? timestamp) {
if (timestamp == null) return null;
final DateTime date = timestamp.toDateTime();
return '${date.month.toString().padLeft(2, '0')}/${date.day.toString().padLeft(2, '0')}/${date.year}';
}
}

View File

@@ -6,6 +6,7 @@ import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/tax_forms_repository.dart';
import '../mappers/tax_form_mapper.dart';
class TaxFormsRepositoryImpl implements TaxFormsRepository {
TaxFormsRepositoryImpl({
@@ -33,11 +34,11 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
@override
Future<List<TaxForm>> getTaxForms() async {
final String staffId = _getStaffId();
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables>
result =
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
await dataConnect.getTaxFormsByStaffId(staffId: staffId).execute();
final List<TaxForm> forms = result.data.taxForms.map((dc.GetTaxFormsBystaffIdTaxForms e) => _mapToEntity(e)).toList();
final List<TaxForm> forms = result.data.taxForms.map(TaxFormMapper.fromDataConnect).toList();
// Check if required forms exist, create if not.
final Set<TaxFormType> typesPresent = forms.map((TaxForm f) => f.type).toSet();
@@ -53,98 +54,161 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
}
if (createdNew) {
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables>
result2 =
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
return result2.data.taxForms.map((dc.GetTaxFormsBystaffIdTaxForms e) => _mapToEntity(e)).toList();
await dataConnect.getTaxFormsByStaffId(staffId: staffId).execute();
return result2.data.taxForms.map(TaxFormMapper.fromDataConnect).toList();
}
return forms;
}
Future<void> _createInitialForm(String staffId, TaxFormType type) async {
String title = '';
String subtitle = '';
String description = '';
if (type == TaxFormType.i9) {
title = 'Form I-9';
subtitle = 'Employment Eligibility Verification';
description = 'Required for all new hires to verify identity.';
} else {
title = 'Form W-4';
subtitle = 'Employee\'s Withholding Certificate';
description = 'Determines federal income tax withholding.';
}
await dataConnect
.createTaxForm(
staffId: staffId,
formType: dc.TaxFormType.values.byName(TaxFormAdapter.typeToString(type)),
title: title,
formType:
dc.TaxFormType.values.byName(TaxFormAdapter.typeToString(type)),
firstName: '',
lastName: '',
socialSN: 0,
address: '',
status: dc.TaxFormStatus.NOT_STARTED,
)
.subtitle(subtitle)
.description(description)
.status(dc.TaxFormStatus.NOT_STARTED)
.execute();
}
@override
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data) async {
final String staffId = _getStaffId();
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
result =
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
final String targetTypeString = TaxFormAdapter.typeToString(type);
final dc.GetTaxFormsBystaffIdTaxForms form = result.data.taxForms.firstWhere(
(dc.GetTaxFormsBystaffIdTaxForms e) => e.formType.stringValue == targetTypeString,
orElse: () => throw Exception('Form not found for submission'),
);
// AnyValue expects a scalar, list, or map.
await dataConnect
.updateTaxForm(
id: form.id,
)
.formData(AnyValue.fromJson(data))
.status(dc.TaxFormStatus.SUBMITTED)
.execute();
Future<void> updateI9Form(I9TaxForm form) async {
final Map<String, dynamic> data = form.formData;
final dc.UpdateTaxFormVariablesBuilder builder = dataConnect.updateTaxForm(id: form.id);
_mapCommonFields(builder, data);
_mapI9Fields(builder, data);
await builder.execute();
}
@override
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status) async {
final String staffId = _getStaffId();
final QueryResult<dc.GetTaxFormsBystaffIdData, dc.GetTaxFormsBystaffIdVariables>
result =
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
final String targetTypeString = TaxFormAdapter.typeToString(type);
final dc.GetTaxFormsBystaffIdTaxForms form = result.data.taxForms.firstWhere(
(dc.GetTaxFormsBystaffIdTaxForms e) => e.formType.stringValue == targetTypeString,
orElse: () => throw Exception('Form not found for update'),
);
await dataConnect
.updateTaxForm(
id: form.id,
)
.status(dc.TaxFormStatus.values.byName(TaxFormAdapter.statusToString(status)))
.execute();
Future<void> submitI9Form(I9TaxForm form) async {
final Map<String, dynamic> data = form.formData;
final dc.UpdateTaxFormVariablesBuilder builder = dataConnect.updateTaxForm(id: form.id);
_mapCommonFields(builder, data);
_mapI9Fields(builder, data);
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
}
TaxForm _mapToEntity(dc.GetTaxFormsBystaffIdTaxForms form) {
return TaxFormAdapter.fromPrimitives(
id: form.id,
type: form.formType.stringValue,
title: form.title,
subtitle: form.subtitle,
description: form.description,
status: form.status.stringValue,
staffId: form.staffId,
formData: form.formData, // Adapter expects dynamic
updatedAt: form.updatedAt?.toDateTime(),
);
@override
Future<void> updateW4Form(W4TaxForm form) async {
final Map<String, dynamic> data = form.formData;
final dc.UpdateTaxFormVariablesBuilder builder = dataConnect.updateTaxForm(id: form.id);
_mapCommonFields(builder, data);
_mapW4Fields(builder, data);
await builder.execute();
}
@override
Future<void> submitW4Form(W4TaxForm form) async {
final Map<String, dynamic> data = form.formData;
final dc.UpdateTaxFormVariablesBuilder builder = dataConnect.updateTaxForm(id: form.id);
_mapCommonFields(builder, data);
_mapW4Fields(builder, data);
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
}
void _mapCommonFields(dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
if (data.containsKey('firstName')) builder.firstName(data['firstName'] as String?);
if (data.containsKey('lastName')) builder.lastName(data['lastName'] as String?);
if (data.containsKey('middleInitial')) builder.mInitial(data['middleInitial'] as String?);
if (data.containsKey('otherLastNames')) builder.oLastName(data['otherLastNames'] as String?);
if (data.containsKey('dob')) {
final String dob = data['dob'] as String;
// Handle both ISO string and MM/dd/yyyy manual entry
DateTime? date;
try {
date = DateTime.parse(dob);
} catch (_) {
try {
// Fallback minimal parse for mm/dd/yyyy
final List<String> parts = dob.split('/');
if (parts.length == 3) {
date = DateTime(
int.parse(parts[2]),
int.parse(parts[0]),
int.parse(parts[1]),
);
}
} catch (_) {}
}
if (date != null) {
final int ms = date.millisecondsSinceEpoch;
final int seconds = (ms / 1000).floor();
builder.dob(Timestamp(0, seconds));
}
}
if (data.containsKey('ssn') && data['ssn']?.toString().isNotEmpty == true) {
builder.socialSN(int.tryParse(data['ssn'].toString().replaceAll(RegExp(r'\D'), '')) ?? 0);
}
if (data.containsKey('email')) builder.email(data['email'] as String?);
if (data.containsKey('phone')) builder.phone(data['phone'] as String?);
if (data.containsKey('address')) builder.address(data['address'] as String?);
if (data.containsKey('aptNumber')) builder.apt(data['aptNumber'] as String?);
if (data.containsKey('city')) builder.city(data['city'] as String?);
if (data.containsKey('state')) builder.state(data['state'] as String?);
if (data.containsKey('zipCode')) builder.zipCode(data['zipCode'] as String?);
}
void _mapI9Fields(dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
if (data.containsKey('citizenshipStatus')) {
final String status = data['citizenshipStatus'] as String;
// Map string to enum if possible, or handle otherwise.
// Generated enum: CITIZEN, NONCITIZEN_NATIONAL, PERMANENT_RESIDENT, ALIEN_AUTHORIZED
try {
builder.citizen(dc.CitizenshipStatus.values.byName(status.toUpperCase()));
} catch (_) {}
}
if (data.containsKey('uscisNumber')) builder.uscis(data['uscisNumber'] as String?);
if (data.containsKey('passportNumber')) builder.passportNumber(data['passportNumber'] as String?);
if (data.containsKey('countryIssuance')) builder.countryIssue(data['countryIssuance'] as String?);
if (data.containsKey('preparerUsed')) builder.prepartorOrTranslator(data['preparerUsed'] as bool?);
if (data.containsKey('signature')) builder.signature(data['signature'] as String?);
// Note: admissionNumber not in builder based on file read
}
void _mapW4Fields(dc.UpdateTaxFormVariablesBuilder builder, Map<String, dynamic> data) {
if (data.containsKey('cityStateZip')) {
final String csz = data['cityStateZip'] as String;
// Extremely basic split: City, State Zip
final List<String> parts = csz.split(',');
if (parts.length >= 2) {
builder.city(parts[0].trim());
final String stateZip = parts[1].trim();
final List<String> szParts = stateZip.split(' ');
if (szParts.isNotEmpty) builder.state(szParts[0]);
if (szParts.length > 1) builder.zipCode(szParts.last);
}
}
if (data.containsKey('filingStatus')) {
// MARITIAL_STATUS_SINGLE, MARITIAL_STATUS_MARRIED, MARITIAL_STATUS_HEAD
try {
final String status = data['filingStatus'] as String;
// Simple mapping assumptions:
if (status.contains('single')) builder.marital(dc.MaritalStatus.SINGLE);
else if (status.contains('married')) builder.marital(dc.MaritalStatus.MARRIED);
else if (status.contains('head')) builder.marital(dc.MaritalStatus.HEAD);
} catch (_) {}
}
if (data.containsKey('multipleJobs')) builder.multipleJob(data['multipleJobs'] as bool?);
if (data.containsKey('qualifyingChildren')) builder.childrens(data['qualifyingChildren'] as int?);
if (data.containsKey('otherDependents')) builder.otherDeps(data['otherDependents'] as int?);
if (data.containsKey('otherIncome')) {
builder.otherInconme(double.tryParse(data['otherIncome'].toString()));
}
if (data.containsKey('deductions')) {
builder.deductions(double.tryParse(data['deductions'].toString()));
}
if (data.containsKey('extraWithholding')) {
builder.extraWithholding(double.tryParse(data['extraWithholding'].toString()));
}
if (data.containsKey('signature')) builder.signature(data['signature'] as String?);
}
}

View File

@@ -1,26 +0,0 @@
import 'package:equatable/equatable.dart';
enum TaxFormType { i9, w4 }
enum TaxFormStatus { notStarted, inProgress, submitted, approved, rejected }
class TaxFormEntity extends Equatable {
final TaxFormType type;
final String title;
final String subtitle;
final String description;
final TaxFormStatus status;
final DateTime? lastUpdated;
const TaxFormEntity({
required this.type,
required this.title,
required this.subtitle,
required this.description,
this.status = TaxFormStatus.notStarted,
this.lastUpdated,
});
@override
List<Object?> get props => [type, title, subtitle, description, status, lastUpdated];
}

View File

@@ -2,6 +2,8 @@ import 'package:krow_domain/krow_domain.dart';
abstract class TaxFormsRepository {
Future<List<TaxForm>> getTaxForms();
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data);
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status);
Future<void> updateI9Form(I9TaxForm form);
Future<void> submitI9Form(I9TaxForm form);
Future<void> updateW4Form(W4TaxForm form);
Future<void> submitW4Form(W4TaxForm form);
}

View File

@@ -0,0 +1,12 @@
import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SaveI9FormUseCase {
final TaxFormsRepository _repository;
SaveI9FormUseCase(this._repository);
Future<void> call(I9TaxForm form) async {
return _repository.updateI9Form(form);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SaveW4FormUseCase {
final TaxFormsRepository _repository;
SaveW4FormUseCase(this._repository);
Future<void> call(W4TaxForm form) async {
return _repository.updateW4Form(form);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SubmitI9FormUseCase {
final TaxFormsRepository _repository;
SubmitI9FormUseCase(this._repository);
Future<void> call(I9TaxForm form) async {
return _repository.submitI9Form(form);
}
}

View File

@@ -1,12 +0,0 @@
import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SubmitTaxFormUseCase {
final TaxFormsRepository _repository;
SubmitTaxFormUseCase(this._repository);
Future<void> call(TaxFormType type, Map<String, dynamic> data) async {
return _repository.submitForm(type, data);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:krow_domain/krow_domain.dart';
import '../repositories/tax_forms_repository.dart';
class SubmitW4FormUseCase {
final TaxFormsRepository _repository;
SubmitW4FormUseCase(this._repository);
Future<void> call(W4TaxForm form) async {
return _repository.submitW4Form(form);
}
}

View File

@@ -1,13 +1,47 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:uuid/uuid.dart';
import '../../../domain/usecases/submit_tax_form_usecase.dart';
import '../../../domain/usecases/submit_i9_form_usecase.dart';
import 'form_i9_state.dart';
class FormI9Cubit extends Cubit<FormI9State> {
final SubmitTaxFormUseCase _submitTaxFormUseCase;
final SubmitI9FormUseCase _submitI9FormUseCase;
String _formId = '';
FormI9Cubit(this._submitTaxFormUseCase) : super(const FormI9State());
FormI9Cubit(this._submitI9FormUseCase) : super(const FormI9State());
void initialize(TaxForm? form) {
if (form == null || form.formData.isEmpty) {
emit(const FormI9State()); // Reset to empty if no form
return;
}
final Map<String, dynamic> data = form.formData;
_formId = form.id;
emit(FormI9State(
firstName: data['firstName'] as String? ?? '',
lastName: data['lastName'] as String? ?? '',
middleInitial: data['middleInitial'] as String? ?? '',
otherLastNames: data['otherLastNames'] as String? ?? '',
dob: data['dob'] as String? ?? '',
ssn: data['ssn'] as String? ?? '',
email: data['email'] as String? ?? '',
phone: data['phone'] as String? ?? '',
address: data['address'] as String? ?? '',
aptNumber: data['aptNumber'] as String? ?? '',
city: data['city'] as String? ?? '',
state: data['state'] as String? ?? '',
zipCode: data['zipCode'] as String? ?? '',
citizenshipStatus: data['citizenshipStatus'] as String? ?? '',
uscisNumber: data['uscisNumber'] as String? ?? '',
admissionNumber: data['admissionNumber'] as String? ?? '',
passportNumber: data['passportNumber'] as String? ?? '',
countryIssuance: data['countryIssuance'] as String? ?? '',
preparerUsed: data['preparerUsed'] as bool? ?? false,
signature: data['signature'] as String? ?? '',
));
}
void nextStep(int totalSteps) {
if (state.currentStep < totalSteps - 1) {
@@ -52,18 +86,36 @@ class FormI9Cubit extends Cubit<FormI9State> {
Future<void> submit() async {
emit(state.copyWith(status: FormI9Status.submitting));
try {
await _submitTaxFormUseCase(
TaxFormType.i9,
<String, dynamic>{
'firstName': state.firstName,
'lastName': state.lastName,
'middleInitial': state.middleInitial,
'citizenshipStatus': state.citizenshipStatus,
'ssn': state.ssn,
'signature': state.signature,
// ... add other fields as needed for backend
},
final Map<String, dynamic> formData = {
'firstName': state.firstName,
'lastName': state.lastName,
'middleInitial': state.middleInitial,
'otherLastNames': state.otherLastNames,
'dob': state.dob,
'ssn': state.ssn,
'email': state.email,
'phone': state.phone,
'address': state.address,
'aptNumber': state.aptNumber,
'city': state.city,
'state': state.state,
'zipCode': state.zipCode,
'citizenshipStatus': state.citizenshipStatus,
'uscisNumber': state.uscisNumber,
'admissionNumber': state.admissionNumber,
'passportNumber': state.passportNumber,
'countryIssuance': state.countryIssuance,
'preparerUsed': state.preparerUsed,
'signature': state.signature,
};
final I9TaxForm form = I9TaxForm(
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
title: 'Form I-9',
formData: formData,
);
await _submitI9FormUseCase(form);
emit(state.copyWith(status: FormI9Status.success));
} catch (e) {
emit(state.copyWith(

View File

@@ -1,13 +1,47 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:uuid/uuid.dart';
import '../../../domain/usecases/submit_tax_form_usecase.dart';
import '../../../domain/usecases/submit_w4_form_usecase.dart';
import 'form_w4_state.dart';
class FormW4Cubit extends Cubit<FormW4State> {
final SubmitTaxFormUseCase _submitTaxFormUseCase;
final SubmitW4FormUseCase _submitW4FormUseCase;
String _formId = '';
FormW4Cubit(this._submitTaxFormUseCase) : super(const FormW4State());
FormW4Cubit(this._submitW4FormUseCase) : super(const FormW4State());
void initialize(TaxForm? form) {
if (form == null || form.formData.isEmpty) {
emit(const FormW4State()); // Reset
return;
}
final Map<String, dynamic> data = form.formData;
_formId = form.id;
// Combine address parts if needed, or take existing
final String city = data['city'] as String? ?? '';
final String stateVal = data['state'] as String? ?? '';
final String zip = data['zipCode'] as String? ?? '';
final String cityStateZip = '$city, $stateVal $zip'.trim();
emit(FormW4State(
firstName: data['firstName'] as String? ?? '',
lastName: data['lastName'] as String? ?? '',
ssn: data['ssn'] as String? ?? '',
address: data['address'] as String? ?? '',
cityStateZip: cityStateZip.contains(',') ? cityStateZip : '',
filingStatus: data['filingStatus'] as String? ?? '',
multipleJobs: data['multipleJobs'] as bool? ?? false,
qualifyingChildren: data['qualifyingChildren'] as int? ?? 0,
otherDependents: data['otherDependents'] as int? ?? 0,
otherIncome: data['otherIncome'] as String? ?? '',
deductions: data['deductions'] as String? ?? '',
extraWithholding: data['extraWithholding'] as String? ?? '',
signature: data['signature'] as String? ?? '',
));
}
void nextStep(int totalSteps) {
if (state.currentStep < totalSteps - 1) {
@@ -45,18 +79,29 @@ class FormW4Cubit extends Cubit<FormW4State> {
Future<void> submit() async {
emit(state.copyWith(status: FormW4Status.submitting));
try {
await _submitTaxFormUseCase(
TaxFormType.w4,
<String, dynamic>{
'firstName': state.firstName,
'lastName': state.lastName,
'ssn': state.ssn,
'filingStatus': state.filingStatus,
'multipleJobs': state.multipleJobs,
'signature': state.signature,
// ... add other fields as needed
},
final Map<String, dynamic> formData = {
'firstName': state.firstName,
'lastName': state.lastName,
'ssn': state.ssn,
'address': state.address,
'cityStateZip': state.cityStateZip, // Note: Repository should split this if needed.
'filingStatus': state.filingStatus,
'multipleJobs': state.multipleJobs,
'qualifyingChildren': state.qualifyingChildren,
'otherDependents': state.otherDependents,
'otherIncome': state.otherIncome,
'deductions': state.deductions,
'extraWithholding': state.extraWithholding,
'signature': state.signature,
};
final W4TaxForm form = W4TaxForm(
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
title: 'Form W-4',
formData: formData,
);
await _submitW4FormUseCase(form);
emit(state.copyWith(status: FormW4Status.success));
} catch (e) {
emit(state.copyWith(

View File

@@ -2,12 +2,14 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/i9/form_i9_cubit.dart';
import '../blocs/i9/form_i9_state.dart';
class FormI9Page extends StatefulWidget {
const FormI9Page({super.key});
final TaxForm? form;
const FormI9Page({super.key, this.form});
@override
State<FormI9Page> createState() => _FormI9PageState();
@@ -22,6 +24,16 @@ class _FormI9PageState extends State<FormI9Page> {
'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'
];
@override
void initState() {
super.initState();
if (widget.form != null) {
// Use post-frame callback or simple direct call since we are using Modular.get in build
// But better helper:
Modular.get<FormI9Cubit>().initialize(widget.form);
}
}
final List<Map<String, String>> _steps = <Map<String, String>>[
<String, String>{'title': 'Personal Information', 'subtitle': 'Name and contact details'},
<String, String>{'title': 'Address', 'subtitle': 'Your current address'},

View File

@@ -2,18 +2,81 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/w4/form_w4_cubit.dart';
import '../blocs/w4/form_w4_state.dart';
class FormW4Page extends StatefulWidget {
const FormW4Page({super.key});
final TaxForm? form;
const FormW4Page({super.key, this.form});
@override
State<FormW4Page> createState() => _FormW4PageState();
}
class _FormW4PageState extends State<FormW4Page> {
@override
void initState() {
super.initState();
if (widget.form != null) {
Modular.get<FormW4Cubit>().initialize(widget.form);
}
}
final List<String> _usStates = <String>[
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
'California',
'Colorado',
'Connecticut',
'Delaware',
'Florida',
'Georgia',
'Hawaii',
'Idaho',
'Illinois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Ohio',
'Oklahoma',
'Oregon',
'Pennsylvania',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming',
];
final List<Map<String, String>> _steps = <Map<String, String>>[
<String, String>{'title': 'Personal Information', 'subtitle': 'Step 1'},
<String, String>{'title': 'Filing Status', 'subtitle': 'Step 1c'},

View File

@@ -140,14 +140,14 @@ class TaxFormsPage extends StatelessWidget {
Widget _buildFormCard(TaxForm form) {
// 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 is I9TaxForm ? '🛂' : '📋';
return GestureDetector(
onTap: () {
if (form.type == TaxFormType.i9) {
Modular.to.pushNamed('i9');
} else if (form.type == TaxFormType.w4) {
Modular.to.pushNamed('w4');
if (form is I9TaxForm) {
Modular.to.pushNamed('i9', arguments: form);
} else if (form is W4TaxForm) {
Modular.to.pushNamed('w4', arguments: form);
}
},
child: Container(

View File

@@ -1,10 +1,12 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart';
import 'data/repositories/tax_forms_repository_impl.dart';
import 'domain/repositories/tax_forms_repository.dart';
import 'domain/usecases/get_tax_forms_usecase.dart';
import 'domain/usecases/submit_tax_form_usecase.dart';
import 'domain/usecases/submit_i9_form_usecase.dart';
import 'domain/usecases/submit_w4_form_usecase.dart';
import 'presentation/blocs/i9/form_i9_cubit.dart';
import 'presentation/blocs/tax_forms/tax_forms_cubit.dart';
import 'presentation/blocs/w4/form_w4_cubit.dart';
@@ -24,7 +26,8 @@ class StaffTaxFormsModule extends Module {
// Use Cases
i.addLazySingleton(GetTaxFormsUseCase.new);
i.addLazySingleton(SubmitTaxFormUseCase.new);
i.addLazySingleton(SubmitI9FormUseCase.new);
i.addLazySingleton(SubmitW4FormUseCase.new);
// Blocs
i.addLazySingleton(TaxFormsCubit.new);
@@ -35,7 +38,13 @@ class StaffTaxFormsModule extends Module {
@override
void routes(RouteManager r) {
r.child('/', child: (_) => const TaxFormsPage());
r.child('/i9', child: (_) => const FormI9Page());
r.child('/w4', child: (_) => const FormW4Page());
r.child(
'/i9',
child: (_) => FormI9Page(form: r.args.data as TaxForm?),
);
r.child(
'/w4',
child: (_) => FormW4Page(form: r.args.data as TaxForm?),
);
}
}

View File

@@ -1,778 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: cd83f7d6bd4e4c0b0b4fef802e8796784032e1cc23d7b0e982cf5d05d9bbe182
url: "https://pub.dev"
source: hosted
version: "1.3.66"
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
auto_injector:
dependency: transitive
description:
name: auto_injector
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bloc:
dependency: "direct main"
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
core_localization:
dependency: "direct main"
description:
path: "../../../../../core_localization"
relative: true
source: path
version: "0.0.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csv:
dependency: transitive
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
design_system:
dependency: "direct main"
description:
path: "../../../../../design_system"
relative: true
source: path
version: "0.0.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.5"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
firebase_app_check:
dependency: transitive
description:
name: firebase_app_check
sha256: "45f0d279ea7ae4eac1867a4c85aa225761e3ac0ccf646386a860b2bc16581f76"
url: "https://pub.dev"
source: hosted
version: "0.4.1+4"
firebase_app_check_platform_interface:
dependency: transitive
description:
name: firebase_app_check_platform_interface
sha256: e32b4e6adeaac207a6f7afe0906d97c0811de42fb200d9b6317a09155de65e2b
url: "https://pub.dev"
source: hosted
version: "0.2.1+4"
firebase_app_check_web:
dependency: transitive
description:
name: firebase_app_check_web
sha256: "2cbc8a18a34813a7e31d7b30f989973087421cd5d0e397b4dd88a90289aa2bed"
url: "https://pub.dev"
source: hosted
version: "0.2.2+2"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: b20d1540460814c5984474c1e9dd833bdbcff6ecd8d6ad86cc9da8cfd581c172
url: "https://pub.dev"
source: hosted
version: "6.1.4"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: fd0225320b6bbc92460c86352d16b60aea15f9ef88292774cca97b0522ea9f72
url: "https://pub.dev"
source: hosted
version: "8.1.6"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: be7dccb263b89fbda2a564de9d8193118196e8481ffb937222a025cdfdf82c40
url: "https://pub.dev"
source: hosted
version: "6.1.2"
firebase_core:
dependency: transitive
description:
name: firebase_core
sha256: "923085c881663ef685269b013e241b428e1fb03cdd0ebde265d9b40ff18abf80"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64
url: "https://pub.dev"
source: hosted
version: "6.0.2"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "83e7356c704131ca4d8d8dd57e360d8acecbca38b1a3705c7ae46cc34c708084"
url: "https://pub.dev"
source: hosted
version: "3.4.0"
firebase_data_connect:
dependency: "direct main"
description:
name: firebase_data_connect
sha256: "01d0f8e33c520a6e6f59cf5ac6ff281d1927f7837f094fa8eb5fdb0b1b328ad8"
url: "https://pub.dev"
source: hosted
version: "0.2.2+2"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.6"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_modular:
dependency: "direct main"
description:
name: flutter_modular
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
flutter_test:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
font_awesome_flutter:
dependency: transitive
description:
name: font_awesome_flutter
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
url: "https://pub.dev"
source: hosted
version: "10.12.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_fonts:
dependency: transitive
description:
name: google_fonts
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
google_identity_services_web:
dependency: transitive
description:
name: google_identity_services_web
sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454"
url: "https://pub.dev"
source: hosted
version: "0.3.3+1"
googleapis_auth:
dependency: transitive
description:
name: googleapis_auth
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
url: "https://pub.dev"
source: hosted
version: "1.6.0"
grpc:
dependency: transitive
description:
name: grpc
sha256: e93ee3bce45c134bf44e9728119102358c7cd69de7832d9a874e2e74eb8cab40
url: "https://pub.dev"
source: hosted
version: "3.2.4"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http2:
dependency: transitive
description:
name: http2
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: transitive
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
krow_core:
dependency: "direct main"
description:
path: "../../../../../core"
relative: true
source: path
version: "0.0.1"
krow_data_connect:
dependency: "direct main"
description:
path: "../../../../../data_connect"
relative: true
source: path
version: "0.0.1"
krow_domain:
dependency: "direct main"
description:
path: "../../../../../domain"
relative: true
source: path
version: "0.0.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lucide_icons:
dependency: "direct main"
description:
name: lucide_icons
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
url: "https://pub.dev"
source: hosted
version: "0.257.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
modular_core:
dependency: transitive
description:
name: modular_core
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "7fd0c4d8ac8980011753b9bdaed2bf15111365924cdeeeaeb596214ea2b03537"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
protobuf:
dependency: transitive
description:
name: protobuf
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
provider:
dependency: transitive
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
result_dart:
dependency: transitive
description:
name: result_dart
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev"
source: hosted
version: "2.4.18"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
slang:
dependency: transitive
description:
name: slang
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
slang_flutter:
dependency: transitive
description:
name: slang_flutter
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.7 <4.0.0"
flutter: ">=3.38.4"

View File

@@ -2,6 +2,7 @@ name: staff_tax_forms
description: Staff Tax Forms feature.
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: '>=3.10.0 <4.0.0'

View File

@@ -34,4 +34,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_lints: ^6.0.0

View File

@@ -2,9 +2,10 @@ name: staff_time_card
description: Staff Time Card Feature
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.10.0 <4.0.0'
flutter: ">=3.0.0"
dependencies:

View File

@@ -31,4 +31,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_lints: ^6.0.0

View File

@@ -25,11 +25,13 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
required UpdatePersonalInfoUseCase updatePersonalInfoUseCase,
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
_updatePersonalInfoUseCase = updatePersonalInfoUseCase,
super(const PersonalInfoState()) {
super(const PersonalInfoState.initial()) {
on<PersonalInfoLoadRequested>(_onLoadRequested);
on<PersonalInfoFieldUpdated>(_onFieldUpdated);
on<PersonalInfoSaveRequested>(_onSaveRequested);
on<PersonalInfoPhotoUploadRequested>(_onPhotoUploadRequested);
on<PersonalInfoFieldChanged>(_onFieldChanged);
on<PersonalInfoAddressSelected>(_onAddressSelected);
on<PersonalInfoFormSubmitted>(_onSubmitted);
add(const PersonalInfoLoadRequested());
}
/// Handles loading staff profile information.
@@ -67,8 +69,8 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
}
/// Handles updating a field value in the current staff profile.
void _onFieldUpdated(
PersonalInfoFieldUpdated event,
void _onFieldChanged(
PersonalInfoFieldChanged event,
Emitter<PersonalInfoState> emit,
) {
final Map<String, dynamic> updatedValues = Map.from(state.formValues);
@@ -77,8 +79,8 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
}
/// Handles saving staff profile information.
Future<void> _onSaveRequested(
PersonalInfoSaveRequested event,
Future<void> _onSubmitted(
PersonalInfoFormSubmitted event,
Emitter<PersonalInfoState> emit,
) async {
if (state.staff == null) return;
@@ -116,33 +118,16 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
}
}
/// Handles uploading a profile photo.
Future<void> _onPhotoUploadRequested(
PersonalInfoPhotoUploadRequested event,
void _onAddressSelected(
PersonalInfoAddressSelected event,
Emitter<PersonalInfoState> emit,
) async {
if (state.staff == null) return;
emit(state.copyWith(status: PersonalInfoStatus.uploadingPhoto));
try {
// TODO: Implement photo upload when repository method is available
// final photoUrl = await _repository.uploadProfilePhoto(event.filePath);
// final updatedStaff = Staff(...);
// emit(state.copyWith(
// status: PersonalInfoStatus.loaded,
// staff: updatedStaff,
// ));
// For now, just return to loaded state
emit(state.copyWith(status: PersonalInfoStatus.loaded));
} catch (e) {
emit(state.copyWith(
status: PersonalInfoStatus.error,
errorMessage: e.toString(),
));
}
) {
// TODO: Implement Google Places logic if needed
}
/// With _onPhotoUploadRequested and _onSaveRequested removed or renamed,
/// there are no errors pointing to them here.
@override
void dispose() {
close();

View File

@@ -14,11 +14,11 @@ class PersonalInfoLoadRequested extends PersonalInfoEvent {
}
/// Event to update a field value.
class PersonalInfoFieldUpdated extends PersonalInfoEvent {
class PersonalInfoFieldChanged extends PersonalInfoEvent {
final String field;
final dynamic value;
const PersonalInfoFieldUpdated({
const PersonalInfoFieldChanged({
required this.field,
required this.value,
});
@@ -27,17 +27,16 @@ class PersonalInfoFieldUpdated extends PersonalInfoEvent {
List<Object?> get props => [field, value];
}
/// Event to save personal information.
class PersonalInfoSaveRequested extends PersonalInfoEvent {
const PersonalInfoSaveRequested();
/// Event to submit the form.
class PersonalInfoFormSubmitted extends PersonalInfoEvent {
const PersonalInfoFormSubmitted();
}
/// Event to upload a profile photo.
class PersonalInfoPhotoUploadRequested extends PersonalInfoEvent {
final String filePath;
const PersonalInfoPhotoUploadRequested({required this.filePath});
/// Event when an address is selected from autocomplete.
class PersonalInfoAddressSelected extends PersonalInfoEvent {
final String address;
const PersonalInfoAddressSelected(this.address);
@override
List<Object?> get props => [filePath];
List<Object?> get props => [address];
}

View File

@@ -49,6 +49,13 @@ class PersonalInfoState extends Equatable {
this.errorMessage,
});
/// Initial state.
const PersonalInfoState.initial()
: status = PersonalInfoStatus.initial,
staff = null,
formValues = const {},
errorMessage = null;
/// Creates a copy of this state with the given fields replaced.
PersonalInfoState copyWith({
PersonalInfoStatus? status,

View File

@@ -26,8 +26,7 @@ class PersonalInfoPage extends StatelessWidget {
Widget build(BuildContext context) {
final TranslationsStaffOnboardingPersonalInfoEn i18n = t.staff.onboarding.personal_info;
return BlocProvider<PersonalInfoBloc>(
create: (BuildContext context) => Modular.get<PersonalInfoBloc>()
..add(const PersonalInfoLoadRequested()),
create: (BuildContext context) => Modular.get<PersonalInfoBloc>(),
child: BlocListener<PersonalInfoBloc, PersonalInfoState>(
listener: (BuildContext context, PersonalInfoState state) {
if (state.status == PersonalInfoStatus.saved) {

View File

@@ -56,7 +56,7 @@ class _PersonalInfoContentState extends State<PersonalInfoContent> {
void _onPhoneChanged() {
context.read<PersonalInfoBloc>().add(
PersonalInfoFieldUpdated(
PersonalInfoFieldChanged(
field: 'phone',
value: _phoneController.text,
),
@@ -73,7 +73,7 @@ class _PersonalInfoContentState extends State<PersonalInfoContent> {
.toList();
context.read<PersonalInfoBloc>().add(
PersonalInfoFieldUpdated(
PersonalInfoFieldChanged(
field: 'preferredLocations',
value: locations,
),
@@ -81,7 +81,7 @@ class _PersonalInfoContentState extends State<PersonalInfoContent> {
}
void _handleSave() {
context.read<PersonalInfoBloc>().add(const PersonalInfoSaveRequested());
context.read<PersonalInfoBloc>().add(const PersonalInfoFormSubmitted());
}
void _handlePhotoTap() {

View File

@@ -1,650 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
auto_injector:
dependency: transitive
description:
name: auto_injector
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bloc:
dependency: transitive
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
core_localization:
dependency: "direct main"
description:
path: "../../../core_localization"
relative: true
source: path
version: "0.0.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csv:
dependency: transitive
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
design_system:
dependency: "direct main"
description:
path: "../../../design_system"
relative: true
source: path
version: "0.0.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.5"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.6"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_modular:
dependency: "direct main"
description:
name: flutter_modular
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
font_awesome_flutter:
dependency: transitive
description:
name: font_awesome_flutter
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
url: "https://pub.dev"
source: hosted
version: "10.12.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_fonts:
dependency: transitive
description:
name: google_fonts
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
krow_core:
dependency: "direct main"
description:
path: "../../../core"
relative: true
source: path
version: "0.0.1"
krow_data_connect:
dependency: "direct main"
description:
path: "../../../data_connect"
relative: true
source: path
version: "0.0.1"
krow_domain:
dependency: "direct main"
description:
path: "../../../domain"
relative: true
source: path
version: "0.0.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "3.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lucide_icons:
dependency: transitive
description:
name: lucide_icons
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
url: "https://pub.dev"
source: hosted
version: "0.257.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
modular_core:
dependency: transitive
description:
name: modular_core
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "7fd0c4d8ac8980011753b9bdaed2bf15111365924cdeeeaeb596214ea2b03537"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: transitive
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
result_dart:
dependency: transitive
description:
name: result_dart
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev"
source: hosted
version: "2.4.18"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
slang:
dependency: transitive
description:
name: slang
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
slang_flutter:
dependency: transitive
description:
name: slang_flutter
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.7 <4.0.0"
flutter: ">=3.38.4"

View File

@@ -2,9 +2,10 @@ name: staff_shifts
description: A new Flutter package project.
version: 0.0.1
publish_to: 'none'
resolution: workspace
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: '>=3.10.0 <4.0.0'
flutter: ">=3.0.0"
dependencies:
@@ -30,4 +31,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter_lints: ^6.0.0