diff --git a/.gitignore b/.gitignore index b118231a..9dd0e50a 100644 --- a/.gitignore +++ b/.gitignore @@ -142,6 +142,7 @@ build/ .firebase/ dataconnect/.dataconnect/ backend/dataconnect/.dataconnect/ +backend/dataconnect/dataconnect.yaml # Debug Logs (Recursive) **/firebase-debug.log diff --git a/Makefile b/Makefile index e6e35bbf..a54c9e9d 100644 --- a/Makefile +++ b/Makefile @@ -55,22 +55,25 @@ help: @echo "" @echo " šŸ—„ļø DATA CONNECT & BACKEND (backend/dataconnect)" @echo " ────────────────────────────────────────────────────────────────────" - @echo " make dataconnect-init Initialize Firebase Data Connect" - @echo " make dataconnect-deploy Deploy Data Connect schemas to Cloud SQL" - @echo " make dataconnect-sql-migrate Apply pending SQL migrations" - @echo " make dataconnect-generate-sdk Regenerate Data Connect client SDK" - @echo " make dataconnect-sync Full sync: deploy + migrate + generate SDK" - @echo " make dataconnect-seed Seed database with test data" - @echo " make dataconnect-clean Delete all data from Data Connect" - @echo " make dataconnect-test Test Data Connect deployment (dry-run)" - @echo " make dataconnect-enable-apis Enable required GCP APIs" - @echo " make dataconnect-bootstrap-db ONE-TIME: Full Cloud SQL + Data Connect setup" + @echo " make dataconnect-init Initialize Firebase Data Connect" + @echo " make dataconnect-deploy [ENV=dev] Deploy Data Connect schemas (dev/staging)" + @echo " make dataconnect-sql-migrate [ENV=dev] Apply pending SQL migrations" + @echo " make dataconnect-generate-sdk [ENV=dev] Regenerate Data Connect client SDK" + @echo " make dataconnect-sync [ENV=dev] Full sync: deploy + migrate + generate SDK" + @echo " make dataconnect-seed [ENV=dev] Seed database with test data" + @echo " make dataconnect-clean [ENV=dev] Delete all data from Data Connect" + @echo " make dataconnect-test [ENV=dev] Test Data Connect deployment (dry-run)" + @echo " make dataconnect-enable-apis [ENV=dev] Enable required GCP APIs" + @echo " make dataconnect-bootstrap-db ONE-TIME: Full Cloud SQL + Data Connect setup (dev)" + @echo " make dataconnect-bootstrap-validation-database ONE-TIME: Setup validation database" + @echo " make dataconnect-backup-dev-to-validation Backup dev database to validation" @echo "" @echo " šŸ› ļø DEVELOPMENT TOOLS" @echo " ────────────────────────────────────────────────────────────────────" @echo " make install-melos Install Melos globally (for mobile dev)" @echo " make install-git-hooks Install git pre-push hook (protect main/dev)" @echo " make sync-prototypes Sync prototypes from client-krow-poc repo" + @echo " make clean-branches Delete local branches (keeps main/dev/demo/**/protected)" @echo "" @echo " ā„¹ļø HELP" @echo " ────────────────────────────────────────────────────────────────────" @@ -79,4 +82,5 @@ help: @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo " šŸ’” Tip: Run 'make mobile-install' first for mobile development" @echo " šŸ’” Tip: Use 'make dataconnect-sync' after schema changes" + @echo " šŸ’” Tip: Default ENV=dev, use ENV=staging for staging environment" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" \ No newline at end of file diff --git a/PROTECTED_BRANCHES.md b/PROTECTED_BRANCHES.md new file mode 100644 index 00000000..89874a4a --- /dev/null +++ b/PROTECTED_BRANCHES.md @@ -0,0 +1,5 @@ +# Protected Branches + +- `main` +- `dev` +- `demo/**` diff --git a/apps/mobile/NEXT_SPRINT_TASKS.md b/apps/mobile/NEXT_SPRINT_TASKS.md index 8b192813..3647bdd5 100644 --- a/apps/mobile/NEXT_SPRINT_TASKS.md +++ b/apps/mobile/NEXT_SPRINT_TASKS.md @@ -5,16 +5,14 @@ * Break down large widgets into **smaller, reusable widgets** * Add **doc comments** where necessary to improve readability and maintainability * **Remove overly complicated or unnecessary logic** introduced by AI and simplify where possible - * **Adhere to the design system** and remove all **hard-coded colors and typography**, using shared tokens instead - * Improvement points - apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart - Fix the location field in CoverageShiftRole to use the correct fallback logic. - line 125 remove redundant location values. -- Need to clarify the difference b/w `case dc.ApplicationStatus.ACCEPTED` and `case dc.ApplicationStatus.CONFIRMED`. - Update the dataconnect docs. - Track `lat` and `lng` in the staff preferred work locations (for now we are only storing the name). +- Change the name of the dataconnect connector replacing the "ExampleConnecter" with "KrowConnecter" - ` final String status;` in `OrderItem` make it an enum. - /// Date of the shift (ISO format). @@ -23,20 +21,20 @@ - in `view_orders_cubit.dart` combine the logic of `_calculateUpNextCount ` and `_calculateTodayCount` into a single function that calculates both counts together to avoid redundant filtering of orders. - In places api call in the when the api's not working we need to show a proper error message instead of just an empty list. - pending should come first in the view order list. -- rename connect name to 'krow-connect' in the project. -- fix "dartdepend_on_referenced_packages" warnings. -- fix "dartunnecessary_library_name" warnings. -- fix lint issues. -- fix localization. -- centralise way to handle errors. + + + - track minimum shift hours in the staff profile and show a warning if they try to apply for shifts that are below their minimum hours. - this need to be added in the BE and also a FE validation (5 hrs). + - Cannot cancel before 24 hours of the shift start time. If do we should charge for 4 hours of work for each shifts. + - verify the order creation process in the client app. - Vendor don't need to verify the order, when the order is created it should be automatically published. - rethink the order status, we need to simplify it. + - Validation layer - Profile info - emergency contact @@ -48,4 +46,9 @@ - there should be manual verification by the client even if the ai verification is passed. - to track false positives and false negatives. - documents - - tax forms \ No newline at end of file + - tax forms + +- How do we handle the current bank account verifcaiton in the current legacy application. +- We need have a show a list of clothing items in the staff app -> shift page. +- Template models for the pdf reports in the client and web apps. +- remove `any` type and replace it with the correct types in the codebase. \ No newline at end of file diff --git a/apps/mobile/apps/client/pubspec.yaml b/apps/mobile/apps/client/pubspec.yaml index c896944d..101a2b77 100644 --- a/apps/mobile/apps/client/pubspec.yaml +++ b/apps/mobile/apps/client/pubspec.yaml @@ -1,7 +1,7 @@ name: krowwithus_client description: "Krow Client Application" publish_to: "none" -version: 0.0.1-M3+5 +version: 0.0.1-IlianaClientM3 resolution: workspace environment: diff --git a/apps/mobile/apps/staff/pubspec.yaml b/apps/mobile/apps/staff/pubspec.yaml index 62c2ae21..8bc77687 100644 --- a/apps/mobile/apps/staff/pubspec.yaml +++ b/apps/mobile/apps/staff/pubspec.yaml @@ -1,7 +1,7 @@ name: krowwithus_staff description: "Krow Staff Application" publish_to: 'none' -version: 0.0.1-M3+3 +version: 0.0.1-IlianaStaffM3 resolution: workspace environment: diff --git a/apps/mobile/packages/design_system/lib/design_system.dart b/apps/mobile/packages/design_system/lib/design_system.dart index a20a8d7c..2bfc01d4 100644 --- a/apps/mobile/packages/design_system/lib/design_system.dart +++ b/apps/mobile/packages/design_system/lib/design_system.dart @@ -10,5 +10,5 @@ export 'src/widgets/ui_step_indicator.dart'; export 'src/widgets/ui_icon_button.dart'; export 'src/widgets/ui_button.dart'; export 'src/widgets/ui_chip.dart'; -export 'src/widgets/ui_error_snackbar.dart'; -export 'src/widgets/ui_success_snackbar.dart'; +export 'src/widgets/ui_loading_page.dart'; +export 'src/widgets/ui_snackbar.dart'; diff --git a/apps/mobile/packages/design_system/lib/src/ui_colors.dart b/apps/mobile/packages/design_system/lib/src/ui_colors.dart index 2462d0f3..30a56dc3 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_colors.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_colors.dart @@ -120,7 +120,7 @@ class UiColors { static const Color textDescription = mutedForeground; /// Success text (#10B981) - static const Color textSuccess = Color(0xFF10B981); + static const Color textSuccess = Color(0xFF0A8159); /// Error text (#F04444) static const Color textError = destructive; diff --git a/apps/mobile/packages/design_system/lib/src/ui_icons.dart b/apps/mobile/packages/design_system/lib/src/ui_icons.dart index d5c9b139..829553e9 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_icons.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_icons.dart @@ -225,4 +225,28 @@ class UiIcons { /// Globe icon static const IconData globe = _IconLib.globe; + + /// Sunrise icon + static const IconData sunrise = _IconLib.sunrise; + + /// Sun icon + static const IconData sun = _IconLib.sun; + + /// Moon icon + static const IconData moon = _IconLib.moon; + + /// Timer icon + static const IconData timer = _IconLib.timer; + + /// Coffee icon for breaks + static const IconData coffee = _IconLib.coffee; + + /// Wifi icon for NFC check-in + static const IconData wifi = _IconLib.wifi; + + /// X Circle icon for no-shows + static const IconData xCircle = _IconLib.xCircle; + + /// Ban icon for cancellations + static const IconData ban = _IconLib.ban; } diff --git a/apps/mobile/packages/design_system/lib/src/ui_theme.dart b/apps/mobile/packages/design_system/lib/src/ui_theme.dart index 2b098529..98ee7214 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_theme.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_theme.dart @@ -255,7 +255,7 @@ class UiTheme { } return UiColors.switchInactive; }), - thumbColor: const WidgetStatePropertyAll(UiColors.white), + thumbColor: const WidgetStatePropertyAll(UiColors.white), ), // Checkbox Theme diff --git a/apps/mobile/packages/design_system/lib/src/ui_typography.dart b/apps/mobile/packages/design_system/lib/src/ui_typography.dart index ed823b5e..6d33312b 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_typography.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_typography.dart @@ -579,4 +579,10 @@ extension TypographyColors on TextStyle { /// Active content color TextStyle get activeContentColor => copyWith(color: UiColors.textPrimary); + + /// Primary color + TextStyle get primary => copyWith(color: UiColors.primary); + + /// Accent color + TextStyle get accent => copyWith(color: UiColors.accent); } diff --git a/apps/mobile/packages/design_system/lib/src/widgets/ui_error_snackbar.dart b/apps/mobile/packages/design_system/lib/src/widgets/ui_error_snackbar.dart deleted file mode 100644 index c35e36ae..00000000 --- a/apps/mobile/packages/design_system/lib/src/widgets/ui_error_snackbar.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:core_localization/core_localization.dart'; -import '../ui_colors.dart'; -import '../ui_typography.dart'; - -/// Centralized error snackbar for consistent error presentation across the app. -/// -/// This widget automatically resolves localization keys and displays -/// user-friendly error messages with optional error codes for support. -/// -/// Usage: -/// ```dart -/// UiErrorSnackbar.show( -/// context, -/// messageKey: 'errors.auth.invalid_credentials', -/// errorCode: 'AUTH_001', -/// ); -/// ``` -class UiErrorSnackbar { - /// Shows an error snackbar with a localized message. - /// - /// [messageKey] should be a dot-separated path like 'errors.auth.invalid_credentials' - /// [errorCode] is optional and will be shown in smaller text for support reference - /// [duration] controls how long the snackbar is visible - static void show( - BuildContext context, { - required String messageKey, - String? errorCode, - Duration duration = const Duration(seconds: 4), - }) { - // 1. Added explicit type 'Translations' to satisfy the lint - final Translations texts = Translations.of(context); - final String message = _getMessageFromKey(texts, messageKey); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Row( - children: [ - const Icon(Icons.error_outline, color: UiColors.white), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - message, - style: UiTypography.body2m.copyWith(color: UiColors.white), - ), - if (errorCode != null) ...[ - const SizedBox(height: 4), - Text( - 'Error Code: $errorCode', - style: UiTypography.footnote2r.copyWith( - // 3. Fixed deprecated member use - color: UiColors.white.withValues(alpha: 0.7), - ), - ), - ], - ], - ), - ), - ], - ), - backgroundColor: UiColors.error, - behavior: SnackBarBehavior.floating, - duration: duration, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - margin: const EdgeInsets.all(16), - ), - ); - } - - /// Resolves a localization key path to the actual translated message. - /// - /// Supports keys like: - /// - errors.auth.invalid_credentials - /// - errors.hub.has_orders - /// - errors.generic.unknown - static String _getMessageFromKey(Translations texts, String key) { - // Parse key like "errors.auth.invalid_credentials" - final parts = key.split('.'); - if (parts.length < 2) return texts.errors.generic.unknown; - - try { - switch (parts[1]) { - case 'auth': - return _getAuthError(texts, parts.length > 2 ? parts[2] : ''); - case 'hub': - return _getHubError(texts, parts.length > 2 ? parts[2] : ''); - case 'order': - return _getOrderError(texts, parts.length > 2 ? parts[2] : ''); - case 'profile': - return _getProfileError(texts, parts.length > 2 ? parts[2] : ''); - case 'shift': - return _getShiftError(texts, parts.length > 2 ? parts[2] : ''); - case 'generic': - return _getGenericError(texts, parts.length > 2 ? parts[2] : ''); - default: - return texts.errors.generic.unknown; - } - } catch (_) { - return texts.errors.generic.unknown; - } - } - - static String _getAuthError(Translations texts, String key) { - switch (key) { - case 'invalid_credentials': - return texts.errors.auth.invalid_credentials; - case 'account_exists': - return texts.errors.auth.account_exists; - case 'session_expired': - return texts.errors.auth.session_expired; - case 'user_not_found': - return texts.errors.auth.user_not_found; - case 'unauthorized_app': - return texts.errors.auth.unauthorized_app; - case 'weak_password': - return texts.errors.auth.weak_password; - case 'sign_up_failed': - return texts.errors.auth.sign_up_failed; - case 'sign_in_failed': - return texts.errors.auth.sign_in_failed; - case 'not_authenticated': - return texts.errors.auth.not_authenticated; - case 'password_mismatch': - return texts.errors.auth.password_mismatch; - case 'google_only_account': - return texts.errors.auth.google_only_account; - default: - return texts.errors.generic.unknown; - } - } - - static String _getHubError(Translations texts, String key) { - switch (key) { - case 'has_orders': - return texts.errors.hub.has_orders; - case 'not_found': - return texts.errors.hub.not_found; - case 'creation_failed': - return texts.errors.hub.creation_failed; - default: - return texts.errors.generic.unknown; - } - } - - static String _getOrderError(Translations texts, String key) { - switch (key) { - case 'missing_hub': - return texts.errors.order.missing_hub; - case 'missing_vendor': - return texts.errors.order.missing_vendor; - case 'creation_failed': - return texts.errors.order.creation_failed; - case 'shift_creation_failed': - return texts.errors.order.shift_creation_failed; - case 'missing_business': - return texts.errors.order.missing_business; - default: - return texts.errors.generic.unknown; - } - } - - static String _getProfileError(Translations texts, String key) { - switch (key) { - case 'staff_not_found': - return texts.errors.profile.staff_not_found; - case 'business_not_found': - return texts.errors.profile.business_not_found; - case 'update_failed': - return texts.errors.profile.update_failed; - default: - return texts.errors.generic.unknown; - } - } - - static String _getShiftError(Translations texts, String key) { - switch (key) { - case 'no_open_roles': - return texts.errors.shift.no_open_roles; - case 'application_not_found': - return texts.errors.shift.application_not_found; - case 'no_active_shift': - return texts.errors.shift.no_active_shift; - default: - return texts.errors.generic.unknown; - } - } - - static String _getGenericError(Translations texts, String key) { - switch (key) { - case 'unknown': - return texts.errors.generic.unknown; - case 'no_connection': - return texts.errors.generic.no_connection; - default: - return texts.errors.generic.unknown; - } - } -} diff --git a/apps/mobile/packages/design_system/lib/src/widgets/ui_loading_page.dart b/apps/mobile/packages/design_system/lib/src/widgets/ui_loading_page.dart new file mode 100644 index 00000000..9d59ebbe --- /dev/null +++ b/apps/mobile/packages/design_system/lib/src/widgets/ui_loading_page.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '../ui_colors.dart'; + +/// A common loading page that adheres to the design system. +/// It features a blurred background using [UiColors.popupShadow] and a [CircularProgressIndicator]. +class UiLoadingPage extends StatelessWidget { + /// Creates a [UiLoadingPage]. + const UiLoadingPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: UiColors.transparent, + body: Stack( + fit: StackFit.expand, + children: [ + // Background blur and color + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Container(color: UiColors.popupShadow), + ), + ), + // Center loading indicator + const Center(child: CircularProgressIndicator()), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/design_system/lib/src/widgets/ui_snackbar.dart b/apps/mobile/packages/design_system/lib/src/widgets/ui_snackbar.dart new file mode 100644 index 00000000..25ca55d0 --- /dev/null +++ b/apps/mobile/packages/design_system/lib/src/widgets/ui_snackbar.dart @@ -0,0 +1,99 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import '../ui_colors.dart'; +import '../ui_icons.dart'; +import '../ui_typography.dart'; + +/// Types of snackbars available in the design system. +enum UiSnackbarType { + /// Success state - green text and light blurred green background. + success, + + /// Message state - blue text and light blurred blue background. + message, + + /// Warning state - dark yellow text and light blurred yellow background. + warning, + + /// Error state - red text and light blurred red background. + error, +} + +/// A centralized snackbar widget that adheres to the design system with glassmorphism effects. +class UiSnackbar { + UiSnackbar._(); + + /// Shows a snackbar with the specified [message] and [type]. + static void show( + BuildContext context, { + required String message, + required UiSnackbarType type, + Duration duration = const Duration(seconds: 3), + }) { + final Color textColor; + final Color backgroundColor; + final IconData icon; + + switch (type) { + case UiSnackbarType.success: + textColor = UiColors.textSuccess; + backgroundColor = UiColors.tagSuccess.withValues(alpha: 0.7); + icon = UiIcons.success; + break; + case UiSnackbarType.message: + textColor = UiColors.primary; + backgroundColor = UiColors.tagInProgress.withValues(alpha: 0.7); + icon = UiIcons.info; + break; + case UiSnackbarType.warning: + textColor = UiColors.textWarning; + backgroundColor = UiColors.tagPending.withValues(alpha: 0.7); + icon = UiIcons.warning; + break; + case UiSnackbarType.error: + textColor = UiColors.textError; + backgroundColor = UiColors.tagError.withValues(alpha: 0.7); + icon = UiIcons.error; + break; + } + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: duration, + backgroundColor: UiColors.transparent, + elevation: 0, + behavior: SnackBarBehavior.floating, + content: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: textColor.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 12, + children: [ + Icon(icon, color: textColor, size: 20), + Expanded( + child: Text( + message, + style: UiTypography.body2b.copyWith(color: textColor), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/apps/mobile/packages/design_system/lib/src/widgets/ui_step_indicator.dart b/apps/mobile/packages/design_system/lib/src/widgets/ui_step_indicator.dart index 3388098d..c6989e13 100644 --- a/apps/mobile/packages/design_system/lib/src/widgets/ui_step_indicator.dart +++ b/apps/mobile/packages/design_system/lib/src/widgets/ui_step_indicator.dart @@ -8,12 +8,6 @@ import '../ui_icons.dart'; /// This widget shows a series of circular step indicators connected by lines, /// with different visual states for completed, active, and inactive steps. class UiStepIndicator extends StatelessWidget { - /// The list of icons to display for each step. - final List stepIcons; - - /// The index of the currently active step (0-based). - final int currentStep; - /// Creates a [UiStepIndicator]. const UiStepIndicator({ super.key, @@ -21,6 +15,12 @@ class UiStepIndicator extends StatelessWidget { required this.currentStep, }); + /// The list of icons to display for each step. + final List stepIcons; + + /// The index of the currently active step (0-based). + final int currentStep; + @override /// Builds the step indicator UI. Widget build(BuildContext context) { @@ -35,7 +35,7 @@ class UiStepIndicator extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: UiConstants.space2), child: Row( mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(stepIcons.length, (int index) { + children: List.generate(stepIcons.length, (int index) { final bool isActive = index == currentStep; final bool isCompleted = index < currentStep; diff --git a/apps/mobile/packages/design_system/lib/src/widgets/ui_success_snackbar.dart b/apps/mobile/packages/design_system/lib/src/widgets/ui_success_snackbar.dart deleted file mode 100644 index 81855390..00000000 --- a/apps/mobile/packages/design_system/lib/src/widgets/ui_success_snackbar.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import '../ui_colors.dart'; -import '../ui_typography.dart'; - -/// Centralized success snackbar for consistent success message presentation. -/// -/// This widget provides a unified way to show success feedback across the app -/// with consistent styling and behavior. -/// -/// Usage: -/// ```dart -/// UiSuccessSnackbar.show( -/// context, -/// message: 'Profile updated successfully!', -/// ); -/// ``` -class UiSuccessSnackbar { - /// Shows a success snackbar with a custom message. - /// - /// [message] is the success message to display - /// [duration] controls how long the snackbar is visible - static void show( - BuildContext context, { - required String message, - Duration duration = const Duration(seconds: 3), - }) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Row( - children: [ - Icon(Icons.check_circle_outline, color: UiColors.white), - const SizedBox(width: 12), - Expanded( - child: Text( - message, - style: UiTypography.body2m.copyWith(color: UiColors.white), - ), - ), - ], - ), - backgroundColor: UiColors.success, - behavior: SnackBarBehavior.floating, - duration: duration, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - margin: const EdgeInsets.all(16), - ), - ); - } -} diff --git a/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart b/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart index 572d74a1..6c5033a7 100644 --- a/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart +++ b/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart @@ -41,7 +41,6 @@ class TimeCardAdapter { case 'DISPUTED': return TimeCardStatus.disputed; case 'CHECKED_IN': - case 'ACCEPTED': case 'CONFIRMED': default: return TimeCardStatus.pending; diff --git a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index c06706fc..1841794c 100644 --- a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -12,8 +12,7 @@ import 'package:krow_domain/krow_domain.dart' AccountExistsException, UserNotFoundException, UnauthorizedAppException, - PasswordMismatchException, - GoogleOnlyAccountException; + PasswordMismatchException; import 'package:krow_domain/krow_domain.dart' as domain; import '../../domain/repositories/auth_repository_interface.dart'; diff --git a/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart b/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart index b79c7ddc..e7cb2bd9 100644 --- a/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart +++ b/apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart @@ -177,8 +177,6 @@ class CoverageRepositoryImpl implements CoverageRepository { switch (status.value) { case dc.ApplicationStatus.PENDING: return CoverageWorkerStatus.pending; - case dc.ApplicationStatus.ACCEPTED: - return CoverageWorkerStatus.confirmed; case dc.ApplicationStatus.REJECTED: return CoverageWorkerStatus.rejected; case dc.ApplicationStatus.CONFIRMED: diff --git a/apps/mobile/packages/features/staff/authentication/feature_manifest.md b/apps/mobile/packages/features/staff/authentication/feature_manifest.md deleted file mode 100644 index cd530b30..00000000 --- a/apps/mobile/packages/features/staff/authentication/feature_manifest.md +++ /dev/null @@ -1,33 +0,0 @@ -# Feature Manifest: Staff Authentication - -## Overview -**Feature Name:** Staff Authentication & Onboarding -**Package Path:** `packages/features/staff/authentication` - -## Responsibilities -* Handle user sign-up and log-in via Phone Auth. -* Verify OTP codes. -* Manage the Onboarding Wizard for new staff. -* Persist onboarding progress. - -## Architecture -* **Domain**: - * `AuthRepositoryInterface` - * `SignInWithPhoneUseCase` - * `VerifyOtpUseCase` -* **Data**: - * `AuthRepositoryImpl` (uses `AuthRepositoryMock` from `krow_data_connect`) -* **Presentation**: - * `AuthBloc`: Manages auth state (phone, otp, user status). - * `OnboardingBloc`: Manages wizard steps. - * Pages: `GetStartedPage`, `PhoneVerificationPage`, `ProfileSetupPage`. - -## Dependencies -* `krow_domain`: User entities. -* `krow_data_connect`: Auth mocks. -* `design_system`: UI components. - -## Routes -* `/`: Get Started (Welcome) -* `/phone-verification`: OTP Entry -* `/profile-setup`: Onboarding Wizard diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart index 35750c80..fd65b050 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/get_started_page.dart @@ -1,3 +1,4 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; @@ -36,7 +37,7 @@ class GetStartedPage extends StatelessWidget { // Content Overlay Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(UiConstants.space6), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -44,7 +45,7 @@ class GetStartedPage extends StatelessWidget { // Main text and actions const GetStartedHeader(), - const SizedBox(height: 48), + const SizedBox(height: UiConstants.space10), // Actions GetStartedActions( @@ -52,7 +53,7 @@ class GetStartedPage extends StatelessWidget { onLoginPressed: onLoginPressed, ), - const SizedBox(height: 32), + const SizedBox(height: UiConstants.space8), ], ), ), diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart index 2f6a178c..8b0720cc 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/pages/profile_setup_page.dart @@ -1,15 +1,16 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension; +import 'package:krow_core/core.dart'; + import '../blocs/profile_setup/profile_setup_bloc.dart'; import '../widgets/profile_setup_page/profile_setup_basic_info.dart'; -import '../widgets/profile_setup_page/profile_setup_location.dart'; import '../widgets/profile_setup_page/profile_setup_experience.dart'; import '../widgets/profile_setup_page/profile_setup_header.dart'; -import 'package:staff_authentication/staff_authentication.dart'; -import 'package:krow_core/core.dart'; +import '../widgets/profile_setup_page/profile_setup_location.dart'; /// Page for setting up the user profile after authentication. class ProfileSetupPage extends StatefulWidget { @@ -106,7 +107,8 @@ class _ProfileSetupPageState extends State { } }, builder: (BuildContext context, ProfileSetupState state) { - final bool isCreatingProfile = state.status == ProfileSetupStatus.loading; + final bool isCreatingProfile = + state.status == ProfileSetupStatus.loading; return Scaffold( body: SafeArea( @@ -125,7 +127,10 @@ class _ProfileSetupPageState extends State { // Step Indicators UiStepIndicator( stepIcons: steps - .map((Map step) => step['icon'] as IconData) + .map( + (Map step) => + step['icon'] as IconData, + ) .toList(), currentStep: _currentStep, ), @@ -211,9 +216,10 @@ class _ProfileSetupPageState extends State { return ProfileSetupLocation( preferredLocations: state.preferredLocations, maxDistanceMiles: state.maxDistanceMiles, - onLocationsChanged: (List val) => BlocProvider.of( - context, - ).add(ProfileSetupLocationsChanged(val)), + onLocationsChanged: (List val) => + BlocProvider.of( + context, + ).add(ProfileSetupLocationsChanged(val)), onDistanceChanged: (double val) => BlocProvider.of( context, ).add(ProfileSetupDistanceChanged(val)), @@ -222,12 +228,14 @@ class _ProfileSetupPageState extends State { return ProfileSetupExperience( skills: state.skills, industries: state.industries, - onSkillsChanged: (List val) => BlocProvider.of( - context, - ).add(ProfileSetupSkillsChanged(val)), - onIndustriesChanged: (List val) => BlocProvider.of( - context, - ).add(ProfileSetupIndustriesChanged(val)), + onSkillsChanged: (List val) => + BlocProvider.of( + context, + ).add(ProfileSetupSkillsChanged(val)), + onIndustriesChanged: (List val) => + BlocProvider.of( + context, + ).add(ProfileSetupIndustriesChanged(val)), ); default: return const SizedBox.shrink(); diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart index 95b6dff8..13f07feb 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/common/auth_trouble_link.dart @@ -1,6 +1,6 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A common widget that displays a "Having trouble? Contact Support" link. class AuthTroubleLink extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart index e2b37211..69a11986 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/get_started_page/get_started_header.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; -import 'package:staff_authentication/staff_authentication.dart'; +import 'package:flutter/material.dart'; /// A widget that displays the welcome text and description on the Get Started page. class GetStartedHeader extends StatelessWidget { @@ -20,9 +20,7 @@ class GetStartedHeader extends StatelessWidget { text: TextSpan( style: UiTypography.displayM, children: [ - TextSpan( - text: i18n.title_part1, - ), + TextSpan(text: i18n.title_part1), TextSpan( text: i18n.title_part2, style: UiTypography.displayMb.textLink, @@ -39,4 +37,4 @@ class GetStartedHeader extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart index 12fadb8c..41793f03 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_resend_section.dart @@ -1,6 +1,6 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget that handles the OTP resend logic and countdown timer. class OtpResendSection extends StatefulWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart index a307b6df..750a0cff 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_actions.dart @@ -1,6 +1,7 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:staff_authentication/staff_authentication.dart'; + import '../../common/auth_trouble_link.dart'; /// A widget that displays the primary action button and trouble link for OTP verification. diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart index ec4ff79c..d3bcfa5e 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/otp_verification/otp_verification_header.dart @@ -1,6 +1,6 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget that displays the title and subtitle for the OTP Verification page. class OtpVerificationHeader extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart index dcbe0d06..b9ced284 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_actions.dart @@ -1,7 +1,7 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:staff_authentication/src/presentation/widgets/common/auth_trouble_link.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget that displays the primary action button and trouble link for Phone Input. class PhoneInputActions extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart index 5b86b6e3..1b607af4 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/phone_verification_page/phone_input/phone_input_header.dart @@ -1,6 +1,6 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget that displays the title and subtitle for the Phone Input page. class PhoneInputHeader extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart index 4d9ad36c..93adabd5 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart @@ -1,7 +1,7 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget for setting up basic profile information (photo, name, bio). class ProfileSetupBasicInfo extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart index ac4273c1..e834dd1e 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart @@ -1,8 +1,8 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:krow_domain/krow_domain.dart'; import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget for setting up skills and preferred industries. class ProfileSetupExperience extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart index 5f727d48..f4168b7d 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_header.dart @@ -1,6 +1,6 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A header widget for the profile setup page showing back button and step count. class ProfileSetupHeader extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart index 5ee01419..a9458571 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/presentation/widgets/profile_setup_page/profile_setup_location.dart @@ -1,10 +1,11 @@ import 'dart:async'; + +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart'; import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; -import 'package:staff_authentication/staff_authentication.dart'; /// A widget for setting up preferred work locations and distance. class ProfileSetupLocation extends StatefulWidget { @@ -47,22 +48,23 @@ class _ProfileSetupLocationState extends State { void _onSearchChanged(String query) { if (_debounce?.isActive ?? false) _debounce!.cancel(); _debounce = Timer(const Duration(milliseconds: 300), () { - context - .read() - .add(ProfileSetupLocationQueryChanged(query)); + context.read().add( + ProfileSetupLocationQueryChanged(query), + ); }); } /// Adds the selected location. void _addLocation(String location) { if (location.isNotEmpty && !widget.preferredLocations.contains(location)) { - final List updatedList = - List.from(widget.preferredLocations)..add(location); + final List updatedList = List.from( + widget.preferredLocations, + )..add(location); widget.onLocationsChanged(updatedList); _locationController.clear(); - context - .read() - .add(const ProfileSetupClearLocationSuggestions()); + context.read().add( + const ProfileSetupClearLocationSuggestions(), + ); } } @@ -79,10 +81,16 @@ class _ProfileSetupLocationState extends State { // Search Input UiTextField( - label: t.staff_authentication.profile_setup_page.location + label: t + .staff_authentication + .profile_setup_page + .location .add_location_label, controller: _locationController, - hintText: t.staff_authentication.profile_setup_page.location + hintText: t + .staff_authentication + .profile_setup_page + .location .add_location_hint, onChanged: _onSearchChanged, ), @@ -99,15 +107,8 @@ class _ProfileSetupLocationState extends State { constraints: const BoxConstraints(maxHeight: 200), margin: const EdgeInsets.only(top: UiConstants.space2), decoration: BoxDecoration( - color: Theme.of(context).cardColor, + color: UiColors.cardViewBackground, borderRadius: UiConstants.radiusMd, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], ), child: ListView.separated( shrinkWrap: true, @@ -167,12 +168,18 @@ class _ProfileSetupLocationState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - t.staff_authentication.profile_setup_page.location + t + .staff_authentication + .profile_setup_page + .location .min_dist_label, style: UiTypography.footnote1r.textSecondary, ), Text( - t.staff_authentication.profile_setup_page.location + t + .staff_authentication + .profile_setup_page + .location .max_dist_label, style: UiTypography.footnote1r.textSecondary, ), @@ -185,8 +192,9 @@ class _ProfileSetupLocationState extends State { /// Removes the specified [location] from the list. void _removeLocation({required String location}) { - final List updatedList = - List.from(widget.preferredLocations)..remove(location); + final List updatedList = List.from( + widget.preferredLocations, + )..remove(location); widget.onLocationsChanged(updatedList); } } diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart b/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart new file mode 100644 index 00000000..ef1f34da --- /dev/null +++ b/apps/mobile/packages/features/staff/authentication/lib/src/staff_authentication_module.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_core/core.dart'; +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:firebase_auth/firebase_auth.dart' as firebase; +import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart'; +import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart'; +import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart'; +import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart'; +import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart'; +import 'package:staff_authentication/src/data/repositories_impl/profile_setup_repository_impl.dart'; +import 'package:staff_authentication/src/domain/usecases/submit_profile_setup_usecase.dart'; +import 'package:staff_authentication/src/domain/repositories/place_repository.dart'; +import 'package:staff_authentication/src/data/repositories_impl/place_repository_impl.dart'; +import 'package:staff_authentication/src/domain/usecases/search_cities_usecase.dart'; +import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart'; +import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart'; +import 'package:staff_authentication/src/presentation/pages/get_started_page.dart'; +import 'package:staff_authentication/src/presentation/pages/phone_verification_page.dart'; +import 'package:staff_authentication/src/presentation/pages/profile_setup_page.dart'; +import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; + +/// A [Module] for the staff authentication feature. +class StaffAuthenticationModule extends Module { + @override + List get imports => [DataConnectModule()]; + + @override + void binds(Injector i) { + // Repositories + i.addLazySingleton( + () => AuthRepositoryImpl( + firebaseAuth: firebase.FirebaseAuth.instance, + dataConnect: ExampleConnector.instance, + ), + ); + i.addLazySingleton( + () => ProfileSetupRepositoryImpl( + firebaseAuth: firebase.FirebaseAuth.instance, + dataConnect: ExampleConnector.instance, + ), + ); + i.addLazySingleton(PlaceRepositoryImpl.new); + + // UseCases + i.addLazySingleton(SignInWithPhoneUseCase.new); + i.addLazySingleton(VerifyOtpUseCase.new); + i.addLazySingleton(SubmitProfileSetup.new); + i.addLazySingleton(SearchCitiesUseCase.new); + + // BLoCs + i.addLazySingleton( + () => AuthBloc( + signInUseCase: i.get(), + verifyOtpUseCase: i.get(), + ), + ); + i.add( + () => ProfileSetupBloc( + submitProfileSetup: i.get(), + searchCities: i.get(), + ), + ); + } + + @override + void routes(RouteManager r) { + r.child(StaffPaths.root, child: (_) => const GetStartedPage()); + r.child( + StaffPaths.phoneVerification, + child: (BuildContext context) { + final Map? data = r.args.data; + final String? modeName = data?['mode']; + final AuthMode mode = AuthMode.values.firstWhere( + (AuthMode e) => e.name == modeName, + orElse: () => AuthMode.login, + ); + return PhoneVerificationPage(mode: mode); + }, + ); + r.child(StaffPaths.profileSetup, child: (_) => const ProfileSetupPage()); + } +} diff --git a/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart b/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart index f6265aff..6b4d54cc 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart @@ -1,91 +1,5 @@ -library staff_authentication; - -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:krow_core/core.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; -import 'package:firebase_auth/firebase_auth.dart' as firebase; -import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart'; -import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart'; -import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart'; -import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart'; -import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart'; -import 'package:staff_authentication/src/data/repositories_impl/profile_setup_repository_impl.dart'; -import 'package:staff_authentication/src/domain/usecases/submit_profile_setup_usecase.dart'; -import 'package:staff_authentication/src/domain/repositories/place_repository.dart'; -import 'package:staff_authentication/src/data/repositories_impl/place_repository_impl.dart'; -import 'package:staff_authentication/src/domain/usecases/search_cities_usecase.dart'; -import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart'; -import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart'; -import 'package:staff_authentication/src/presentation/pages/get_started_page.dart'; -import 'package:staff_authentication/src/presentation/pages/phone_verification_page.dart'; -import 'package:staff_authentication/src/presentation/pages/profile_setup_page.dart'; -import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart'; - export 'src/domain/ui_entities/auth_mode.dart'; export 'src/presentation/pages/get_started_page.dart'; export 'src/presentation/pages/phone_verification_page.dart'; export 'src/presentation/pages/profile_setup_page.dart'; -export 'package:core_localization/core_localization.dart'; - -/// A [Module] for the staff authentication feature. -class StaffAuthenticationModule extends Module { - @override - List get imports => [DataConnectModule()]; - - @override - void binds(Injector i) { - // Repositories - i.addLazySingleton( - () => AuthRepositoryImpl( - firebaseAuth: firebase.FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); - i.addLazySingleton( - () => ProfileSetupRepositoryImpl( - firebaseAuth: firebase.FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); - i.addLazySingleton(PlaceRepositoryImpl.new); - - // UseCases - i.addLazySingleton(SignInWithPhoneUseCase.new); - i.addLazySingleton(VerifyOtpUseCase.new); - i.addLazySingleton(SubmitProfileSetup.new); - i.addLazySingleton(SearchCitiesUseCase.new); - - // BLoCs - i.addLazySingleton( - () => AuthBloc( - signInUseCase: i.get(), - verifyOtpUseCase: i.get(), - ), - ); - i.add( - () => ProfileSetupBloc( - submitProfileSetup: i.get(), - searchCities: i.get(), - ), - ); - } - - @override - void routes(RouteManager r) { - r.child(StaffPaths.root, child: (_) => const GetStartedPage()); - r.child( - StaffPaths.phoneVerification, - child: (BuildContext context) { - final Map? data = r.args.data; - final String? modeName = data?['mode']; - final AuthMode mode = AuthMode.values.firstWhere( - (AuthMode e) => e.name == modeName, - orElse: () => AuthMode.login, - ); - return PhoneVerificationPage(mode: mode); - }, - ); - r.child(StaffPaths.profileSetup, child: (_) => const ProfileSetupPage()); - } -} +export 'src/staff_authentication_module.dart'; diff --git a/apps/mobile/packages/features/staff/authentication/pubspec.yaml b/apps/mobile/packages/features/staff/authentication/pubspec.yaml index 6a955e2e..966934ef 100644 --- a/apps/mobile/packages/features/staff/authentication/pubspec.yaml +++ b/apps/mobile/packages/features/staff/authentication/pubspec.yaml @@ -14,9 +14,8 @@ dependencies: flutter_bloc: ^8.1.0 flutter_modular: ^6.3.0 equatable: ^2.0.5 - lucide_icons: ^0.257.0 firebase_core: ^4.2.1 - firebase_auth: ^6.1.2 # Updated for compatibility + firebase_auth: ^6.1.2 firebase_data_connect: ^0.2.2+1 http: ^1.2.0 diff --git a/apps/mobile/packages/features/staff/availability/lib/src/presentation/pages/availability_page.dart b/apps/mobile/packages/features/staff/availability/lib/src/presentation/pages/availability_page.dart index 03ea6b07..bb5fb06e 100644 --- a/apps/mobile/packages/features/staff/availability/lib/src/presentation/pages/availability_page.dart +++ b/apps/mobile/packages/features/staff/availability/lib/src/presentation/pages/availability_page.dart @@ -2,14 +2,14 @@ import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension; +import 'package:flutter_modular/flutter_modular.dart' + hide ModularWatchExtension; import 'package:intl/intl.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:krow_domain/krow_domain.dart'; import '../blocs/availability_bloc.dart'; import '../blocs/availability_event.dart'; import '../blocs/availability_state.dart'; -import 'package:krow_domain/krow_domain.dart'; class AvailabilityPage extends StatefulWidget { const AvailabilityPage({super.key}); @@ -46,7 +46,6 @@ class _AvailabilityPageState extends State { return BlocProvider.value( value: _bloc, child: Scaffold( - backgroundColor: AppColors.krowBackground, appBar: UiAppBar( title: 'My Availability', centerTitle: false, @@ -55,13 +54,18 @@ class _AvailabilityPageState extends State { body: BlocListener( listener: (context, state) { if (state is AvailabilityLoaded && state.successMessage != null) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.successMessage!), - backgroundColor: Colors.green, - behavior: SnackBarBehavior.floating, - ), + UiSnackbar.show( + context, + message: state.successMessage!, + type: UiSnackbarType.success, + ); + } + + if (state is AvailabilityError) { + UiSnackbar.show( + context, + message: state.message, + type: UiSnackbarType.error, ); } else if (state is AvailabilityError) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); @@ -85,19 +89,19 @@ class _AvailabilityPageState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: UiConstants.space6, children: [ _buildQuickSet(context), - const SizedBox(height: 24), _buildWeekNavigation(context, state), - const SizedBox(height: 24), _buildSelectedDayAvailability( context, state.selectedDayAvailability, ), - const SizedBox(height: 24), _buildInfoCard(), ], ), @@ -106,12 +110,7 @@ class _AvailabilityPageState extends State { ), ), if (state.isActionInProgress) - Container( - color: Colors.black.withOpacity(0.3), - child: const Center( - child: CircularProgressIndicator(), - ), - ), + const UiLoadingPage(), // Show loading overlay during actions ], ); } else if (state is AvailabilityError) { @@ -124,14 +123,12 @@ class _AvailabilityPageState extends State { Text( translateErrorKey(state.message), textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - color: AppColors.krowMuted, - ), + style: UiTypography.body2r.textSecondary, ), ], ), ), + ), ); } return const SizedBox.shrink(); @@ -144,49 +141,31 @@ class _AvailabilityPageState extends State { Widget _buildQuickSet(BuildContext context) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: AppColors.krowBlue.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), + color: UiColors.primary.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( 'Quick Set Availability', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Color(0xFF333F48), - ), + style: UiTypography.body2b, ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( children: [ + Expanded(child: _buildQuickSetButton(context, 'All Week', 'all')), + const SizedBox(width: UiConstants.space2), Expanded( - child: _buildQuickSetButton( - context, - 'All Week', - 'all', - ), + child: _buildQuickSetButton(context, 'Weekdays', 'weekdays'), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Expanded( - child: _buildQuickSetButton( - context, - 'Weekdays', - 'weekdays', - ), + child: _buildQuickSetButton(context, 'Weekends', 'weekends'), ), - const SizedBox(width: 8), - Expanded( - child: _buildQuickSetButton( - context, - 'Weekends', - 'weekends', - ), - ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Expanded( child: _buildQuickSetButton( context, @@ -211,23 +190,26 @@ class _AvailabilityPageState extends State { return SizedBox( height: 32, child: OutlinedButton( - onPressed: () => context.read().add(PerformQuickSet(type)), + onPressed: () => + context.read().add(PerformQuickSet(type)), style: OutlinedButton.styleFrom( padding: EdgeInsets.zero, side: BorderSide( color: isDestructive - ? Colors.red.withOpacity(0.2) - : AppColors.krowBlue.withOpacity(0.2), + ? UiColors.destructive.withValues(alpha: 0.2) + : UiColors.primary.withValues(alpha: 0.2), ), - backgroundColor: Colors.transparent, + backgroundColor: UiColors.transparent, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), - foregroundColor: isDestructive ? Colors.red : AppColors.krowBlue, + foregroundColor: isDestructive + ? UiColors.destructive + : UiColors.primary, ), child: Text( label, - style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w500), + style: UiTypography.body4r, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -241,42 +223,35 @@ class _AvailabilityPageState extends State { final monthYear = DateFormat('MMMM yyyy').format(middleDate); return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey.shade100), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], + color: UiColors.cardViewBackground, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), ), child: Column( children: [ // Nav Header Padding( - padding: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.only(bottom: UiConstants.space4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildNavButton( - LucideIcons.chevronLeft, - () => context.read().add(const NavigateWeek(-1)), + UiIcons.chevronLeft, + () => context.read().add( + const NavigateWeek(-1), + ), ), Text( monthYear, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.krowCharcoal, - ), + style: UiTypography.title2b, ), _buildNavButton( - LucideIcons.chevronRight, - () => context.read().add(const NavigateWeek(1)), + UiIcons.chevronRight, + () => context.read().add( + const NavigateWeek(1), + ), ), ], ), @@ -284,7 +259,9 @@ class _AvailabilityPageState extends State { // Days Row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: state.days.map((day) => _buildDayItem(context, day, state.selectedDate)).toList(), + children: state.days + .map((day) => _buildDayItem(context, day, state.selectedDate)) + .toList(), ), ], ), @@ -298,15 +275,19 @@ class _AvailabilityPageState extends State { width: 32, height: 32, decoration: const BoxDecoration( - color: Color(0xFFF1F5F9), // slate-100 + color: UiColors.separatorSecondary, shape: BoxShape.circle, ), - child: Icon(icon, size: 20, color: AppColors.krowMuted), + child: Icon(icon, size: 20, color: UiColors.iconSecondary), ), ); } - Widget _buildDayItem(BuildContext context, DayAvailability day, DateTime selectedDate) { + Widget _buildDayItem( + BuildContext context, + DayAvailability day, + DateTime selectedDate, + ) { final isSelected = AvailabilityLoaded.isSameDay(day.date, selectedDate); final isAvailable = day.isAvailable; final isToday = AvailabilityLoaded.isSameDay(day.date, DateTime.now()); @@ -316,30 +297,19 @@ class _AvailabilityPageState extends State { onTap: () => context.read().add(SelectDate(day.date)), child: Container( margin: const EdgeInsets.symmetric(horizontal: 2), - padding: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: UiConstants.space3), decoration: BoxDecoration( color: isSelected - ? AppColors.krowBlue - : (isAvailable - ? const Color(0xFFECFDF5) - : const Color(0xFFF8FAFC)), // emerald-50 or slate-50 - borderRadius: BorderRadius.circular(16), + ? UiColors.primary + : (isAvailable ? UiColors.tagSuccess : UiColors.bgSecondary), + borderRadius: UiConstants.radiusLg, border: Border.all( color: isSelected - ? AppColors.krowBlue + ? UiColors.primary : (isAvailable - ? const Color(0xFFA7F3D0) - : Colors.transparent), // emerald-200 + ? UiColors.success.withValues(alpha: 0.3) + : UiColors.transparent), ), - boxShadow: isSelected - ? [ - BoxShadow( - color: AppColors.krowBlue.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ] - : null, ), child: Stack( clipBehavior: Clip.none, @@ -349,26 +319,24 @@ class _AvailabilityPageState extends State { children: [ Text( day.date.day.toString().padLeft(2, '0'), - style: TextStyle( - fontSize: 18, + style: UiTypography.title1m.copyWith( fontWeight: FontWeight.bold, color: isSelected - ? Colors.white + ? UiColors.white : (isAvailable - ? const Color(0xFF047857) - : AppColors.krowMuted), // emerald-700 + ? UiColors.textSuccess + : UiColors.textSecondary), ), ), const SizedBox(height: 2), Text( - DateFormat('EEE').format(day.date), - style: TextStyle( - fontSize: 10, + DateFormat('EEE').format(day.date), + style: UiTypography.footnote2r.copyWith( color: isSelected - ? Colors.white.withOpacity(0.8) + ? UiColors.white.withValues(alpha: 0.8) : (isAvailable - ? const Color(0xFF047857) - : AppColors.krowMuted), + ? UiColors.textSuccess + : UiColors.textSecondary), ), ), ], @@ -380,7 +348,7 @@ class _AvailabilityPageState extends State { width: 6, height: 6, decoration: const BoxDecoration( - color: AppColors.krowBlue, + color: UiColors.primary, shape: BoxShape.circle, ), ), @@ -400,18 +368,11 @@ class _AvailabilityPageState extends State { final isAvailable = day.isAvailable; return Container( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey.shade100), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], + color: UiColors.cardViewBackground, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), ), child: Column( children: [ @@ -424,114 +385,112 @@ class _AvailabilityPageState extends State { children: [ Text( dateStr, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.krowCharcoal, - ), + style: UiTypography.title2b, ), Text( isAvailable ? 'You are available' : 'Not available', - style: const TextStyle( - fontSize: 14, - color: AppColors.krowMuted, - ), + style: UiTypography.body2r.textSecondary, ), ], ), Switch( value: isAvailable, - onChanged: (val) => context.read().add(ToggleDayStatus(day)), - activeColor: AppColors.krowBlue, + onChanged: (val) => + context.read().add(ToggleDayStatus(day)), + activeColor: UiColors.primary, ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Time Slots (only from Domain) ...day.slots.map((slot) { // Get UI config for this slot ID final uiConfig = _getSlotUiConfig(slot.id); - + return _buildTimeSlotItem(context, day, slot, uiConfig); }).toList(), ], ), ); } - + Map _getSlotUiConfig(String slotId) { switch (slotId) { case 'morning': return { - 'icon': LucideIcons.sunrise, - 'bg': const Color(0xFFE6EBF9), // bg-[#0032A0]/10 - 'iconColor': const Color(0xFF0032A0), + 'icon': UiIcons.sunrise, + 'bg': UiColors.primary.withValues(alpha: 0.1), + 'iconColor': UiColors.primary, }; case 'afternoon': return { - 'icon': LucideIcons.sun, - 'bg': const Color(0xFFCCD6EC), // bg-[#0032A0]/20 - 'iconColor': const Color(0xFF0032A0), + 'icon': UiIcons.sun, + 'bg': UiColors.primary.withValues(alpha: 0.2), + 'iconColor': UiColors.primary, }; case 'evening': return { - 'icon': LucideIcons.moon, - 'bg': const Color(0xFFEBEDEE), // bg-[#333F48]/10 - 'iconColor': const Color(0xFF333F48), + 'icon': UiIcons.moon, + 'bg': UiColors.bgSecondary, + 'iconColor': UiColors.foreground, }; default: return { - 'icon': LucideIcons.clock, - 'bg': Colors.grey.shade100, - 'iconColor': Colors.grey, + 'icon': UiIcons.clock, + 'bg': UiColors.bgSecondary, + 'iconColor': UiColors.iconSecondary, }; } } Widget _buildTimeSlotItem( - BuildContext context, - DayAvailability day, - AvailabilitySlot slot, - Map uiConfig + BuildContext context, + DayAvailability day, + AvailabilitySlot slot, + Map uiConfig, ) { // Determine styles based on state - final isEnabled = day.isAvailable; + final isEnabled = day.isAvailable; final isActive = slot.isAvailable; - + // Container style Color bgColor; Color borderColor; if (!isEnabled) { - bgColor = const Color(0xFFF8FAFC); // slate-50 - borderColor = const Color(0xFFF1F5F9); // slate-100 + bgColor = UiColors.bgSecondary; + borderColor = UiColors.borderInactive; } else if (isActive) { - bgColor = AppColors.krowBlue.withOpacity(0.05); - borderColor = AppColors.krowBlue.withOpacity(0.2); + bgColor = UiColors.primary.withValues(alpha: 0.05); + borderColor = UiColors.primary.withValues(alpha: 0.2); } else { - bgColor = const Color(0xFFF8FAFC); // slate-50 - borderColor = const Color(0xFFE2E8F0); // slate-200 + bgColor = UiColors.bgSecondary; + borderColor = UiColors.borderPrimary; } // Text colors final titleColor = (isEnabled && isActive) - ? AppColors.krowCharcoal - : AppColors.krowMuted; + ? UiColors.foreground + : UiColors.mutedForeground; final subtitleColor = (isEnabled && isActive) - ? AppColors.krowMuted - : Colors.grey.shade400; + ? UiColors.mutedForeground + : UiColors.textInactive; return GestureDetector( - onTap: isEnabled ? () => context.read().add(ToggleSlotStatus(day, slot.id)) : null, + onTap: isEnabled + ? () => context.read().add( + ToggleSlotStatus(day, slot.id), + ) + : null, child: AnimatedContainer( duration: const Duration(milliseconds: 200), - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(bottom: UiConstants.space3), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: bgColor, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: borderColor, width: 2), ), child: Row( @@ -542,7 +501,7 @@ class _AvailabilityPageState extends State { height: 40, decoration: BoxDecoration( color: uiConfig['bg'], - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: Icon( uiConfig['icon'], @@ -550,7 +509,7 @@ class _AvailabilityPageState extends State { size: 20, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), // Text Expanded( child: Column( @@ -558,18 +517,11 @@ class _AvailabilityPageState extends State { children: [ Text( slot.label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: titleColor, - ), + style: UiTypography.body2m.copyWith(color: titleColor), ), Text( slot.timeRange, - style: TextStyle( - fontSize: 12, - color: subtitleColor, - ), + style: UiTypography.body3r.copyWith(color: subtitleColor), ), ], ), @@ -580,13 +532,13 @@ class _AvailabilityPageState extends State { width: 24, height: 24, decoration: const BoxDecoration( - color: AppColors.krowBlue, + color: UiColors.primary, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.check, + UiIcons.check, size: 16, - color: Colors.white, + color: UiColors.white, ), ) else if (isEnabled && !isActive) @@ -596,9 +548,9 @@ class _AvailabilityPageState extends State { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: const Color(0xFFCBD5E1), + color: UiColors.borderStill, width: 2, - ), // slate-300 + ), ), ), ], @@ -609,32 +561,28 @@ class _AvailabilityPageState extends State { Widget _buildInfoCard() { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: AppColors.krowBlue.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), + color: UiColors.primary.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), - child: const Row( + child: Row( crossAxisAlignment: CrossAxisAlignment.start, + spacing: UiConstants.space3, children: [ - Icon(LucideIcons.clock, size: 20, color: AppColors.krowBlue), - SizedBox(width: 12), + const Icon(UiIcons.clock, size: 20, color: UiColors.primary), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: UiConstants.space1, children: [ Text( 'Auto-Match uses your availability', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: AppColors.krowCharcoal, - ), + style: UiTypography.body2m, ), - SizedBox(height: 2), Text( "When enabled, you'll only be matched with shifts during your available times.", - style: TextStyle(fontSize: 12, color: AppColors.krowMuted), + style: UiTypography.body3r.textSecondary, ), ], ), @@ -644,15 +592,3 @@ class _AvailabilityPageState extends State { ); } } - -class AppColors { - static const Color krowBlue = Color(0xFF0A39DF); - static const Color krowYellow = Color(0xFFFFED4A); - static const Color krowCharcoal = Color(0xFF121826); - static const Color krowMuted = Color(0xFF6A7382); - static const Color krowBorder = Color(0xFFE3E6E9); - static const Color krowBackground = Color(0xFFFAFBFC); - - static const Color white = Colors.white; - static const Color black = Colors.black; -} diff --git a/apps/mobile/packages/features/staff/availability/pubspec.yaml b/apps/mobile/packages/features/staff/availability/pubspec.yaml index fe5ef376..b8353e1a 100644 --- a/apps/mobile/packages/features/staff/availability/pubspec.yaml +++ b/apps/mobile/packages/features/staff/availability/pubspec.yaml @@ -1,22 +1,17 @@ name: staff_availability description: Staff Availability Feature version: 0.0.1 -publish_to: 'none' +publish_to: "none" resolution: workspace environment: - sdk: '>=3.10.0 <4.0.0' + sdk: ">=3.10.0 <4.0.0" flutter: ">=1.17.0" dependencies: flutter: sdk: flutter - flutter_bloc: ^8.1.3 - equatable: ^2.0.5 - intl: ^0.20.0 - lucide_icons: ^0.257.0 - flutter_modular: ^6.3.2 - + # Internal packages core_localization: path: ../../../core_localization @@ -28,6 +23,11 @@ dependencies: path: ../../../data_connect krow_core: path: ../../../core + + flutter_bloc: ^8.1.3 + equatable: ^2.0.5 + intl: ^0.20.0 + flutter_modular: ^6.3.2 firebase_data_connect: ^0.2.2+2 firebase_auth: ^6.1.4 diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/pages/clock_in_page.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/pages/clock_in_page.dart index 6240ad4a..59a233f5 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/pages/clock_in_page.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/pages/clock_in_page.dart @@ -5,12 +5,10 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:intl/intl.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import '../bloc/clock_in_bloc.dart'; import '../bloc/clock_in_event.dart'; import '../bloc/clock_in_state.dart'; -import '../theme/app_colors.dart'; import '../widgets/commute_tracker.dart'; import '../widgets/date_selector.dart'; import '../widgets/lunch_break_modal.dart'; @@ -40,10 +38,10 @@ class _ClockInPageState extends State { listener: (BuildContext context, ClockInState state) { if (state.status == ClockInStatus.failure && state.errorMessage != null) { - ScaffoldMessenger.of( + UiSnackbar.show( context, - ).showSnackBar( - SnackBar(content: Text(translateErrorKey(state.errorMessage!))), + message: translateErrorKey(state.errorMessage!), + type: UiSnackbarType.error, ); } }, @@ -67,14 +65,6 @@ class _ClockInPageState extends State { final bool isCheckedIn = state.attendance.isCheckedIn && isActiveSelected; - // Format times for display - final String checkInStr = checkInTime != null - ? DateFormat('h:mm a').format(checkInTime) - : '--:-- --'; - final String checkOutStr = checkOutTime != null - ? DateFormat('h:mm a').format(checkOutTime) - : '--:-- --'; - return Scaffold( appBar: UiAppBar( titleWidget: Text( @@ -94,7 +84,9 @@ class _ClockInPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -113,26 +105,22 @@ class _ClockInPageState extends State { // Date Selector DateSelector( selectedDate: state.selectedDate, - onSelect: (DateTime date) => _bloc.add(DateSelected(date)), + onSelect: (DateTime date) => + _bloc.add(DateSelected(date)), shiftDates: [ DateFormat('yyyy-MM-dd').format(DateTime.now()), ], ), - const SizedBox(height: 20), - + const SizedBox(height: UiConstants.space5), // Your Activity Header - const Text( + Text( "Your Activity", textAlign: TextAlign.start, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.krowCharcoal, - ), + style: UiTypography.headline4m, ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Selected Shift Info Card if (todayShifts.isNotEmpty) @@ -143,19 +131,21 @@ class _ClockInPageState extends State { onTap: () => _bloc.add(ShiftSelected(shift)), child: Container( - padding: const EdgeInsets.all(12), - margin: - const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all( + UiConstants.space3, + ), + margin: const EdgeInsets.only( + bottom: UiConstants.space3, + ), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular( - 12, - ), + color: UiColors.white, + borderRadius: + UiConstants.radiusLg, border: Border.all( color: shift.id == selectedShift?.id - ? AppColors.krowBlue - : const Color(0xFFE2E8F0), + ? UiColors.primary + : UiColors.border, width: shift.id == selectedShift?.id ? 2 @@ -176,34 +166,25 @@ class _ClockInPageState extends State { selectedShift?.id ? "SELECTED SHIFT" : "TODAY'S SHIFT", - style: TextStyle( - fontSize: 10, - fontWeight: - FontWeight.w600, + style: UiTypography + .titleUppercase4b + .copyWith( color: shift.id == selectedShift?.id - ? AppColors.krowBlue - : AppColors - .krowCharcoal, - letterSpacing: 0.5, + ? UiColors.primary + : UiColors + .textSecondary, ), ), const SizedBox(height: 2), Text( shift.title, - style: const TextStyle( - fontSize: 14, - fontWeight: - FontWeight.w600, - color: Color(0xFF1E293B), - ), + style: UiTypography.body2b, ), Text( "${shift.clientName} • ${shift.location}", - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - ), + style: UiTypography.body3r + .textSecondary, ), ], ), @@ -214,18 +195,14 @@ class _ClockInPageState extends State { children: [ Text( "${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}", - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF475569), - ), + style: UiTypography.body3m + .textSecondary, ), Text( "\$${shift.hourlyRate}/hr", - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: AppColors.krowBlue, + style: UiTypography.body3m + .copyWith( + color: UiColors.primary, ), ), ], @@ -239,46 +216,41 @@ class _ClockInPageState extends State { ), // Swipe To Check In / Checked Out State / No Shift State - if (selectedShift != null && checkOutTime == null) ...[ - if (!isCheckedIn && - !_isCheckInAllowed(selectedShift)) - Container( - width: double.infinity, - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), // slate-100 - borderRadius: BorderRadius.circular(16), - ), - child: Column( - children: [ - const Icon( - LucideIcons.clock, - size: 48, - color: Color(0xFF94A3B8), // slate-400 - ), - const SizedBox(height: 16), - const Text( - "You're early!", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xFF475569), // slate-600 - ), - ), - const SizedBox(height: 4), - Text( - "Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}", - style: const TextStyle( - fontSize: 14, - color: Color(0xFF64748B), // slate-500 - ), - textAlign: TextAlign.center, - ), - ], - ), - ) - else - SwipeToCheckIn( + if (selectedShift != null && + checkOutTime == null) ...[ + if (!isCheckedIn && + !_isCheckInAllowed(selectedShift)) + Container( + width: double.infinity, + padding: + const EdgeInsets.all(UiConstants.space6), + decoration: BoxDecoration( + color: UiColors.bgSecondary, + borderRadius: UiConstants.radiusLg, + ), + child: Column( + children: [ + const Icon( + UiIcons.clock, + size: 48, + color: UiColors.iconThird, + ), + const SizedBox(height: UiConstants.space4), + Text( + "You're early!", + style: UiTypography.body1m.textSecondary, + ), + const SizedBox(height: UiConstants.space1), + Text( + "Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}", + style: UiTypography.body2r.textSecondary, + textAlign: TextAlign.center, + ), + ], + ), + ) + else + SwipeToCheckIn( isCheckedIn: isCheckedIn, mode: state.checkInMode, isLoading: @@ -299,14 +271,17 @@ class _ClockInPageState extends State { onCheckOut: () { showDialog( context: context, - builder: (BuildContext context) => LunchBreakDialog( - onComplete: () { - Navigator.of( - context, - ).pop(); // Close dialog first - _bloc.add(const CheckOutRequested()); - }, - ), + builder: (BuildContext context) => + LunchBreakDialog( + onComplete: () { + Navigator.of( + context, + ).pop(); // Close dialog first + _bloc.add( + const CheckOutRequested(), + ); + }, + ), ); }, ), @@ -314,13 +289,15 @@ class _ClockInPageState extends State { checkOutTime != null) ...[ // Shift Completed State Container( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(UiConstants.space6), decoration: BoxDecoration( - color: const Color(0xFFECFDF5), // emerald-50 - borderRadius: BorderRadius.circular(16), + color: UiColors.tagSuccess, + borderRadius: UiConstants.radiusLg, border: Border.all( - color: const Color(0xFFA7F3D0), - ), // emerald-200 + color: UiColors.success.withValues( + alpha: 0.3, + ), + ), ), child: Column( children: [ @@ -328,31 +305,24 @@ class _ClockInPageState extends State { width: 48, height: 48, decoration: const BoxDecoration( - color: Color(0xFFD1FAE5), // emerald-100 + color: UiColors.tagActive, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.check, - color: Color(0xFF059669), // emerald-600 + UiIcons.check, + color: UiColors.textSuccess, size: 24, ), ), - const SizedBox(height: 12), - const Text( + const SizedBox(height: UiConstants.space3), + Text( "Shift Completed!", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xFF065F46), // emerald-800 - ), + style: UiTypography.body1b.textSuccess, ), - const SizedBox(height: 4), - const Text( + const SizedBox(height: UiConstants.space1), + Text( "Great work today", - style: TextStyle( - fontSize: 14, - color: Color(0xFF059669), // emerald-600 - ), + style: UiTypography.body2r.textSuccess, ), ], ), @@ -361,29 +331,22 @@ class _ClockInPageState extends State { // No Shift State Container( width: double.infinity, - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(UiConstants.space6), decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), // slate-100 - borderRadius: BorderRadius.circular(16), + color: UiColors.bgSecondary, + borderRadius: UiConstants.radiusLg, ), - child: const Column( + child: Column( children: [ Text( "No confirmed shifts for today", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Color(0xFF475569), // slate-600 - ), + style: UiTypography.body1m.textSecondary, textAlign: TextAlign.center, ), - SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( "Accept a shift to clock in", - style: TextStyle( - fontSize: 14, - color: Color(0xFF64748B), // slate-500 - ), + style: UiTypography.body2r.textSecondary, textAlign: TextAlign.center, ), ], @@ -393,15 +356,17 @@ class _ClockInPageState extends State { // Checked In Banner if (isCheckedIn && checkInTime != null) ...[ - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(UiConstants.space3), decoration: BoxDecoration( - color: const Color(0xFFECFDF5), // emerald-50 - borderRadius: BorderRadius.circular(12), + color: UiColors.tagSuccess, + borderRadius: UiConstants.radiusLg, border: Border.all( - color: const Color(0xFFA7F3D0), - ), // emerald-200 + color: UiColors.success.withValues( + alpha: 0.3, + ), + ), ), child: Row( mainAxisAlignment: @@ -411,23 +376,15 @@ class _ClockInPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "Checked in at", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF059669), - ), + style: UiTypography.body3m.textSuccess, ), Text( DateFormat( 'h:mm a', ).format(checkInTime), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Color(0xFF065F46), - ), + style: UiTypography.body1b.textSuccess, ), ], ), @@ -435,12 +392,12 @@ class _ClockInPageState extends State { width: 40, height: 40, decoration: const BoxDecoration( - color: Color(0xFFD1FAE5), + color: UiColors.tagActive, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.check, - color: Color(0xFF059669), + UiIcons.check, + color: UiColors.textSuccess, ), ), ], @@ -476,14 +433,14 @@ class _ClockInPageState extends State { child: GestureDetector( onTap: () => _bloc.add(CheckInModeChanged(value)), child: Container( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: UiConstants.space2), decoration: BoxDecoration( - color: isSelected ? Colors.white : Colors.transparent, - borderRadius: BorderRadius.circular(8), + color: isSelected ? UiColors.white : UiColors.transparent, + borderRadius: UiConstants.radiusMd, boxShadow: isSelected ? [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -496,15 +453,15 @@ class _ClockInPageState extends State { Icon( icon, size: 16, - color: isSelected ? Colors.black : Colors.grey, + color: isSelected ? UiColors.foreground : UiColors.iconThird, ), const SizedBox(width: 6), Text( label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: isSelected ? Colors.black : Colors.grey, + style: UiTypography.body2m.copyWith( + color: isSelected + ? UiColors.foreground + : UiColors.textSecondary, ), ), ], @@ -534,36 +491,31 @@ class _ClockInPageState extends State { height: 96, decoration: BoxDecoration( color: scanned - ? Colors.green.shade50 - : Colors.blue.shade50, + ? UiColors.tagSuccess + : UiColors.tagInProgress, shape: BoxShape.circle, ), child: Icon( - scanned ? LucideIcons.check : LucideIcons.nfc, + scanned ? UiIcons.check : UiIcons.nfc, size: 48, - color: scanned - ? Colors.green.shade600 - : Colors.blue.shade600, + color: scanned ? UiColors.textSuccess : UiColors.primary, ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Text( scanned ? 'Processing check-in...' : 'Ready to scan', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), + style: UiTypography.headline4m, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( scanned ? 'Please wait...' : 'Hold your phone near the NFC tag at the clock-in station', textAlign: TextAlign.center, - style: TextStyle(fontSize: 14, color: Colors.grey.shade600), + style: UiTypography.body2r.textSecondary, ), if (!scanned) ...[ - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), SizedBox( width: double.infinity, height: 56, @@ -584,19 +536,16 @@ class _ClockInPageState extends State { // But this dialog is just a function call. // It's safer to just return a result }, - icon: const Icon(LucideIcons.nfc, size: 24), - label: const Text( + icon: const Icon(UiIcons.nfc, size: 24), + label: Text( 'Tap to Scan', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), + style: UiTypography.headline4m.white, ), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0047FF), - foregroundColor: Colors.white, + backgroundColor: UiColors.primary, + foregroundColor: UiColors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), ), ), diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/theme/app_colors.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/theme/app_colors.dart deleted file mode 100644 index a41fe11f..00000000 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/theme/app_colors.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/material.dart'; - -class AppColors { - static const Color krowBlue = Color(0xFF0A39DF); - static const Color krowYellow = Color(0xFFFFED4A); - static const Color krowCharcoal = Color(0xFF121826); - static const Color krowMuted = Color(0xFF6A7382); - static const Color krowBorder = Color(0xFFE3E6E9); - static const Color krowBackground = Color(0xFFFAFBFC); - - static const Color white = Colors.white; - static const Color black = Colors.black; -} diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/attendance_card.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/attendance_card.dart index 9f5f07dd..fc187fdb 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/attendance_card.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/attendance_card.dart @@ -1,10 +1,9 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; enum AttendanceType { checkin, checkout, breaks, days } class AttendanceCard extends StatelessWidget { - const AttendanceCard({ super.key, required this.type, @@ -24,14 +23,14 @@ class AttendanceCard extends StatelessWidget { final _AttendanceStyle styles = _getStyles(type); return Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(UiConstants.space3), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey.shade100), + color: UiColors.white, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.bgSecondary), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -46,17 +45,14 @@ class AttendanceCard extends StatelessWidget { height: 32, decoration: BoxDecoration( color: styles.bgColor, - borderRadius: BorderRadius.circular(8), + borderRadius: UiConstants.radiusMd, ), child: Icon(styles.icon, size: 16, color: styles.iconColor), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( title, - style: const TextStyle( - fontSize: 11, - color: Color(0xFF64748B), // slate-500 - ), + style: UiTypography.titleUppercase4m.textSecondary, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -65,27 +61,20 @@ class AttendanceCard extends StatelessWidget { fit: BoxFit.scaleDown, child: Text( value, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.headline4m, ), ), if (scheduledTime != null) ...[ const SizedBox(height: 2), Text( "Scheduled: $scheduledTime", - style: const TextStyle( - fontSize: 10, - color: Color(0xFF94A3B8), // slate-400 - ), + style: UiTypography.footnote2r.textInactive, ), ], const SizedBox(height: 2), Text( subtitle, - style: const TextStyle(fontSize: 12, color: Color(0xFF0032A0)), + style: UiTypography.footnote1r.copyWith(color: UiColors.primary), ), ], ), @@ -96,27 +85,27 @@ class AttendanceCard extends StatelessWidget { switch (type) { case AttendanceType.checkin: return _AttendanceStyle( - icon: LucideIcons.logIn, - bgColor: const Color(0xFF0032A0).withOpacity(0.1), - iconColor: const Color(0xFF0032A0), + icon: UiIcons.logIn, + bgColor: UiColors.primary.withValues(alpha: 0.1), + iconColor: UiColors.primary, ); case AttendanceType.checkout: return _AttendanceStyle( - icon: LucideIcons.logOut, - bgColor: const Color(0xFF333F48).withOpacity(0.1), - iconColor: const Color(0xFF333F48), + icon: UiIcons.logOut, + bgColor: UiColors.foreground.withValues(alpha: 0.1), + iconColor: UiColors.foreground, ); case AttendanceType.breaks: return _AttendanceStyle( - icon: LucideIcons.coffee, - bgColor: const Color(0xFFF9E547).withOpacity(0.2), - iconColor: const Color(0xFF4C460D), + icon: UiIcons.coffee, + bgColor: UiColors.accent.withValues(alpha: 0.2), + iconColor: UiColors.accentForeground, ); case AttendanceType.days: return _AttendanceStyle( - icon: LucideIcons.calendar, - bgColor: Colors.green.withOpacity(0.1), - iconColor: Colors.green, + icon: UiIcons.calendar, + bgColor: UiColors.success.withValues(alpha: 0.1), + iconColor: UiColors.textSuccess, ); } } diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/commute_tracker.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/commute_tracker.dart index 8f3726eb..e251b6cb 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/commute_tracker.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/commute_tracker.dart @@ -1,6 +1,5 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; -import '../theme/app_colors.dart'; import 'package:krow_domain/krow_domain.dart'; enum CommuteMode { @@ -158,21 +157,21 @@ class _CommuteTrackerState extends State { Widget _buildConsentCard() { return Container( - margin: const EdgeInsets.only(bottom: 20), - padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(bottom: UiConstants.space5), + padding: const EdgeInsets.all(UiConstants.space3), decoration: BoxDecoration( - gradient: const LinearGradient( + gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFFEFF6FF), // blue-50 - Color(0xFFECFEFF), // cyan-50 + UiColors.primary.withValues(alpha: 0.05), + UiColors.primary.withValues(alpha: 0.1), ], ), - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -188,42 +187,35 @@ class _CommuteTrackerState extends State { width: 32, height: 32, decoration: const BoxDecoration( - color: Color(0xFF2563EB), // blue-600 + color: UiColors.primary, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.mapPin, + UiIcons.mapPin, size: 16, - color: Colors.white, + color: UiColors.white, ), ), - const SizedBox(width: 12), - const Expanded( + const SizedBox(width: UiConstants.space3), + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Enable Commute Tracking?', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.body2m.textPrimary, ), - SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( 'Share location 1hr before shift so your manager can see you\'re on the way.', - style: TextStyle( - fontSize: 12, - color: Color(0xFF475569), // slate-600 - ), + style: UiTypography.body4r.textSecondary, ), ], ), ), ], ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( children: [ Expanded( @@ -232,25 +224,29 @@ class _CommuteTrackerState extends State { setState(() => _localHasConsent = false); }, style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 8), - side: const BorderSide(color: Color(0xFFE2E8F0)), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space2, + ), + side: const BorderSide(color: UiColors.border), ), - child: const Text('Not Now', style: TextStyle(fontSize: 12)), + child: Text('Not Now', style: UiTypography.footnote1m), ), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Expanded( child: ElevatedButton( onPressed: () { setState(() => _localHasConsent = true); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF2563EB), // blue-600 - padding: const EdgeInsets.symmetric(vertical: 8), + backgroundColor: UiColors.primary, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space2, + ), ), - child: const Text( + child: Text( 'Enable', - style: TextStyle(fontSize: 12, color: Colors.white), + style: UiTypography.footnote1m.white, ), ), ), @@ -263,14 +259,14 @@ class _CommuteTrackerState extends State { Widget _buildPreShiftCard() { return Container( - margin: const EdgeInsets.only(bottom: 20), - padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(bottom: UiConstants.space5), + padding: const EdgeInsets.all(UiConstants.space3), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: UiColors.white, + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -282,56 +278,46 @@ class _CommuteTrackerState extends State { width: 32, height: 32, decoration: const BoxDecoration( - color: Color(0xFFF1F5F9), // slate-100 + color: UiColors.bgSecondary, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.navigation, + UiIcons.navigation, size: 16, - color: Color(0xFF475569), // slate-600 + color: UiColors.textSecondary, ), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - const Text( + Text( 'On My Way', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.body2m.textPrimary, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Row( children: [ const Icon( - LucideIcons.clock, + UiIcons.clock, size: 12, - color: Color(0xFF64748B), // slate-500 + color: UiColors.textInactive, ), const SizedBox(width: 2), Text( 'Shift starts in ${_getMinutesUntilShift()} min', - style: const TextStyle( - fontSize: 11, - color: Color(0xFF64748B), // slate-500 - ), + style: UiTypography.titleUppercase4m.textSecondary, ), ], ), ], ), - const Text( + Text( 'Track arrival', - style: TextStyle( - fontSize: 10, - color: Color(0xFF64748B), // slate-500 - ), + style: UiTypography.titleUppercase4m.textSecondary, ), ], ), @@ -342,7 +328,7 @@ class _CommuteTrackerState extends State { setState(() => _localIsCommuteOn = value); widget.onCommuteToggled?.call(value); }, - activeThumbColor: AppColors.krowBlue, + activeThumbColor: UiColors.primary, ), ], ), @@ -357,8 +343,8 @@ class _CommuteTrackerState extends State { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFF2563EB), // blue-600 - Color(0xFF0891B2), // cyan-600 + UiColors.primary, + UiColors.iconActive, ], ), ), @@ -368,7 +354,7 @@ class _CommuteTrackerState extends State { Expanded( child: Center( child: Padding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -383,13 +369,13 @@ class _CommuteTrackerState extends State { width: 96, height: 96, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: UiColors.white.withValues(alpha: 0.2), shape: BoxShape.circle, ), child: const Icon( - LucideIcons.navigation, + UiIcons.navigation, size: 48, - color: Colors.white, + color: UiColors.white, ), ), ); @@ -399,100 +385,84 @@ class _CommuteTrackerState extends State { setState(() {}); }, ), - const SizedBox(height: 24), - const Text( + const SizedBox(height: UiConstants.space6), + Text( 'On My Way', - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + style: UiTypography.displayMb.white, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( 'Your manager can see you\'re heading to the site', - style: TextStyle( - fontSize: 14, - color: Colors.blue.shade100, + style: UiTypography.body2r.copyWith( + color: UiColors.primaryForeground.withValues(alpha: 0.8), ), textAlign: TextAlign.center, ), - const SizedBox(height: 32), + const SizedBox(height: UiConstants.space8), if (widget.distanceMeters != null) ...[ Container( width: double.infinity, constraints: const BoxConstraints(maxWidth: 300), - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), + color: UiColors.white.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, border: Border.all( - color: Colors.white.withOpacity(0.2), + color: UiColors.white.withValues(alpha: 0.2), ), ), child: Column( children: [ Text( 'Distance to Site', - style: TextStyle( - fontSize: 14, - color: Colors.blue.shade100, + style: UiTypography.body2r.copyWith( + color: UiColors.primaryForeground.withValues(alpha: 0.8), ), ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( _formatDistance(widget.distanceMeters!), - style: const TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + style: UiTypography.displayM.white, ), ], ), ), if (widget.etaMinutes != null) ...[ - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Container( width: double.infinity, constraints: const BoxConstraints(maxWidth: 300), - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), + color: UiColors.white.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, border: Border.all( - color: Colors.white.withOpacity(0.2), + color: UiColors.white.withValues(alpha: 0.2), ), ), child: Column( children: [ Text( 'Estimated Arrival', - style: TextStyle( - fontSize: 14, - color: Colors.blue.shade100, + style: UiTypography.body2r.copyWith( + color: UiColors.primaryForeground.withValues(alpha: 0.8), ), ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( '${widget.etaMinutes} min', - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + style: UiTypography.headline1m.white, ), ], ), ), ], ], - const SizedBox(height: 32), + const SizedBox(height: UiConstants.space8), Text( 'Most app features are locked while commute mode is on. You\'ll be able to clock in once you arrive.', - style: TextStyle( - fontSize: 12, - color: Colors.blue.shade100, + style: UiTypography.footnote1r.copyWith( + color: UiColors.primaryForeground.withValues(alpha: 0.8), ), textAlign: TextAlign.center, ), @@ -502,18 +472,20 @@ class _CommuteTrackerState extends State { ), ), Padding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), child: OutlinedButton( onPressed: () { setState(() => _localIsCommuteOn = false); }, style: OutlinedButton.styleFrom( - foregroundColor: Colors.white, - side: BorderSide(color: Colors.white.withOpacity(0.3)), - padding: const EdgeInsets.symmetric(vertical: 16), + foregroundColor: UiColors.white, + side: BorderSide(color: UiColors.white.withValues(alpha: 0.3)), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), minimumSize: const Size(double.infinity, 48), ), - child: const Text('Turn Off Commute Mode'), + child: Text('Turn Off Commute Mode', style: UiTypography.buttonL), ), ), ], @@ -524,21 +496,21 @@ class _CommuteTrackerState extends State { Widget _buildArrivedCard() { return Container( - margin: const EdgeInsets.only(bottom: 20), - padding: const EdgeInsets.all(20), + margin: const EdgeInsets.only(bottom: UiConstants.space5), + padding: const EdgeInsets.all(UiConstants.space5), decoration: BoxDecoration( - gradient: const LinearGradient( + gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFFECFDF5), // emerald-50 - Color(0xFFD1FAE5), // green-50 + UiColors.tagSuccess, + UiColors.tagActive, ], ), - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: UiColors.black.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 2), ), @@ -550,31 +522,24 @@ class _CommuteTrackerState extends State { width: 64, height: 64, decoration: const BoxDecoration( - color: Color(0xFF10B981), // emerald-500 + color: UiColors.success, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.checkCircle, + UiIcons.check, size: 32, - color: Colors.white, + color: UiColors.white, ), ), - const SizedBox(height: 16), - const Text( + const SizedBox(height: UiConstants.space4), + Text( 'You\'ve Arrived! šŸŽ‰', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.headline3m.textPrimary, ), - const SizedBox(height: 8), - const Text( + const SizedBox(height: UiConstants.space2), + Text( 'You\'re at the shift location. Ready to clock in?', - style: TextStyle( - fontSize: 14, - color: Color(0xFF475569), // slate-600 - ), + style: UiTypography.body2r.textSecondary, textAlign: TextAlign.center, ), ], diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/date_selector.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/date_selector.dart index 3b732041..2d849477 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/date_selector.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/date_selector.dart @@ -1,8 +1,8 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class DateSelector extends StatelessWidget { - const DateSelector({ super.key, required this.selectedDate, @@ -34,14 +34,16 @@ class DateSelector extends StatelessWidget { onTap: () => onSelect(date), child: AnimatedContainer( duration: const Duration(milliseconds: 200), - margin: const EdgeInsets.symmetric(horizontal: 4), + margin: const EdgeInsets.symmetric( + horizontal: UiConstants.space1, + ), decoration: BoxDecoration( - color: isSelected ? const Color(0xFF0032A0) : Colors.white, - borderRadius: BorderRadius.circular(16), + color: isSelected ? UiColors.primary : UiColors.white, + borderRadius: UiConstants.radiusLg, boxShadow: isSelected ? [ BoxShadow( - color: const Color(0xFF0032A0).withOpacity(0.3), + color: UiColors.primary.withValues(alpha: 0.3), blurRadius: 10, offset: const Offset(0, 4), ), @@ -53,33 +55,28 @@ class DateSelector extends StatelessWidget { children: [ Text( DateFormat('d').format(date), - style: TextStyle( - fontSize: 18, + style: UiTypography.title1m.copyWith( fontWeight: FontWeight.bold, - color: isSelected - ? Colors.white - : const Color(0xFF0F172A), + color: + isSelected ? UiColors.white : UiColors.foreground, ), ), const SizedBox(height: 2), Text( DateFormat('E').format(date), - style: TextStyle( - fontSize: 12, + style: UiTypography.footnote2r.copyWith( color: isSelected - ? Colors.white.withOpacity(0.8) - : const Color(0xFF94A3B8), + ? UiColors.white.withValues(alpha: 0.8) + : UiColors.textInactive, ), ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), if (hasShift) Container( width: 6, height: 6, decoration: BoxDecoration( - color: isSelected - ? Colors.white - : const Color(0xFF0032A0), + color: isSelected ? UiColors.white : UiColors.primary, shape: BoxShape.circle, ), ) @@ -87,8 +84,8 @@ class DateSelector extends StatelessWidget { Container( width: 6, height: 6, - decoration: BoxDecoration( - color: Colors.grey.shade300, + decoration: const BoxDecoration( + color: UiColors.border, shape: BoxShape.circle, ), ) diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/location_map_placeholder.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/location_map_placeholder.dart index 9f3d594d..803d9f7a 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/location_map_placeholder.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/location_map_placeholder.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:design_system/design_system.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:flutter/material.dart'; class LocationMapPlaceholder extends StatelessWidget { @@ -18,8 +17,8 @@ class LocationMapPlaceholder extends StatelessWidget { height: 200, width: double.infinity, decoration: BoxDecoration( - color: const Color(0xFFE2E8F0), - borderRadius: BorderRadius.circular(16), + color: UiColors.border, + borderRadius: UiConstants.radiusLg, image: DecorationImage( image: const NetworkImage( 'https://maps.googleapis.com/maps/api/staticmap?center=40.7128,-74.0060&zoom=15&size=600x300&maptype=roadmap&markers=color:red%7C40.7128,-74.0060&key=YOUR_API_KEY', @@ -33,30 +32,37 @@ class LocationMapPlaceholder extends StatelessWidget { child: Stack( children: [ // Fallback UI if image fails (which it will without key) - const Center( + Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(LucideIcons.mapPin, size: 48, color: UiColors.iconSecondary), - SizedBox(height: 8), - Text('Map View (GPS)', style: TextStyle(color: UiColors.textSecondary)), + const Icon( + UiIcons.mapPin, + size: 48, + color: UiColors.iconSecondary, + ), + const SizedBox(height: UiConstants.space2), + Text('Map View (GPS)', style: UiTypography.body2r.textSecondary), ], ), ), - + // Status Overlay Positioned( - bottom: 16, - left: 16, - right: 16, + bottom: UiConstants.space4, + left: UiConstants.space4, + right: UiConstants.space4, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space3, + ), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: UiColors.white, + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: UiColors.black.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 4), ), @@ -65,23 +71,25 @@ class LocationMapPlaceholder extends StatelessWidget { child: Row( children: [ Icon( - isVerified ? LucideIcons.checkCircle : LucideIcons.alertCircle, - color: isVerified ? UiColors.textSuccess : UiColors.destructive, + isVerified ? UiIcons.checkCircle : UiIcons.warning, + color: isVerified + ? UiColors.textSuccess + : UiColors.destructive, size: 20, ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isVerified ? 'Location Verified' : 'Location Check', - style: UiTypography.body1b.copyWith(color: UiColors.textPrimary), + style: UiTypography.body1b.textPrimary, ), if (distance != null) Text( '${distance!.toStringAsFixed(0)}m from venue', - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), ], ), diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/lunch_break_modal.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/lunch_break_modal.dart index f095f6a4..83f3d58a 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/lunch_break_modal.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/lunch_break_modal.dart @@ -1,8 +1,7 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; class LunchBreakDialog extends StatefulWidget { - const LunchBreakDialog({super.key, required this.onComplete}); final VoidCallback onComplete; @@ -47,8 +46,10 @@ class _LunchBreakDialogState extends State { @override Widget build(BuildContext context) { return Dialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + backgroundColor: UiColors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.space6), + ), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: _buildCurrentStep(), @@ -75,34 +76,30 @@ class _LunchBreakDialogState extends State { Widget _buildStep1() { return Padding( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(UiConstants.space6), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 80, height: 80, - decoration: BoxDecoration( - color: Colors.grey.shade100, + decoration: const BoxDecoration( + color: UiColors.bgSecondary, shape: BoxShape.circle, ), child: const Icon( - LucideIcons.coffee, + UiIcons.coffee, size: 40, - color: Color(0xFF6A7382), + color: UiColors.iconSecondary, ), ), - const SizedBox(height: 24), - const Text( + const SizedBox(height: UiConstants.space6), + Text( "Did You Take\na Lunch?", textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Color(0xFF121826), - ), + style: UiTypography.headline1m.textPrimary, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Row( children: [ Expanded( @@ -114,25 +111,23 @@ class _LunchBreakDialogState extends State { }); }, child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(12), - color: Colors.transparent, + border: Border.all(color: UiColors.border), + borderRadius: UiConstants.radiusLg, + color: UiColors.transparent, ), alignment: Alignment.center, - child: const Text( + child: Text( "No", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xFF121826), - ), + style: UiTypography.body1m.textPrimary, ), ), ), ), - const SizedBox(width: 16), + const SizedBox(width: UiConstants.space4), Expanded( child: ElevatedButton( onPressed: () { @@ -142,19 +137,17 @@ class _LunchBreakDialogState extends State { }); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0032A0), - padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: UiColors.primary, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), ), - child: const Text( + child: Text( "Yes", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), + style: UiTypography.body1m.white, ), ), ), @@ -168,155 +161,183 @@ class _LunchBreakDialogState extends State { Widget _buildStep2() { // Time input return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - "When did you take lunch?", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 24), - // Mock Inputs - Row( - children: [ - Expanded( - child: DropdownButtonFormField( - isExpanded: true, - initialValue: _breakStart, - items: _timeOptions.map((String t) => DropdownMenuItem(value: t, child: Text(t, style: const TextStyle(fontSize: 13)))).toList(), - onChanged: (String? v) => setState(() => _breakStart = v), - decoration: const InputDecoration( - labelText: 'Start', - contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), - ), - ), - ), - const SizedBox(width: 10), - Expanded( - child: DropdownButtonFormField( - isExpanded: true, - initialValue: _breakEnd, - items: _timeOptions.map((String t) => DropdownMenuItem(value: t, child: Text(t, style: const TextStyle(fontSize: 13)))).toList(), - onChanged: (String? v) => setState(() => _breakEnd = v), - decoration: const InputDecoration( - labelText: 'End', - contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), - ), - ), - ), - ], - ), - - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - setState(() => _step = 3); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0032A0), - minimumSize: const Size(double.infinity, 48), + padding: const EdgeInsets.all(UiConstants.space6), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "When did you take lunch?", + style: UiTypography.headline4m, + ), + const SizedBox(height: UiConstants.space6), + // Mock Inputs + Row( + children: [ + Expanded( + child: DropdownButtonFormField( + isExpanded: true, + value: _breakStart, + items: _timeOptions + .map( + (String t) => DropdownMenuItem( + value: t, + child: Text(t, style: UiTypography.body3r), + ), + ) + .toList(), + onChanged: (String? v) => setState(() => _breakStart = v), + decoration: const InputDecoration( + labelText: 'Start', + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + ), ), - child: const Text("Next", style: TextStyle(color: Colors.white)), + ), + const SizedBox(width: 10), + Expanded( + child: DropdownButtonFormField( + isExpanded: true, + value: _breakEnd, + items: _timeOptions + .map( + (String t) => DropdownMenuItem( + value: t, + child: Text(t, style: UiTypography.body3r), + ), + ) + .toList(), + onChanged: (String? v) => setState(() => _breakEnd = v), + decoration: const InputDecoration( + labelText: 'End', + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + ), + ), + ), + ], + ), + + const SizedBox(height: UiConstants.space6), + ElevatedButton( + onPressed: () { + setState(() => _step = 3); + }, + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + minimumSize: const Size(double.infinity, 48), ), - ], - )); + child: Text("Next", style: UiTypography.body1m.white), + ), + ], + ), + ); } - Widget _buildStep2b() { + Widget _buildStep2b() { // No lunch reason return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Text( - "Why didn't you take lunch?", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + padding: const EdgeInsets.all(UiConstants.space6), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Why didn't you take lunch?", + style: UiTypography.headline4m, + ), + const SizedBox(height: UiConstants.space4), + ..._noLunchReasons.map( + (String reason) => RadioListTile( + title: Text(reason, style: UiTypography.body2r), + value: reason, + groupValue: _noLunchReason, + onChanged: (String? val) => setState(() => _noLunchReason = val), + activeColor: UiColors.primary, ), - const SizedBox(height: 16), - ..._noLunchReasons.map((String reason) => RadioListTile( - title: Text(reason), - value: reason, - groupValue: _noLunchReason, - onChanged: (String? val) => setState(() => _noLunchReason = val), - )), + ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - setState(() => _step = 3); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0032A0), - minimumSize: const Size(double.infinity, 48), - ), - child: const Text("Next", style: TextStyle(color: Colors.white)), + const SizedBox(height: UiConstants.space6), + ElevatedButton( + onPressed: () { + setState(() => _step = 3); + }, + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + minimumSize: const Size(double.infinity, 48), ), - ], - )); + child: Text("Next", style: UiTypography.body1m.white), + ), + ], + ), + ); } Widget _buildStep3() { // Additional Notes - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - "Additional Notes", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + return Padding( + padding: const EdgeInsets.all(UiConstants.space6), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Additional Notes", + style: UiTypography.headline4m, + ), + const SizedBox(height: UiConstants.space4), + TextField( + onChanged: (String v) => _additionalNotes = v, + style: UiTypography.body2r, + decoration: const InputDecoration( + hintText: 'Add any details...', + border: OutlineInputBorder(), ), - const SizedBox(height: 16), - TextField( - onChanged: (String v) => _additionalNotes = v, - decoration: const InputDecoration( - hintText: 'Add any details...', - border: OutlineInputBorder(), - ), - maxLines: 3, - ), + maxLines: 3, + ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - setState(() => _step = 4); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0032A0), - minimumSize: const Size(double.infinity, 48), - ), - child: const Text("Submit", style: TextStyle(color: Colors.white)), + const SizedBox(height: UiConstants.space6), + ElevatedButton( + onPressed: () { + setState(() => _step = 4); + }, + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + minimumSize: const Size(double.infinity, 48), ), - ], - )); + child: Text("Submit", style: UiTypography.body1m.white), + ), + ], + ), + ); } - + Widget _buildStep4() { // Success - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(LucideIcons.checkCircle, size: 64, color: Colors.green), - const SizedBox(height: 24), - const Text( - "Break Logged!", - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + return Padding( + padding: const EdgeInsets.all(UiConstants.space6), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(UiIcons.checkCircle, size: 64, color: UiColors.success), + const SizedBox(height: UiConstants.space6), + Text( + "Break Logged!", + style: UiTypography.headline1m, + ), + const SizedBox(height: UiConstants.space6), + ElevatedButton( + onPressed: widget.onComplete, + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + minimumSize: const Size(double.infinity, 48), ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: widget.onComplete, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0032A0), - minimumSize: const Size(double.infinity, 48), - ), - child: const Text("Close", style: TextStyle(color: Colors.white)), - ), - ], - )); + child: Text("Close", style: UiTypography.body1m.white), + ), + ], + ), + ); } } diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart index 10315dd8..bb79e4d3 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart @@ -1,8 +1,7 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; class SwipeToCheckIn extends StatefulWidget { - const SwipeToCheckIn({ super.key, this.onCheckIn, @@ -73,8 +72,8 @@ class _SwipeToCheckInState extends State @override Widget build(BuildContext context) { final Color baseColor = widget.isCheckedIn - ? const Color(0xFF10B981) - : const Color(0xFF0032A0); + ? UiColors.success + : UiColors.primary; if (widget.mode == 'nfc') { return GestureDetector( @@ -93,10 +92,10 @@ class _SwipeToCheckInState extends State height: 56, decoration: BoxDecoration( color: baseColor, - borderRadius: BorderRadius.circular(16), + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: baseColor.withOpacity(0.4), + color: baseColor.withValues(alpha: 0.4), blurRadius: 25, offset: const Offset(0, 10), spreadRadius: -5, @@ -106,19 +105,15 @@ class _SwipeToCheckInState extends State child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(LucideIcons.wifi, color: Colors.white), - const SizedBox(width: 12), + const Icon(UiIcons.wifi, color: UiColors.white), + const SizedBox(width: UiConstants.space3), Text( widget.isLoading ? (widget.isCheckedIn ? "Checking out..." : "Checking in...") : (widget.isCheckedIn ? "NFC Check Out" : "NFC Check In"), - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 18, - ), + style: UiTypography.body1b.white, ), ], ), @@ -134,11 +129,11 @@ class _SwipeToCheckInState extends State // Calculate background color based on drag final double progress = _dragValue / maxDrag; final Color startColor = widget.isCheckedIn - ? const Color(0xFF10B981) - : const Color(0xFF0032A0); + ? UiColors.success + : UiColors.primary; final Color endColor = widget.isCheckedIn - ? const Color(0xFF0032A0) - : const Color(0xFF10B981); + ? UiColors.primary + : UiColors.success; final Color currentColor = Color.lerp(startColor, endColor, progress) ?? startColor; @@ -146,10 +141,10 @@ class _SwipeToCheckInState extends State height: 56, decoration: BoxDecoration( color: currentColor, - borderRadius: BorderRadius.circular(16), + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: UiColors.black.withValues(alpha: 0.1), blurRadius: 4, offset: const Offset(0, 2), ), @@ -164,11 +159,7 @@ class _SwipeToCheckInState extends State widget.isCheckedIn ? "Swipe to Check Out" : "Swipe to Check In", - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontWeight: FontWeight.w600, - fontSize: 18, - ), + style: UiTypography.body1b, ), ), ), @@ -176,28 +167,26 @@ class _SwipeToCheckInState extends State Center( child: Text( widget.isCheckedIn ? "Check Out!" : "Check In!", - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w600, - fontSize: 18, - ), + style: UiTypography.body1b, ), ), Positioned( left: 4 + _dragValue, top: 4, child: GestureDetector( - onHorizontalDragUpdate: (DragUpdateDetails d) => _onDragUpdate(d, maxWidth), - onHorizontalDragEnd: (DragEndDetails d) => _onDragEnd(d, maxWidth), + onHorizontalDragUpdate: (DragUpdateDetails d) => + _onDragUpdate(d, maxWidth), + onHorizontalDragEnd: (DragEndDetails d) => + _onDragEnd(d, maxWidth), child: Container( width: _handleSize, height: _handleSize, decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: UiColors.white, + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: UiColors.black.withValues(alpha: 0.1), blurRadius: 2, offset: const Offset(0, 1), ), @@ -205,9 +194,7 @@ class _SwipeToCheckInState extends State ), child: Center( child: Icon( - _isComplete - ? LucideIcons.check - : LucideIcons.arrowRight, + _isComplete ? UiIcons.check : UiIcons.arrowRight, color: startColor, ), ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart b/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart index 74f03d5c..8783e938 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/data/repositories/home_repository_impl.dart @@ -46,10 +46,8 @@ class HomeRepositoryImpl .dayEnd(_toTimestamp(end)) .execute()); - // Filter for ACCEPTED applications (same logic as shifts_repository_impl) + // Filter for CONFIRMED applications (same logic as shifts_repository_impl) final apps = response.data.applications.where((app) => - (app.status is Known && - (app.status as Known).value == ApplicationStatus.ACCEPTED) || (app.status is Known && (app.status as Known).value == ApplicationStatus.CONFIRMED)); @@ -145,4 +143,3 @@ class HomeRepositoryImpl ); } } - diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart index 4919bd11..7de014d6 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart @@ -3,9 +3,9 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:lucide_icons/lucide_icons.dart'; -import 'package:staff_home/src/presentation/blocs/home_cubit.dart'; import 'package:krow_core/core.dart'; + +import 'package:staff_home/src/presentation/blocs/home_cubit.dart'; import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart'; import 'package:staff_home/src/presentation/widgets/home_page/home_header.dart'; import 'package:staff_home/src/presentation/widgets/home_page/placeholder_banner.dart'; @@ -84,28 +84,28 @@ class WorkerHomePage extends StatelessWidget { children: [ Expanded( child: QuickActionItem( - icon: LucideIcons.search, + icon: UiIcons.search, label: quickI18n.find_shifts, onTap: () => Modular.to.toShifts(), ), ), Expanded( child: QuickActionItem( - icon: LucideIcons.calendar, + icon: UiIcons.calendar, label: quickI18n.availability, onTap: () => Modular.to.toAvailability(), ), ), Expanded( child: QuickActionItem( - icon: LucideIcons.dollarSign, + icon: UiIcons.dollar, label: quickI18n.earnings, onTap: () => Modular.to.toPayments(), ), ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Today's Shifts BlocBuilder( @@ -124,9 +124,11 @@ class WorkerHomePage extends StatelessWidget { if (state.status == HomeStatus.loading) const Center( child: SizedBox( - height: 40, - width: 40, - child: CircularProgressIndicator(), + height: UiConstants.space10, + width: UiConstants.space10, + child: CircularProgressIndicator( + color: UiColors.primary, + ), ), ) else if (shifts.isEmpty) @@ -150,7 +152,7 @@ class WorkerHomePage extends StatelessWidget { ); }, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Tomorrow's Shifts BlocBuilder( @@ -178,7 +180,7 @@ class WorkerHomePage extends StatelessWidget { ); }, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Recommended Shifts SectionHeader( @@ -198,7 +200,8 @@ class WorkerHomePage extends StatelessWidget { itemCount: state.recommendedShifts.length, clipBehavior: Clip.none, itemBuilder: (context, index) => Padding( - padding: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.only( + right: UiConstants.space3), child: RecommendedShiftCard( shift: state.recommendedShifts[index], ), @@ -207,7 +210,7 @@ class WorkerHomePage extends StatelessWidget { ); }, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), ], ), ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart index 17127ce5..fd8d9da8 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart @@ -28,23 +28,20 @@ class HomeHeader extends StatelessWidget { spacing: UiConstants.space3, children: [ Container( - width: 48, - height: 48, + width: UiConstants.space12, + height: UiConstants.space12, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: UiColors.primary.withOpacity(0.2), + color: UiColors.primary.withValues(alpha: 0.2), width: 2, ), ), child: CircleAvatar( - backgroundColor: UiColors.primary.withOpacity(0.1), + backgroundColor: UiColors.primary.withValues(alpha: 0.1), child: Text( initial, - style: const TextStyle( - color: UiColors.primary, - fontWeight: FontWeight.bold, - ), + style: UiTypography.body1b.textPrimary, ), ), ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart index 261c7d65..4476aecc 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'package:design_system/design_system.dart'; import 'package:core_localization/core_localization.dart'; @@ -42,7 +42,7 @@ class PendingPaymentCard extends StatelessWidget { shape: BoxShape.circle, ), child: const Icon( - LucideIcons.dollarSign, + UiIcons.dollar, color: UiColors.primary, size: 20, ), @@ -76,7 +76,7 @@ class PendingPaymentCard extends StatelessWidget { ), SizedBox(width: UiConstants.space2), Icon( - LucideIcons.chevronRight, + UiIcons.chevronRight, color: UiColors.mutedForeground, size: 20, ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart index 517eba67..1d648bc4 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'package:design_system/design_system.dart'; @@ -29,19 +29,19 @@ class PlaceholderBanner extends StatelessWidget { decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(UiConstants.radiusBase), - border: Border.all(color: accent.withOpacity(0.3)), + border: Border.all(color: accent.withValues(alpha: 0.3)), ), child: Row( children: [ Container( - width: 40, - height: 40, + width: UiConstants.space10, + height: UiConstants.space10, padding: const EdgeInsets.all(UiConstants.space2), decoration: const BoxDecoration( color: UiColors.bgBanner, shape: BoxShape.circle, ), - child: Icon(LucideIcons.sparkles, color: accent, size: 20), + child: Icon(UiIcons.sparkles, color: accent, size: UiConstants.space5), ), const SizedBox(width: UiConstants.space3), Expanded( @@ -59,7 +59,7 @@ class PlaceholderBanner extends StatelessWidget { ], ), ), - Icon(LucideIcons.chevronRight, color: accent), + Icon(UiIcons.chevronRight, color: accent), ], ), ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart index 02271b5b..f89dd510 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart @@ -22,8 +22,8 @@ class QuickActionItem extends StatelessWidget { child: Column( children: [ Container( - width: 64, - height: 64, + width: UiConstants.space16, + height: UiConstants.space16, padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgBanner, @@ -31,13 +31,13 @@ class QuickActionItem extends StatelessWidget { border: Border.all(color: UiColors.bgSecondary), boxShadow: [ BoxShadow( - color: UiColors.foreground.withOpacity(0.05), + color: UiColors.foreground.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), - child: Icon(icon, color: UiColors.primary, size: 24), + child: Icon(icon, color: UiColors.primary, size: UiConstants.space6), ), const SizedBox(height: UiConstants.space2), Text( diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart index ede4069d..e5ead2d2 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart @@ -3,7 +3,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'package:krow_core/core.dart'; class RecommendedShiftCard extends StatelessWidget { @@ -14,8 +14,6 @@ class RecommendedShiftCard extends StatelessWidget { @override Widget build(BuildContext context) { final recI18n = t.staff.home.recommended_card; - final duration = 8; - final totalPay = duration * shift.hourlyRate; return GestureDetector( onTap: () { @@ -23,14 +21,14 @@ class RecommendedShiftCard extends StatelessWidget { }, child: Container( width: 300, - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.02), + color: UiColors.black.withValues(alpha: 0.02), blurRadius: 4, offset: const Offset(0, 2), ), @@ -45,51 +43,43 @@ class RecommendedShiftCard extends StatelessWidget { children: [ Text( recI18n.act_now, - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Color(0xFFDC2626), - ), + style: UiTypography.body3m.copyWith(color: UiColors.textError), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Container( padding: const EdgeInsets.symmetric( - horizontal: 8, + horizontal: UiConstants.space2, vertical: 2, ), decoration: BoxDecoration( - color: const Color(0xFFE8F0FF), - borderRadius: BorderRadius.circular(999), + color: UiColors.tagInProgress, + borderRadius: UiConstants.radiusFull, ), child: Text( recI18n.one_day, - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - color: Color(0xFF0047FF), - ), + style: UiTypography.body3m.textPrimary, ), ), ], ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - width: 44, - height: 44, + width: UiConstants.space10, + height: UiConstants.space10, decoration: BoxDecoration( - color: const Color(0xFFE8F0FF), - borderRadius: BorderRadius.circular(12), + color: UiColors.tagInProgress, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: const Icon( - LucideIcons.calendar, - color: Color(0xFF0047FF), - size: 20, + UiIcons.calendar, + color: UiColors.primary, + size: UiConstants.space5, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -97,25 +87,16 @@ class RecommendedShiftCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( + Flexible( child: Text( shift.title, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: UiColors.foreground, - ), - maxLines: 1, + style: UiTypography.body1m.textPrimary, overflow: TextOverflow.ellipsis, ), ), Text( - '\$${totalPay.round()}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: UiColors.foreground, - ), + '\$${shift.hourlyRate}/h', + style: UiTypography.headline4m.textPrimary, ), ], ), @@ -125,17 +106,11 @@ class RecommendedShiftCard extends StatelessWidget { children: [ Text( shift.clientName, - style: const TextStyle( - fontSize: 12, - color: UiColors.mutedForeground, - ), + style: UiTypography.body3r.textSecondary, ), Text( - '\$${shift.hourlyRate.toStringAsFixed(0)}/hr • ${duration}h', - style: const TextStyle( - fontSize: 10, - color: UiColors.mutedForeground, - ), + '\$${shift.hourlyRate.toStringAsFixed(0)}/hr', + style: UiTypography.body3r.textSecondary, ), ], ), @@ -144,57 +119,48 @@ class RecommendedShiftCard extends StatelessWidget { ), ], ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( children: [ const Icon( - LucideIcons.calendar, - size: 14, + UiIcons.calendar, + size: UiConstants.space3, color: UiColors.mutedForeground, ), - const SizedBox(width: 4), + const SizedBox(width: UiConstants.space1), Text( recI18n.today, - style: const TextStyle( - fontSize: 12, - color: UiColors.mutedForeground, - ), + style: UiTypography.body3r.textSecondary, ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), const Icon( - LucideIcons.clock, - size: 14, + UiIcons.clock, + size: UiConstants.space3, color: UiColors.mutedForeground, ), - const SizedBox(width: 4), + const SizedBox(width: UiConstants.space1), Text( recI18n.time_range( start: shift.startTime, end: shift.endTime, ), - style: const TextStyle( - fontSize: 12, - color: UiColors.mutedForeground, - ), + style: UiTypography.body3r.textSecondary, ), ], ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Row( children: [ const Icon( - LucideIcons.mapPin, - size: 14, + UiIcons.mapPin, + size: UiConstants.space3, color: UiColors.mutedForeground, ), - const SizedBox(width: 4), + const SizedBox(width: UiConstants.space1), Expanded( child: Text( shift.locationAddress, - style: const TextStyle( - fontSize: 12, - color: UiColors.mutedForeground, - ), + style: UiTypography.body3r.textSecondary, maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart index 59754ec6..e38da6e4 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'package:design_system/design_system.dart'; - /// Section header widget for home page sections, using design system tokens. class SectionHeader extends StatelessWidget { /// Section title @@ -23,43 +22,58 @@ class SectionHeader extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - title, - style: UiTypography.headline4m, - ), - if (action != null) - if (onAction != null) - GestureDetector( - onTap: onAction, - child: Row( - children: [ - Text( - action!, - style: UiTypography.body2m.copyWith(color: UiColors.primary), - ), - const Icon( - LucideIcons.chevronRight, - size: 16, - color: UiColors.primary, - ), - ], - ), - ) - else - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.08), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: UiColors.primary.withOpacity(0.2), + Expanded( + child: action != null + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: UiTypography.body2m.textPrimary, + ), + if (onAction != null) + GestureDetector( + onTap: onAction, + child: Row( + children: [ + Text( + action ?? '', + style: UiTypography.body3r.textPrimary, + ), + const Icon( + UiIcons.chevronRight, + size: UiConstants.space4, + color: UiColors.primary, + ), + ], + ), + ) + else + Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: UiColors.primary.withValues(alpha: 0.08), + borderRadius: + BorderRadius.circular(UiConstants.radiusBase), + border: Border.all( + color: UiColors.primary.withValues(alpha: 0.2), + ), + ), + child: Text( + action!, + style: UiTypography.body3r.textPrimary, + ), + ), + ], + ) + : Text( + title, + style: UiTypography.body2m.textPrimary, ), - ), - child: Text( - action!, - style: UiTypography.body3r.copyWith(color: UiColors.primary), - ), - ), + ), ], ), ); diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/shift_card.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/shift_card.dart index 046afcfe..13a39fb7 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/shift_card.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/shift_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'package:intl/intl.dart'; import 'package:design_system/design_system.dart'; @@ -77,15 +77,15 @@ class _ShiftCardState extends State { Modular.to.pushShiftDetails(widget.shift); }, child: Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(bottom: UiConstants.space3), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -94,27 +94,28 @@ class _ShiftCardState extends State { child: Row( children: [ Container( - width: 48, - height: 48, + width: UiConstants.space12, + height: UiConstants.space12, decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: UiColors.border), ), child: widget.shift.logoUrl != null ? ClipRRect( - borderRadius: BorderRadius.circular(12), + borderRadius: + BorderRadius.circular(UiConstants.radiusBase), child: Image.network( widget.shift.logoUrl!, fit: BoxFit.contain, ), ) - : const Icon( - LucideIcons.building2, + : Icon( + UiIcons.building, color: UiColors.mutedForeground, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -125,28 +126,18 @@ class _ShiftCardState extends State { Flexible( child: Text( widget.shift.title, - style: const TextStyle( - fontWeight: FontWeight.w600, - color: UiColors.foreground, - ), + style: UiTypography.body1m.textPrimary, overflow: TextOverflow.ellipsis, ), ), Text.rich( TextSpan( text: '\$${widget.shift.hourlyRate}', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: UiColors.foreground, - ), - children: const [ + style: UiTypography.body1b.textPrimary, + children: [ TextSpan( text: '/h', - style: TextStyle( - fontWeight: FontWeight.normal, - fontSize: 12, - ), + style: UiTypography.body3r, ), ], ), @@ -155,19 +146,13 @@ class _ShiftCardState extends State { ), Text( widget.shift.clientName, - style: const TextStyle( - color: UiColors.mutedForeground, - fontSize: 13, - ), + style: UiTypography.body2r.textSecondary, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( '${_formatTime(widget.shift.startTime)} • ${widget.shift.location}', - style: const TextStyle( - color: UiColors.mutedForeground, - fontSize: 12, - ), + style: UiTypography.body3r.textSecondary, ), ], ), @@ -179,14 +164,14 @@ class _ShiftCardState extends State { } return Container( - margin: const EdgeInsets.only(bottom: 16), + margin: const EdgeInsets.only(bottom: UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), @@ -195,7 +180,7 @@ class _ShiftCardState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), child: Column( children: [ // Header @@ -203,48 +188,45 @@ class _ShiftCardState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( - width: 56, - height: 56, + width: UiConstants.space14, + height: UiConstants.space14, decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: UiColors.border), ), child: widget.shift.logoUrl != null ? ClipRRect( - borderRadius: BorderRadius.circular(12), + borderRadius: + BorderRadius.circular(UiConstants.radiusBase), child: Image.network( widget.shift.logoUrl!, fit: BoxFit.contain, ), ) - : const Icon( - LucideIcons.building2, + : Icon( + UiIcons.building, size: 28, color: UiColors.primary, ), ), Container( padding: const EdgeInsets.symmetric( - horizontal: 16, + horizontal: UiConstants.space4, vertical: 6, ), decoration: BoxDecoration( color: UiColors.primary, - borderRadius: BorderRadius.circular(20), + borderRadius: UiConstants.radiusFull, ), child: Text( 'Assigned ${_getTimeAgo(widget.shift.createdDate).replaceAll('Pending ', '')}', - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w600, - ), + style: UiTypography.body3m.white, ), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Title and Rate Row( @@ -257,18 +239,11 @@ class _ShiftCardState extends State { children: [ Text( widget.shift.title, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: UiColors.foreground, - ), + style: UiTypography.headline3m.textPrimary, ), Text( widget.shift.clientName, - style: const TextStyle( - color: UiColors.mutedForeground, - fontSize: 14, - ), + style: UiTypography.body2r.textSecondary, ), ], ), @@ -276,31 +251,24 @@ class _ShiftCardState extends State { Text.rich( TextSpan( text: '\$${widget.shift.hourlyRate}', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - color: UiColors.foreground, - ), - children: const [ + style: UiTypography.headline3m.textPrimary, + children: [ TextSpan( text: '/h', - style: TextStyle( - fontWeight: FontWeight.normal, - fontSize: 16, - ), + style: UiTypography.body1r, ), ], ), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Location and Date Row( children: [ - const Icon( - LucideIcons.mapPin, + Icon( + UiIcons.mapPin, size: 16, color: UiColors.mutedForeground, ), @@ -308,30 +276,24 @@ class _ShiftCardState extends State { Expanded( child: Text( widget.shift.location, - style: const TextStyle( - color: UiColors.mutedForeground, - fontSize: 14, - ), + style: UiTypography.body2r.textSecondary, overflow: TextOverflow.ellipsis, ), ), - const SizedBox(width: 16), - const Icon( - LucideIcons.calendar, + const SizedBox(width: UiConstants.space4), + Icon( + UiIcons.calendar, size: 16, color: UiColors.mutedForeground, ), const SizedBox(width: 6), Text( '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)}', - style: const TextStyle( - color: UiColors.mutedForeground, - fontSize: 14, - ), + style: UiTypography.body2r.textSecondary, ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Tags Wrap( @@ -339,21 +301,21 @@ class _ShiftCardState extends State { runSpacing: 8, children: [ _buildTag( - LucideIcons.zap, + UiIcons.zap, 'Immediate start', UiColors.accent.withValues(alpha: 0.3), UiColors.foreground, ), _buildTag( - LucideIcons.timer, + UiIcons.timer, 'No experience', - const Color(0xFFFEE2E2), - const Color(0xFFDC2626), + UiColors.tagError, + UiColors.textError, ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), ], ), ), @@ -361,46 +323,48 @@ class _ShiftCardState extends State { // Actions if (!widget.compact) Padding( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + ), child: Column( children: [ SizedBox( width: double.infinity, - height: 48, + height: UiConstants.space12, child: ElevatedButton( onPressed: widget.onApply, style: ElevatedButton.styleFrom( - backgroundColor: UiColors.foreground, - foregroundColor: Colors.white, + backgroundColor: UiColors.primary, + foregroundColor: UiColors.white, + elevation: 0, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: + BorderRadius.circular(UiConstants.radiusBase), ), ), - child: const Text( - 'Accept shift', - style: TextStyle(fontWeight: FontWeight.w600), - ), + child: const Text('Accept shift'), ), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), SizedBox( width: double.infinity, - height: 48, + height: UiConstants.space12, child: OutlinedButton( onPressed: widget.onDecline, style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFFEF4444), - side: const BorderSide(color: Color(0xFFFCA5A5)), + foregroundColor: UiColors.destructive, + side: BorderSide( + color: UiColors.destructive.withValues(alpha: 0.3), + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: + BorderRadius.circular(UiConstants.radiusBase), ), ), - child: const Text( - 'Decline shift', - style: TextStyle(fontWeight: FontWeight.w600), - ), + child: const Text('Decline shift'), ), ), + const SizedBox(height: UiConstants.space5), ], ), ), @@ -414,7 +378,7 @@ class _ShiftCardState extends State { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: bg, - borderRadius: BorderRadius.circular(20), + borderRadius: UiConstants.radiusFull, ), child: Row( mainAxisSize: MainAxisSize.min, @@ -424,11 +388,7 @@ class _ShiftCardState extends State { Flexible( child: Text( label, - style: TextStyle( - color: text, - fontSize: 12, - fontWeight: FontWeight.w600, - ), + style: UiTypography.body3m.copyWith(color: text), overflow: TextOverflow.ellipsis, ), ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/auto_match_toggle.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/auto_match_toggle.dart index 0813dbfe..c6b35db9 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/auto_match_toggle.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/auto_match_toggle.dart @@ -1,5 +1,6 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'package:core_localization/core_localization.dart'; @@ -17,29 +18,33 @@ class AutoMatchToggle extends StatefulWidget { State createState() => _AutoMatchToggleState(); } -class _AutoMatchToggleState extends State with SingleTickerProviderStateMixin { +class _AutoMatchToggleState extends State + with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { final i18n = t.staff.home.auto_match; - final Color primary = Theme.of(context).colorScheme.primary; return AnimatedContainer( duration: const Duration(milliseconds: 300), - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), gradient: widget.enabled ? LinearGradient( - colors: [primary, primary.withOpacity(0.8)], + colors: [ + UiColors.primary, + UiColors.primary.withValues(alpha: 0.8), + ], begin: Alignment.centerLeft, end: Alignment.centerRight, ) : null, - color: widget.enabled ? null : Colors.white, - border: widget.enabled ? null : Border.all(color: Colors.grey.shade200), + color: widget.enabled ? null : UiColors.white, + border: + widget.enabled ? null : Border.all(color: UiColors.border), boxShadow: widget.enabled ? [ BoxShadow( - color: primary.withOpacity(0.3), + color: UiColors.primary.withValues(alpha: 0.3), blurRadius: 10, offset: const Offset(0, 4), ), @@ -54,36 +59,39 @@ class _AutoMatchToggleState extends State with SingleTickerProv Row( children: [ Container( - width: 40, - height: 40, + width: UiConstants.space10, + height: UiConstants.space10, decoration: BoxDecoration( color: widget.enabled - ? Colors.white.withOpacity(0.2) - : primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), + ? UiColors.white.withValues(alpha: 0.2) + : UiColors.primary.withValues(alpha: 0.1), + borderRadius: + BorderRadius.circular(UiConstants.radiusBase), ), child: Icon( - LucideIcons.zap, - color: widget.enabled ? Colors.white : primary, + UiIcons.zap, + color: widget.enabled ? UiColors.white : UiColors.primary, size: 20, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( i18n.title, - style: TextStyle( - fontWeight: FontWeight.bold, - color: widget.enabled ? Colors.white : const Color(0xFF0F172A), + style: UiTypography.body1b.copyWith( + color: widget.enabled + ? UiColors.white + : UiColors.textPrimary, ), ), Text( widget.enabled ? i18n.finding_shifts : i18n.get_matched, - style: TextStyle( - fontSize: 12, - color: widget.enabled ? const Color(0xFFF8E08E) : Colors.grey.shade500, + style: UiTypography.body3r.copyWith( + color: widget.enabled + ? UiColors.accent + : UiColors.textInactive, ), ), ], @@ -93,10 +101,10 @@ class _AutoMatchToggleState extends State with SingleTickerProv Switch( value: widget.enabled, onChanged: widget.onToggle, - activeThumbColor: Colors.white, - activeTrackColor: Colors.white.withValues(alpha: 0.3), - inactiveThumbColor: Colors.white, - inactiveTrackColor: Colors.grey.shade300, + activeThumbColor: UiColors.white, + activeTrackColor: UiColors.white.withValues(alpha: 0.3), + inactiveThumbColor: UiColors.white, + inactiveTrackColor: UiColors.border, ), ], ), @@ -106,29 +114,28 @@ class _AutoMatchToggleState extends State with SingleTickerProv ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Container( height: 1, - color: Colors.white.withValues(alpha: 0.2), + color: UiColors.white.withValues(alpha: 0.2), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Text( i18n.matching_based_on, - style: const TextStyle( - color: Color(0xFFF8E08E), - fontSize: 12, + style: UiTypography.body3r.copyWith( + color: UiColors.accent, ), ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Wrap( spacing: 8, children: [ - _buildChip(LucideIcons.mapPin, i18n.chips.location), + _buildChip(UiIcons.mapPin, i18n.chips.location), _buildChip( - LucideIcons.clock, + UiIcons.clock, i18n.chips.availability, ), - _buildChip(LucideIcons.briefcase, i18n.chips.skills), + _buildChip(UiIcons.briefcase, i18n.chips.skills), ], ), ], @@ -144,17 +151,17 @@ class _AutoMatchToggleState extends State with SingleTickerProv return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(8), + color: UiColors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 12, color: Colors.white), + Icon(icon, size: 12, color: UiColors.white), const SizedBox(width: 4), Text( label, - style: const TextStyle(color: Colors.white, fontSize: 12), + style: UiTypography.body3r.white, ), ], ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/benefits_widget.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/benefits_widget.dart index 8826ea0c..886a44e4 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/benefits_widget.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/benefits_widget.dart @@ -1,6 +1,7 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:lucide_icons/lucide_icons.dart'; + import 'dart:math' as math; import 'package:core_localization/core_localization.dart'; @@ -15,14 +16,14 @@ class BenefitsWidget extends StatelessWidget { Widget build(BuildContext context) { final i18n = t.staff.home.benefits; return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Theme.of(context).dividerColor), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Theme.of(context).colorScheme.onBackground.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -35,7 +36,7 @@ class BenefitsWidget extends StatelessWidget { children: [ Text( i18n.title, - style: Theme.of(context).textTheme.titleMedium, + style: UiTypography.title1m.textPrimary, ), GestureDetector( onTap: () => Modular.to.pushNamed('/benefits'), @@ -43,19 +44,19 @@ class BenefitsWidget extends StatelessWidget { children: [ Text( i18n.view_all, - style: Theme.of(context).textTheme.labelLarge?.copyWith(color: Theme.of(context).colorScheme.primary), + style: UiTypography.buttonL.textPrimary, ), Icon( - LucideIcons.chevronRight, - size: 16, - color: Theme.of(context).colorScheme.primary, + UiIcons.chevronRight, + size: UiConstants.space4, + color: UiColors.primary, ), ], ), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -63,19 +64,19 @@ class BenefitsWidget extends StatelessWidget { label: i18n.items.sick_days, current: 10, total: 40, - color: Theme.of(context).colorScheme.primary, + color: UiColors.primary, ), _BenefitItem( label: i18n.items.vacation, current: 40, total: 40, - color: Theme.of(context).colorScheme.primary, + color: UiColors.primary, ), _BenefitItem( label: i18n.items.holidays, current: 24, total: 24, - color: Theme.of(context).colorScheme.primary, + color: UiColors.primary, ), ], ), @@ -104,13 +105,13 @@ class _BenefitItem extends StatelessWidget { return Column( children: [ SizedBox( - width: 56, - height: 56, + width: UiConstants.space14, + height: UiConstants.space14, child: CustomPaint( painter: _CircularProgressPainter( progress: current / total, color: color, - backgroundColor: const Color(0xFFE5E7EB), + backgroundColor: UiColors.border, strokeWidth: 4, ), child: Center( @@ -119,32 +120,21 @@ class _BenefitItem extends StatelessWidget { children: [ Text( '${current.toInt()}/${total.toInt()}', - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), + style: UiTypography.body3m.textPrimary, ), Text( i18n.hours_label, - style: const TextStyle( - fontSize: 8, - color: Color(0xFF94A3B8), - ), + style: UiTypography.footnote1r.textTertiary, ), ], ), ), ), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( label, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xFF475569), - ), + style: UiTypography.body3m.textSecondary, ), ], ); diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/improve_yourself_widget.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/improve_yourself_widget.dart index 91d3d4af..dd2cf77a 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/improve_yourself_widget.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/improve_yourself_widget.dart @@ -1,3 +1,4 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:core_localization/core_localization.dart'; @@ -32,9 +33,9 @@ class ImproveYourselfWidget extends StatelessWidget { children: [ Text( i18n.title, - style: Theme.of(context).textTheme.titleMedium, + style: UiTypography.title1m.textPrimary, ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), SingleChildScrollView( scrollDirection: Axis.horizontal, clipBehavior: Clip.none, @@ -51,14 +52,14 @@ class ImproveYourselfWidget extends StatelessWidget { onTap: () => Modular.to.pushNamed(item['page']!), child: Container( width: 160, - margin: const EdgeInsets.only(right: 12), + margin: const EdgeInsets.only(right: UiConstants.space3), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Theme.of(context).dividerColor), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Theme.of(context).colorScheme.onBackground.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -69,36 +70,33 @@ class ImproveYourselfWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - height: 96, + height: UiConstants.space24, width: double.infinity, child: Image.network( item['image']!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( - color: Theme.of(context).colorScheme.surfaceVariant, - child: const Icon( - Icons.image_not_supported, - color: Colors.grey, + color: UiColors.background, + child: Icon( + UiIcons.zap, + color: UiColors.mutedForeground, ), ), ), ), Padding( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(UiConstants.space3), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['title']!, - style: Theme.of(context).textTheme.titleSmall, + style: UiTypography.body1m.textPrimary, ), const SizedBox(height: 2), Text( item['description']!, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - ), + style: UiTypography.body3r.textSecondary, maxLines: 2, overflow: TextOverflow.ellipsis, ), diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/more_ways_widget.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/more_ways_widget.dart index 947e2be9..70949197 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/more_ways_widget.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/worker/more_ways_widget.dart @@ -1,8 +1,8 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:core_localization/core_localization.dart'; - /// Widget for displaying more ways to use Krow, using design system tokens. class MoreWaysToUseKrowWidget extends StatelessWidget { /// Creates a [MoreWaysToUseKrowWidget]. @@ -31,9 +31,9 @@ class MoreWaysToUseKrowWidget extends StatelessWidget { children: [ Text( i18n.title, - style: Theme.of(context).textTheme.titleMedium, + style: UiTypography.title1m.textPrimary, ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), SingleChildScrollView( scrollDirection: Axis.horizontal, clipBehavior: Clip.none, @@ -50,14 +50,14 @@ class MoreWaysToUseKrowWidget extends StatelessWidget { onTap: () => Modular.to.pushNamed(item['page']!), child: Container( width: 160, - margin: const EdgeInsets.only(right: 12), + margin: const EdgeInsets.only(right: UiConstants.space3), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Theme.of(context).dividerColor), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Theme.of(context).colorScheme.onBackground.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -68,25 +68,25 @@ class MoreWaysToUseKrowWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - height: 96, + height: UiConstants.space24, width: double.infinity, child: Image.network( item['image']!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( - color: Theme.of(context).colorScheme.surfaceVariant, - child: const Icon( - Icons.image_not_supported, - color: Colors.grey, + color: UiColors.background, + child: Icon( + UiIcons.zap, + color: UiColors.mutedForeground, ), ), ), ), Padding( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(UiConstants.space3), child: Text( item['title']!, - style: Theme.of(context).textTheme.titleSmall, + style: UiTypography.body1m.textPrimary, ), ), ], diff --git a/apps/mobile/packages/features/staff/home/pubspec.yaml b/apps/mobile/packages/features/staff/home/pubspec.yaml index 8d6afcfd..e4e1225d 100644 --- a/apps/mobile/packages/features/staff/home/pubspec.yaml +++ b/apps/mobile/packages/features/staff/home/pubspec.yaml @@ -5,20 +5,10 @@ publish_to: none resolution: workspace environment: - sdk: '>=3.10.0 <4.0.0' + sdk: ">=3.10.0 <4.0.0" flutter: ">=3.0.0" dependencies: - flutter: - sdk: flutter - flutter_bloc: ^8.1.0 - bloc: ^8.1.0 - flutter_modular: ^6.3.0 - equatable: ^2.0.5 - lucide_icons: ^0.257.0 - intl: ^0.20.0 - google_fonts: ^7.0.0 - # Architecture Packages design_system: path: ../../../design_system @@ -32,6 +22,15 @@ dependencies: path: ../shifts krow_data_connect: path: ../../../data_connect + + flutter: + sdk: flutter + flutter_bloc: ^8.1.0 + bloc: ^8.1.0 + flutter_modular: ^6.3.0 + equatable: ^2.0.5 + intl: ^0.20.0 + google_fonts: ^7.0.0 firebase_data_connect: dev_dependencies: diff --git a/apps/mobile/packages/features/staff/payments/lib/src/presentation/pages/payments_page.dart b/apps/mobile/packages/features/staff/payments/lib/src/presentation/pages/payments_page.dart index 29b374e1..c1d76f56 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/presentation/pages/payments_page.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/presentation/pages/payments_page.dart @@ -1,7 +1,7 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import 'package:intl/intl.dart'; import 'package:krow_domain/krow_domain.dart'; import 'package:core_localization/core_localization.dart'; @@ -35,7 +35,7 @@ class _PaymentsPageState extends State { return BlocProvider.value( value: _bloc, child: Scaffold( - backgroundColor: const Color(0xFFF8FAFC), + backgroundColor: UiColors.background, body: BlocConsumer( listener: (context, state) { if (state is PaymentsError) { @@ -58,7 +58,7 @@ class _PaymentsPageState extends State { child: Text( translateErrorKey(state.message), textAlign: TextAlign.center, - style: const TextStyle(color: Color(0xFF64748B)), // TextSecondary + style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), ), ), ); @@ -67,6 +67,14 @@ class _PaymentsPageState extends State { } return const SizedBox.shrink(); }, + ), + ), + ); + } else if (state is PaymentsLoaded) { + return _buildContent(context, state); + } + return const SizedBox.shrink(); + }, ), ), ); @@ -78,63 +86,57 @@ class _PaymentsPageState extends State { children: [ // Header Section with Gradient Container( - decoration: const BoxDecoration( + decoration: BoxDecoration( gradient: LinearGradient( - colors: [Color(0xFF0032A0), Color(0xFF333F48)], + colors: [ + UiColors.primary, + UiColors.primary.withValues(alpha: 0.8), + ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), padding: EdgeInsets.fromLTRB( - 20, - MediaQuery.of(context).padding.top + 24, - 20, - 32, + UiConstants.space5, + MediaQuery.of(context).padding.top + UiConstants.space6, + UiConstants.space5, + UiConstants.space8, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "Earnings", - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + style: UiTypography.displayMb.white, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Main Balance Center( child: Column( children: [ - const Text( + Text( "Total Earnings", - style: TextStyle( - color: Color(0xFFF8E08E), - fontSize: 14, + style: UiTypography.body2r.copyWith( + color: UiColors.accent, ), ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( "\$${state.summary.totalEarnings.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},')}", - style: const TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + style: UiTypography.displayL.white, ), ], ), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Period Tabs Container( - padding: const EdgeInsets.all(4), + padding: const EdgeInsets.all(UiConstants.space1), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), + color: UiColors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: Row( children: [ @@ -150,9 +152,9 @@ class _PaymentsPageState extends State { // Main Content - Offset upwards Transform.translate( - offset: const Offset(0, -16), + offset: const Offset(0, -UiConstants.space4), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -161,76 +163,75 @@ class _PaymentsPageState extends State { payments: state.history, period: state.activePeriod, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Quick Stats Row( children: [ Expanded( child: PaymentStatsCard( - icon: LucideIcons.trendingUp, - iconColor: const Color(0xFF059669), + icon: UiIcons.chart, + iconColor: UiColors.success, label: "This Week", amount: "\$${state.summary.weeklyEarnings}", ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: PaymentStatsCard( - icon: LucideIcons.calendar, - iconColor: const Color(0xFF2563EB), + icon: UiIcons.calendar, + iconColor: UiColors.primary, label: "This Month", amount: "\$${state.summary.monthlyEarnings.toStringAsFixed(0)}", ), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Pending Pay - if(state.summary.pendingEarnings > 0) PendingPayCard( - amount: state.summary.pendingEarnings, - onCashOut: () { - Modular.to.pushNamed('/early-pay'); - }, - ), - const SizedBox(height: 24), - - + if (state.summary.pendingEarnings > 0) + PendingPayCard( + amount: state.summary.pendingEarnings, + onCashOut: () { + Modular.to.pushNamed('/early-pay'); + }, + ), + const SizedBox(height: UiConstants.space6), // Recent Payments - if (state.history.isNotEmpty) Column( - children: [ - const Text( - "Recent Payments", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF0F172A), + if (state.history.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Recent Payments", + style: UiTypography.body2m.textPrimary, ), - ), - const SizedBox(height: 12), - Column( - children: state.history.map((StaffPayment payment) { - return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: PaymentHistoryItem( - amount: payment.amount, - title: "Shift Payment", - location: "Varies", - address: "Payment ID: ${payment.id}", - date: payment.paidAt != null - ? DateFormat('E, MMM d').format(payment.paidAt!) - : 'Pending', - workedTime: "Completed", - hours: 0, - rate: 0.0, - status: payment.status.name.toUpperCase(), - ), - ); - }).toList(), - ), - ], - ), + const SizedBox(height: UiConstants.space3), + Column( + children: state.history.map((StaffPayment payment) { + return Padding( + padding: const EdgeInsets.only( + bottom: UiConstants.space2), + child: PaymentHistoryItem( + amount: payment.amount, + title: "Shift Payment", + location: "Varies", + address: "Payment ID: ${payment.id}", + date: payment.paidAt != null + ? DateFormat('E, MMM d') + .format(payment.paidAt!) + : 'Pending', + workedTime: "Completed", + hours: 0, + rate: 0.0, + status: payment.status.name.toUpperCase(), + ), + ); + }).toList(), + ), + ], + ), const SizedBox(height: 100), ], @@ -248,19 +249,17 @@ class _PaymentsPageState extends State { child: GestureDetector( onTap: () => _bloc.add(ChangePeriodEvent(value)), child: Container( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: UiConstants.space2), decoration: BoxDecoration( - color: isSelected ? Colors.white : Colors.transparent, - borderRadius: BorderRadius.circular(8), + color: isSelected ? UiColors.white : Colors.transparent, + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), child: Center( child: Text( label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: isSelected ? const Color(0xFF0032A0) : Colors.white, - ), + style: isSelected + ? UiTypography.body2m.copyWith(color: UiColors.primary) + : UiTypography.body2m.white, ), ), ), diff --git a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/earnings_graph.dart b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/earnings_graph.dart index 960bb7c7..18a8ac89 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/earnings_graph.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/earnings_graph.dart @@ -1,3 +1,4 @@ +import 'package:design_system/design_system.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -25,26 +26,30 @@ class EarningsGraph extends StatelessWidget { return Container( height: 200, decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + child: Center( + child: Text( + "No sufficient data for graph", + style: UiTypography.body2r.textSecondary, + ), ), - child: const Center(child: Text("No sufficient data for graph")), ); } final List spots = _generateSpots(validPayments); - final double maxX = spots.isNotEmpty ? spots.last.x : 0.0; final double maxY = spots.isNotEmpty ? spots.map((FlSpot s) => s.y).reduce((double a, double b) => a > b ? a : b) : 0.0; return Container( height: 220, - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), offset: const Offset(0, 4), blurRadius: 12, ), @@ -53,15 +58,11 @@ class EarningsGraph extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "Earnings Trend", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF0F172A), - ), + style: UiTypography.body2b.textPrimary, ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Expanded( child: LineChart( LineChartData( @@ -79,7 +80,7 @@ class EarningsGraph extends StatelessWidget { padding: const EdgeInsets.only(top: 8.0), child: Text( DateFormat('d').format(validPayments[index].paidAt!), - style: const TextStyle(fontSize: 10, color: Colors.grey), + style: UiTypography.footnote1r.textSecondary, ), ); } @@ -96,13 +97,13 @@ class EarningsGraph extends StatelessWidget { LineChartBarData( spots: spots, isCurved: true, - color: const Color(0xFF0032A0), + color: UiColors.primary, barWidth: 3, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData( show: true, - color: const Color(0xFF0032A0).withOpacity(0.1), + color: UiColors.primary.withValues(alpha: 0.1), ), ), ], @@ -121,7 +122,7 @@ class EarningsGraph extends StatelessWidget { List _generateSpots(List data) { // Generate spots based on index in the list for simplicity in this demo // Real implementation would map to actual dates on X-axis - return List.generate(data.length, (int index) { + return List.generate(data.length, (int index) { return FlSpot(index.toDouble(), data[index].amount); }); } diff --git a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_history_item.dart b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_history_item.dart index b64b78a9..e068caee 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_history_item.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_history_item.dart @@ -1,5 +1,5 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; class PaymentHistoryItem extends StatelessWidget { @@ -28,13 +28,13 @@ class PaymentHistoryItem extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -50,23 +50,20 @@ class PaymentHistoryItem extends StatelessWidget { width: 6, height: 6, decoration: const BoxDecoration( - color: Color(0xFF3B82F6), // blue-500 + color: UiColors.primary, shape: BoxShape.circle, ), ), const SizedBox(width: 6), - const Text( + Text( "PAID", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w700, - color: Color(0xFF2563EB), // blue-600 - letterSpacing: 0.5, + style: UiTypography.titleUppercase4b.copyWith( + color: UiColors.primary, ), ), ], ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -76,16 +73,16 @@ class PaymentHistoryItem extends StatelessWidget { width: 44, height: 44, decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), // slate-100 - borderRadius: BorderRadius.circular(12), + color: UiColors.secondary, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: const Icon( - LucideIcons.dollarSign, - color: Color(0xFF334155), // slate-700 + UiIcons.chart, + color: UiColors.mutedForeground, size: 24, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), // Content Expanded( @@ -101,18 +98,11 @@ class PaymentHistoryItem extends StatelessWidget { children: [ Text( title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.body2b.textPrimary, ), Text( location, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF475569), // slate-600 - ), + style: UiTypography.body3r.textSecondary, ), ], ), @@ -122,75 +112,59 @@ class PaymentHistoryItem extends StatelessWidget { children: [ Text( "\$${amount.toStringAsFixed(0)}", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.headline4m.textPrimary, ), Text( "\$${rate.toStringAsFixed(0)}/hr Ā· ${hours}h", - style: const TextStyle( - fontSize: 10, - color: Color(0xFF64748B), // slate-500 - ), + style: UiTypography.footnote1r.textSecondary, ), ], ), ], ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), // Date and Time Row( children: [ const Icon( - LucideIcons.calendar, + UiIcons.calendar, size: 12, - color: Color(0xFF64748B), + color: UiColors.mutedForeground, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Text( date, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - ), + style: UiTypography.body3r.textSecondary, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), const Icon( - LucideIcons.clock, + UiIcons.clock, size: 12, - color: Color(0xFF64748B), + color: UiColors.mutedForeground, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Text( workedTime, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - ), + style: UiTypography.body3r.textSecondary, ), ], ), - const SizedBox(height: 4), + const SizedBox(height: 1), // Address Row( children: [ const Icon( - LucideIcons.mapPin, + UiIcons.mapPin, size: 12, - color: Color(0xFF64748B), + color: UiColors.mutedForeground, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Expanded( child: Text( address, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - ), + style: UiTypography.body3r.textSecondary, maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_stats_card.dart b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_stats_card.dart index 77673455..e49174d5 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_stats_card.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/payment_stats_card.dart @@ -1,7 +1,7 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; class PaymentStatsCard extends StatelessWidget { - const PaymentStatsCard({ super.key, required this.icon, @@ -9,6 +9,7 @@ class PaymentStatsCard extends StatelessWidget { required this.label, required this.amount, }); + final IconData icon; final Color iconColor; final String label; @@ -17,13 +18,13 @@ class PaymentStatsCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -35,24 +36,17 @@ class PaymentStatsCard extends StatelessWidget { Row( children: [ Icon(icon, size: 16, color: iconColor), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Text( label, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), // slate-500 - ), + style: UiTypography.body3r.textSecondary, ), ], ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( amount, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Color(0xFF0F172A), // slate-900 - ), + style: UiTypography.headline1m.textPrimary, ), ], ), diff --git a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/pending_pay_card.dart b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/pending_pay_card.dart index 54a56f39..fe49fbf8 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/pending_pay_card.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/presentation/widgets/pending_pay_card.dart @@ -1,5 +1,5 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; class PendingPayCard extends StatelessWidget { @@ -14,17 +14,13 @@ class PendingPayCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(14), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [Color(0xFFEFF6FF), Color(0xFFEFF6FF)], // blue-50 to blue-50 - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(16), + color: UiColors.tagInProgress, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -36,63 +32,34 @@ class PendingPayCard extends StatelessWidget { Row( children: [ Container( - width: 40, - height: 40, + width: UiConstants.space10, + height: UiConstants.space10, decoration: BoxDecoration( - color: const Color(0xFFE8F0FF), - borderRadius: BorderRadius.circular(8), + color: UiColors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), child: const Icon( - LucideIcons.dollarSign, - color: Color(0xFF0047FF), + UiIcons.chart, + color: UiColors.primary, size: 20, ), ), - const SizedBox(width: 10), + const SizedBox(width: UiConstants.space3), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "Pending", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Color(0xFF0F172A), // slate-900 - fontSize: 14, - ), + style: UiTypography.body2b.textPrimary, ), Text( "\$${amount.toStringAsFixed(0)} available", - style: const TextStyle( - fontSize: 12, - color: Color(0xFF475569), // slate-600 - fontWeight: FontWeight.w500, - ), + style: UiTypography.body3m.textSecondary, ), ], ), ], ), - /* - ElevatedButton.icon( - onPressed: onCashOut, - icon: const Icon(LucideIcons.zap, size: 14), - label: const Text("Early Pay"), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0047FF), - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - elevation: 4, - shadowColor: Colors.black.withOpacity(0.2), - textStyle: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - */ ], ), ); diff --git a/apps/mobile/packages/features/staff/payments/pubspec.yaml b/apps/mobile/packages/features/staff/payments/pubspec.yaml index 3712abe6..44ba5aa6 100644 --- a/apps/mobile/packages/features/staff/payments/pubspec.yaml +++ b/apps/mobile/packages/features/staff/payments/pubspec.yaml @@ -1,23 +1,14 @@ name: staff_payments description: Staff Payments feature version: 0.0.1 -publish_to: 'none' +publish_to: "none" resolution: workspace environment: - sdk: '>=3.10.0 <4.0.0' + sdk: ">=3.10.0 <4.0.0" flutter: ">=3.0.0" dependencies: - flutter: - sdk: flutter - firebase_data_connect: ^0.2.2+2 - firebase_auth: ^6.1.4 - flutter_modular: ^6.3.2 - lucide_icons: ^0.257.0 - intl: ^0.20.0 - fl_chart: ^0.66.0 - # Internal packages design_system: path: ../../../design_system @@ -30,10 +21,17 @@ dependencies: krow_data_connect: path: ../../../data_connect + flutter: + sdk: flutter + firebase_data_connect: ^0.2.2+2 + firebase_auth: ^6.1.4 + flutter_modular: ^6.3.2 + lucide_icons: ^0.257.0 + intl: ^0.20.0 + fl_chart: ^0.66.0 flutter_bloc: any equatable: any dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - \ No newline at end of file diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/logout_button.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/logout_button.dart index 3e81c641..a76e14f6 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/logout_button.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/logout_button.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; /// The sign-out button widget. /// @@ -14,31 +13,33 @@ class LogoutButton extends StatelessWidget { @override Widget build(BuildContext context) { final i18n = t.staff.profile.header; - + return Container( width: double.infinity, decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Material( color: const Color(0x00000000), child: InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + borderRadius: UiConstants.radiusLg, child: Padding( - padding: EdgeInsets.symmetric(vertical: UiConstants.space4), + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(LucideIcons.logOut, color: UiColors.destructive, size: 20), - SizedBox(width: UiConstants.space2), + const Icon( + UiIcons.logOut, + color: UiColors.destructive, + size: 20, + ), + const SizedBox(width: UiConstants.space2), Text( i18n.sign_out, - style: UiTypography.body1m.copyWith( - color: UiColors.destructive, - ), + style: UiTypography.body1m.textError, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart index 9d15de3e..bee90690 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_header.dart @@ -34,13 +34,13 @@ class ProfileHeader extends StatelessWidget { return Container( width: double.infinity, - padding: EdgeInsets.fromLTRB( + padding: const EdgeInsets.fromLTRB( UiConstants.space5, UiConstants.space5, UiConstants.space5, UiConstants.space16, ), - decoration: BoxDecoration( + decoration: const BoxDecoration( color: UiColors.primary, borderRadius: BorderRadius.vertical( bottom: Radius.circular(UiConstants.space6), @@ -56,22 +56,20 @@ class ProfileHeader extends StatelessWidget { children: [ Text( i18n.title, - style: UiTypography.headline4m.copyWith( - color: UiColors.primaryForeground, - ), + style: UiTypography.headline4m.textSecondary, ), GestureDetector( onTap: onSignOutTap, child: Text( i18n.sign_out, style: UiTypography.body2m.copyWith( - color: UiColors.primaryForeground.withOpacity(0.8), + color: UiColors.primaryForeground.withValues(alpha: 0.8), ), ), ), ], ), - SizedBox(height: UiConstants.space8), + const SizedBox(height: UiConstants.space8), // Avatar Section Stack( alignment: Alignment.bottomRight, @@ -79,7 +77,7 @@ class ProfileHeader extends StatelessWidget { Container( width: 112, height: 112, - padding: EdgeInsets.all(UiConstants.space1), + padding: const EdgeInsets.all(UiConstants.space1), decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( @@ -87,13 +85,13 @@ class ProfileHeader extends StatelessWidget { end: Alignment.bottomRight, colors: [ UiColors.accent, - UiColors.accent.withOpacity(0.5), + UiColors.accent.withValues(alpha: 0.5), UiColors.primaryForeground, ], ), boxShadow: [ BoxShadow( - color: UiColors.foreground.withOpacity(0.2), + color: UiColors.foreground.withValues(alpha: 0.2), blurRadius: 10, offset: const Offset(0, 4), ), @@ -103,7 +101,7 @@ class ProfileHeader extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: UiColors.primaryForeground.withOpacity(0.2), + color: UiColors.primaryForeground.withValues(alpha: 0.2), width: 4, ), ), @@ -123,16 +121,16 @@ class ProfileHeader extends StatelessWidget { end: Alignment.bottomRight, colors: [ UiColors.accent, - UiColors.accent.withOpacity(0.7), + UiColors.accent.withValues(alpha: 0.7), ], ), ), alignment: Alignment.center, child: Text( - fullName.isNotEmpty ? fullName[0].toUpperCase() : 'K', - style: UiTypography.displayM.copyWith( - color: UiColors.primary, - ), + fullName.isNotEmpty + ? fullName[0].toUpperCase() + : 'K', + style: UiTypography.displayM.primary, ), ) : null, @@ -148,7 +146,7 @@ class ProfileHeader extends StatelessWidget { border: Border.all(color: UiColors.primary, width: 2), boxShadow: [ BoxShadow( - color: UiColors.foreground.withOpacity(0.1), + color: UiColors.foreground.withValues(alpha: 0.1), blurRadius: 4, ), ], @@ -161,28 +159,24 @@ class ProfileHeader extends StatelessWidget { ), ], ), - SizedBox(height: UiConstants.space4), + const SizedBox(height: UiConstants.space4), Text( fullName, - style: UiTypography.headline3m.copyWith( - color: UiColors.primaryForeground, - ), + style: UiTypography.headline3m.textPlaceholder, ), - SizedBox(height: UiConstants.space1), + const SizedBox(height: UiConstants.space1), Container( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: UiConstants.space3, vertical: UiConstants.space1, ), decoration: BoxDecoration( - color: UiColors.accent.withOpacity(0.2), + color: UiColors.accent.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(UiConstants.space5), ), child: Text( level, - style: UiTypography.footnote1b.copyWith( - color: UiColors.accent, - ), + style: UiTypography.footnote1b.accent, ), ), ], diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart index 6fafeaa9..d61fac6f 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart @@ -25,10 +25,10 @@ class ProfileMenuItem extends StatelessWidget { child: Container( decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), - padding: EdgeInsets.all(UiConstants.space2), + padding: const EdgeInsets.all(UiConstants.space2), child: AspectRatio( aspectRatio: 1.0, child: Stack( @@ -42,24 +42,23 @@ class ProfileMenuItem extends StatelessWidget { width: 36, height: 36, decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.08), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + color: UiColors.primary.withValues(alpha: 0.08), + borderRadius: UiConstants.radiusLg, ), alignment: Alignment.center, child: Icon(icon, color: UiColors.primary, size: 20), ), - SizedBox(height: UiConstants.space1), + const SizedBox(height: UiConstants.space1), Padding( - padding: EdgeInsets.symmetric(horizontal: UiConstants.space1), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space1, + ), child: Text( label, textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, - style: UiTypography.footnote1m.copyWith( - color: UiColors.foreground, - height: 1.2, - ), + style: UiTypography.footnote1m.textSecondary, ), ), ], @@ -76,16 +75,18 @@ class ProfileMenuItem extends StatelessWidget { shape: BoxShape.circle, color: completed! ? UiColors.primary - : UiColors.primary.withOpacity(0.1), + : UiColors.primary.withValues(alpha: 0.1), ), alignment: Alignment.center, child: completed! - ? const Icon(Icons.check, size: 10, color: UiColors.primaryForeground) + ? const Icon( + UiIcons.check, + size: 10, + color: UiColors.primaryForeground, + ) : Text( "!", - style: UiTypography.footnote2b.copyWith( - color: UiColors.primary, - ), + style: UiTypography.footnote2b.primary, ), ), ), diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_score_bar.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_score_bar.dart index 4fa8838b..82c0e4ea 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_score_bar.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_score_bar.dart @@ -19,10 +19,10 @@ class ReliabilityScoreBar extends StatelessWidget { final score = (reliabilityScore ?? 0) / 100; return Container( - padding: EdgeInsets.all(UiConstants.space4), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + color: UiColors.primary.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -32,19 +32,15 @@ class ReliabilityScoreBar extends StatelessWidget { children: [ Text( i18n.title, - style: UiTypography.body2m.copyWith( - color: UiColors.primary, - ), + style: UiTypography.body2m.primary, ), Text( "${reliabilityScore ?? 0}%", - style: UiTypography.headline4m.copyWith( - color: UiColors.primary, - ), + style: UiTypography.headline4m.primary, ), ], ), - SizedBox(height: UiConstants.space2), + const SizedBox(height: UiConstants.space2), ClipRRect( borderRadius: BorderRadius.circular(UiConstants.space1), child: LinearProgressIndicator( @@ -55,12 +51,10 @@ class ReliabilityScoreBar extends StatelessWidget { ), ), Padding( - padding: EdgeInsets.only(top: UiConstants.space2), + padding: const EdgeInsets.only(top: UiConstants.space2), child: Text( i18n.description, - style: UiTypography.footnote2r.copyWith( - color: UiColors.mutedForeground, - ), + style: UiTypography.footnote2r.textSecondary, ), ), ], diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_stats_card.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_stats_card.dart index 05594a60..b883e003 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_stats_card.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/reliability_stats_card.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; /// Displays the staff member's reliability statistics (Shifts, Rating, On Time, etc.). /// @@ -24,14 +23,14 @@ class ReliabilityStatsCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.all(UiConstants.space4), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: UiColors.foreground.withOpacity(0.05), + color: UiColors.foreground.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 1), ), @@ -42,31 +41,31 @@ class ReliabilityStatsCard extends StatelessWidget { children: [ _buildStatItem( context, - LucideIcons.briefcase, + UiIcons.briefcase, "${totalShifts ?? 0}", "Shifts", ), _buildStatItem( context, - LucideIcons.star, + UiIcons.star, (averageRating ?? 0.0).toStringAsFixed(1), "Rating", ), _buildStatItem( context, - LucideIcons.clock, + UiIcons.clock, "${onTimeRate ?? 0}%", "On Time", ), _buildStatItem( context, - LucideIcons.xCircle, + UiIcons.xCircle, "${noShowCount ?? 0}", "No Shows", ), _buildStatItem( context, - LucideIcons.ban, + UiIcons.ban, "${cancellationCount ?? 0}", "Cancel.", ), @@ -88,26 +87,22 @@ class ReliabilityStatsCard extends StatelessWidget { width: 40, height: 40, decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.1), + color: UiColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), alignment: Alignment.center, child: Icon(icon, size: 20, color: UiColors.primary), ), - SizedBox(height: UiConstants.space1), + const SizedBox(height: UiConstants.space1), Text( value, - style: UiTypography.body1b.copyWith( - color: UiColors.foreground, - ), + style: UiTypography.body1b.textSecondary, ), FittedBox( fit: BoxFit.scaleDown, child: Text( label, - style: UiTypography.footnote2r.copyWith( - color: UiColors.mutedForeground, - ), + style: UiTypography.footnote2r.textSecondary, ), ), ], diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/section_title.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/section_title.dart index 89167c61..3cd0c9e0 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/section_title.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/section_title.dart @@ -13,14 +13,11 @@ class SectionTitle extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, - padding: EdgeInsets.only(left: UiConstants.space1), - margin: EdgeInsets.only(bottom: UiConstants.space3), + padding: const EdgeInsets.only(left: UiConstants.space1), + margin: const EdgeInsets.only(bottom: UiConstants.space3), child: Text( title.toUpperCase(), - style: UiTypography.footnote1b.copyWith( - color: UiColors.mutedForeground, - letterSpacing: 0.5, - ), + style: UiTypography.footnote1b.textSecondary, ), ); } diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/pages/certificates_page.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/pages/certificates_page.dart index c90355e0..8b81bbc9 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/pages/certificates_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/pages/certificates_page.dart @@ -65,7 +65,7 @@ class CertificatesPage extends StatelessWidget { Transform.translate( offset: const Offset(0, -48), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5), child: Column( children: [ ...documents.map((StaffDocument doc) => CertificateCard( @@ -82,11 +82,11 @@ class CertificatesPage extends StatelessWidget { ); }, )), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), AddCertificateCard( onTap: () => _showUploadModal(context, null), ), - const SizedBox(height: 32), + const SizedBox(height: UiConstants.space8), ], ), ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/add_certificate_card.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/add_certificate_card.dart index 315e91ec..8e0634a1 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/add_certificate_card.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/add_certificate_card.dart @@ -12,37 +12,36 @@ class AddCertificateCard extends StatelessWidget { return GestureDetector( onTap: onTap, child: Container( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [Colors.grey[50]!, Colors.grey[100]!], // Keep prototype style + colors: [ + UiColors.bgSecondary.withValues(alpha: 0.5), + UiColors.bgSecondary, + ], ), - borderRadius: BorderRadius.circular(16), + borderRadius: UiConstants.radiusLg, border: Border.all( - color: Colors.grey[300]!, + color: UiColors.border, style: BorderStyle.solid, ), ), child: Row( children: [ const Icon(UiIcons.add, color: UiColors.primary, size: 24), - const SizedBox(width: 16), + const SizedBox(width: UiConstants.space4), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( t.staff_certificates.add_more.title, - style: UiTypography.body1b.copyWith( // 16px Bold - color: UiColors.textPrimary, - ), + style: UiTypography.body1b.textPrimary, ), Text( t.staff_certificates.add_more.subtitle, - style: UiTypography.body3r.copyWith( // 12px Regular - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_card.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_card.dart index c1cca227..c798f97a 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_card.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_card.dart @@ -1,8 +1,8 @@ +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:core_localization/core_localization.dart'; class CertificateCard extends StatelessWidget { final StaffDocument document; @@ -39,13 +39,13 @@ class CertificateCard extends StatelessWidget { final _CertificateUiProps uiProps = _getUiProps(document.documentId); return Container( - margin: const EdgeInsets.only(bottom: 16), + margin: const EdgeInsets.only(bottom: UiConstants.space4), decoration: BoxDecoration( color: UiColors.white, - borderRadius: BorderRadius.circular(UiConstants.space4), + borderRadius: UiConstants.radiusLg, boxShadow: [ BoxShadow( - color: UiColors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), @@ -57,11 +57,14 @@ class CertificateCard extends StatelessWidget { children: [ if (isExpiring || isExpired) Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space2, + ), decoration: BoxDecoration( - color: const Color(0xFFF9E547).withOpacity(0.2), // Yellow tint - border: const Border( - bottom: BorderSide(color: Color(0x66F9E547)), + color: UiColors.accent.withValues(alpha: 0.2), // Yellow tint + border: Border( + bottom: BorderSide(color: UiColors.accent.withValues(alpha: 0.4)), ), ), child: Row( @@ -76,16 +79,14 @@ class CertificateCard extends StatelessWidget { isExpired ? t.staff_certificates.card.expired : t.staff_certificates.card.expires_in_days(days: _daysUntilExpiry(document.expiryDate)), - style: UiTypography.body3m.copyWith( // 12px Medium - color: UiColors.textPrimary, - ), + style: UiTypography.body3m.textPrimary, ), ], ), ), Padding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(UiConstants.space5), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -96,8 +97,8 @@ class CertificateCard extends StatelessWidget { width: 64, height: 64, decoration: BoxDecoration( - color: uiProps.color.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), + color: uiProps.color.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, ), child: Center( child: Icon( @@ -137,7 +138,7 @@ class CertificateCard extends StatelessWidget { ), ], ), - const SizedBox(width: 16), + const SizedBox(width: UiConstants.space4), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -151,16 +152,12 @@ class CertificateCard extends StatelessWidget { children: [ Text( document.name, - style: UiTypography.body1m.copyWith( // 16px Medium - color: UiColors.textPrimary, - ), + style: UiTypography.body1m.textPrimary, ), const SizedBox(height: 2), Text( document.description ?? '', // Optional description - style: UiTypography.body3r.copyWith( // 12px Regular - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, ), ], ), @@ -172,7 +169,7 @@ class CertificateCard extends StatelessWidget { ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), if (showComplete) _buildCompleteStatus(document.expiryDate), @@ -186,9 +183,11 @@ class CertificateCard extends StatelessWidget { style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, + ), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space3, ), - padding: const EdgeInsets.symmetric(vertical: 12), elevation: 0, ), child: Row( @@ -202,9 +201,7 @@ class CertificateCard extends StatelessWidget { const SizedBox(width: 8), Text( t.staff_certificates.card.upload_button, - style: UiTypography.body2m.copyWith( // 14px Medium - color: UiColors.white, - ), + style: UiTypography.body2m.white, ), ], ), @@ -212,7 +209,7 @@ class CertificateCard extends StatelessWidget { ), if (showComplete || isExpiring || isExpired) ...[ - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), SizedBox( width: double.infinity, child: OutlinedButton.icon( @@ -223,13 +220,15 @@ class CertificateCard extends StatelessWidget { foregroundColor: UiColors.textPrimary, side: const BorderSide(color: UiColors.border), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, + ), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space3, ), - padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), SizedBox( width: double.infinity, child: TextButton.icon( @@ -238,9 +237,11 @@ class CertificateCard extends StatelessWidget { label: Text(t.staff_certificates.card.remove), style: TextButton.styleFrom( foregroundColor: UiColors.destructive, - padding: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space3, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), ), ), @@ -274,16 +275,14 @@ class CertificateCard extends StatelessWidget { const SizedBox(width: 8), Text( t.staff_certificates.card.verified, - style: UiTypography.body2m.copyWith( - color: UiColors.primary, - ), + style: UiTypography.body2m.textPrimary, ), ], ), if (expiryDate != null) Text( t.staff_certificates.card.exp(date: DateFormat('MMM d, yyyy').format(expiryDate)), - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body3r.textSecondary, ), ], ); diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_upload_modal.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_upload_modal.dart index 852038a2..5651d6af 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_upload_modal.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificate_upload_modal.dart @@ -6,7 +6,8 @@ import 'package:flutter/material.dart'; class CertificateUploadModal extends StatelessWidget { /// The document being edited, or null for a new upload. // ignore: unused_field - final dynamic document; // Using dynamic for now as we don't import domain here to avoid direct coupling if possible, but actually we should import domain. + final dynamic + document; // Using dynamic for now as we don't import domain here to avoid direct coupling if possible, but actually we should import domain. // Ideally, widgets should be dumb. Let's import domain. final VoidCallback onSave; @@ -24,13 +25,13 @@ class CertificateUploadModal extends StatelessWidget { return Container( height: MediaQuery.of(context).size.height * 0.75, decoration: const BoxDecoration( - color: Colors.white, + color: UiColors.bgPopup, borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), + topLeft: Radius.circular(UiConstants.radiusBase), + topRight: Radius.circular(UiConstants.radiusBase), ), ), - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(UiConstants.space6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -39,7 +40,7 @@ class CertificateUploadModal extends StatelessWidget { children: [ Text( t.staff_certificates.upload_modal.title, - style: UiTypography.headline3m.copyWith(color: UiColors.textPrimary), + style: UiTypography.headline3m.textPrimary, ), IconButton( onPressed: onCancel, @@ -47,35 +48,42 @@ class CertificateUploadModal extends StatelessWidget { ), ], ), - const SizedBox(height: 32), + const SizedBox(height: UiConstants.space8), Text( t.staff_certificates.upload_modal.expiry_label, style: UiTypography.body1m, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space3, + ), decoration: BoxDecoration( border: Border.all(color: UiColors.border), - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), child: Row( children: [ - const Icon(UiIcons.calendar, size: 20, color: UiColors.textSecondary), - const SizedBox(width: 12), + const Icon( + UiIcons.calendar, + size: 20, + color: UiColors.textSecondary, + ), + const SizedBox(width: UiConstants.space3), Text( t.staff_certificates.upload_modal.select_date, - style: UiTypography.body1m.copyWith(color: UiColors.textSecondary), + style: UiTypography.body1m.textSecondary, ), ], ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Text( t.staff_certificates.upload_modal.upload_file, style: UiTypography.body1m, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Expanded( child: Container( width: double.infinity, @@ -84,16 +92,16 @@ class CertificateUploadModal extends StatelessWidget { color: UiColors.border, style: BorderStyle.solid, ), - borderRadius: BorderRadius.circular(16), + borderRadius: UiConstants.radiusLg, color: UiColors.background, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - padding: const EdgeInsets.all(16), - decoration: const BoxDecoration( - color: Color(0xFFEFF6FF), // Light blue + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.tagActive, shape: BoxShape.circle, ), child: const Icon( @@ -102,7 +110,7 @@ class CertificateUploadModal extends StatelessWidget { color: UiColors.primary, ), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Text( t.staff_certificates.upload_modal.drag_drop, style: UiTypography.body1m, @@ -110,43 +118,51 @@ class CertificateUploadModal extends StatelessWidget { const SizedBox(height: 4), Text( t.staff_certificates.upload_modal.supported_formats, - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body3r.textSecondary, ), ], ), ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Row( children: [ Expanded( child: OutlinedButton( onPressed: onCancel, style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), side: const BorderSide(color: UiColors.border), ), - child: Text(t.staff_certificates.upload_modal.cancel, - style: UiTypography.body1m.copyWith(color: UiColors.textPrimary)), + child: Text( + t.staff_certificates.upload_modal.cancel, + style: UiTypography.body1m.textPrimary, + ), ), ), - const SizedBox(width: 16), + const SizedBox(width: UiConstants.space4), Expanded( child: ElevatedButton( onPressed: onSave, style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), elevation: 0, ), - child: Text(t.staff_certificates.upload_modal.save, - style: UiTypography.body1m.copyWith(color: Colors.white)), + child: Text( + t.staff_certificates.upload_modal.save, + style: UiTypography.body1m.white, + ), ), ), ], diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificates_header.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificates_header.dart index 0d07100f..49555db9 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificates_header.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/certificates/lib/src/presentation/widgets/certificates_header.dart @@ -20,13 +20,21 @@ class CertificatesHeader extends StatelessWidget { final int progressPercent = totalCount == 0 ? 0 : (progressValue * 100).round(); return Container( - padding: const EdgeInsets.fromLTRB(20, 60, 20, 80), + padding: const EdgeInsets.fromLTRB( + UiConstants.space5, + 60, + UiConstants.space5, + 80, + ), // Keeping gradient as per prototype layout requirement decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [UiColors.primary, Color(0xFF1E40AF)], // Using Primary and a darker shade + colors: [ + UiColors.primary, + Color(0xFF1E40AF), + ], // Using Primary and a darker shade ), ), child: Column( @@ -39,7 +47,7 @@ class CertificatesHeader extends StatelessWidget { width: 40, height: 40, decoration: BoxDecoration( - color: UiColors.white.withOpacity(0.1), + color: UiColors.white.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: const Icon( @@ -49,16 +57,14 @@ class CertificatesHeader extends StatelessWidget { ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Text( t.staff_certificates.title, - style: UiTypography.headline3m.copyWith( // 18px Bold - color: UiColors.white, - ), + style: UiTypography.headline3m.white, ), ], ), - const SizedBox(height: 32), + const SizedBox(height: UiConstants.space8), Row( children: [ SizedBox( @@ -70,53 +76,48 @@ class CertificatesHeader extends StatelessWidget { CircularProgressIndicator( value: progressValue, strokeWidth: 8, - backgroundColor: UiColors.white.withOpacity(0.2), + backgroundColor: UiColors.white.withValues(alpha: 0.2), valueColor: const AlwaysStoppedAnimation( - Color(0xFFF9E547), // Yellow from prototype + UiColors.accent, // Yellow from prototype ), ), Center( child: Text( '$progressPercent%', - style: UiTypography.display1b.copyWith( // 26px Bold - color: UiColors.white, - ), + style: UiTypography.display1b.white, ), ), ], ), ), - const SizedBox(width: 24), + const SizedBox(width: UiConstants.space6), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( t.staff_certificates.progress.title, - style: UiTypography.body1b.copyWith( // 16px Bold - color: UiColors.white, - ), + style: UiTypography.body1b.white, ), const SizedBox(height: 4), Text( - t.staff_certificates.progress.verified_count(completed: completedCount, total: totalCount), - style: UiTypography.body3r.copyWith( // 12px Regular - color: UiColors.white.withOpacity(0.7), + t.staff_certificates.progress.verified_count( + completed: completedCount, total: totalCount), + style: UiTypography.body3r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), ), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Row( children: [ const Icon( UiIcons.shield, - color: Color(0xFFF9E547), + color: UiColors.accent, size: 16, ), const SizedBox(width: 8), Text( t.staff_certificates.progress.active, - style: UiTypography.body3m.copyWith( // 12px Medium - color: const Color(0xFFF9E547), - ), + style: UiTypography.body3m.accent, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_card.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_card.dart index 764caa18..0331e566 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_card.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_card.dart @@ -1,7 +1,6 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:lucide_icons/lucide_icons.dart'; // ignore: depend_on_referenced_packages import 'package:core_localization/core_localization.dart'; @@ -18,11 +17,11 @@ class DocumentCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(bottom: UiConstants.space3), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(8), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Row( @@ -32,7 +31,7 @@ class DocumentCard extends StatelessWidget { width: 40, height: 40, decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.1), + color: UiColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: const Center( @@ -43,7 +42,7 @@ class DocumentCard extends StatelessWidget { ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -53,9 +52,7 @@ class DocumentCard extends StatelessWidget { children: [ Text( document.name, - style: UiTypography.body1m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body1m.textPrimary, ), _getStatusIcon(document.status), ], @@ -64,15 +61,13 @@ class DocumentCard extends StatelessWidget { if (document.description != null) Text( document.description!, - style: UiTypography.body2r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body2r.textSecondary, ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( children: [ _buildStatusBadge(document.status), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), _buildActionButton(document.status), ], ), @@ -114,27 +109,27 @@ class DocumentCard extends StatelessWidget { switch (status) { case DocumentStatus.verified: - bg = UiColors.textSuccess.withOpacity(0.2); + bg = UiColors.tagSuccess; text = UiColors.textSuccess; label = t.staff_documents.card.verified; break; case DocumentStatus.pending: - bg = UiColors.textWarning.withOpacity(0.2); + bg = UiColors.tagPending; text = UiColors.textWarning; label = t.staff_documents.card.pending; break; case DocumentStatus.missing: - bg = UiColors.textError.withOpacity(0.2); + bg = UiColors.textError.withValues(alpha: 0.1); text = UiColors.textError; label = t.staff_documents.card.missing; break; case DocumentStatus.rejected: - bg = UiColors.textError.withOpacity(0.2); + bg = UiColors.textError.withValues(alpha: 0.1); text = UiColors.textError; label = t.staff_documents.card.rejected; break; case DocumentStatus.expired: - bg = UiColors.textError.withOpacity(0.2); + bg = UiColors.textError.withValues(alpha: 0.1); text = UiColors.textError; label = t.staff_documents.card.rejected; // Or define "Expired" string break; @@ -165,7 +160,7 @@ class DocumentCard extends StatelessWidget { child: Row( children: [ Icon( - isVerified ? UiIcons.eye : LucideIcons.upload, + isVerified ? UiIcons.eye : UiIcons.upload, size: 16, color: UiColors.primary, ), @@ -174,9 +169,7 @@ class DocumentCard extends StatelessWidget { isVerified ? t.staff_documents.card.view : t.staff_documents.card.upload, - style: UiTypography.body3m.copyWith( - color: UiColors.primary, - ), + style: UiTypography.body3m.primary, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/documents_progress_card.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/documents_progress_card.dart index 95395e08..50180fb6 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/documents_progress_card.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/documents_progress_card.dart @@ -24,10 +24,10 @@ class DocumentsProgressCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(8), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Column( @@ -37,16 +37,14 @@ class DocumentsProgressCard extends StatelessWidget { children: [ Text( t.staff_documents.verification_card.title, - style: UiTypography.body1m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body1m.textPrimary, ), Text( t.staff_documents.verification_card.progress( completed: completedCount, total: totalCount, ), - style: UiTypography.body2r.copyWith(color: UiColors.primary), + style: UiTypography.body2r.primary, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_i9_page.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_i9_page.dart index fc513b7a..19affeb5 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_i9_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_i9_page.dart @@ -83,10 +83,13 @@ class _FormI9PageState extends State { if (state.status == FormI9Status.success) { // Success view is handled by state check in build or we can navigate } else if (state.status == FormI9Status.failure) { - final ScaffoldMessengerState messenger = ScaffoldMessenger.of(context); + final ScaffoldMessengerState messenger = + ScaffoldMessenger.of(context); messenger.hideCurrentSnackBar(); messenger.showSnackBar( - SnackBar(content: Text(state.errorMessage ?? 'An error occurred')), + SnackBar( + content: Text(state.errorMessage ?? 'An error occurred'), + ), ); } }, @@ -100,7 +103,10 @@ class _FormI9PageState extends State { _buildHeader(context, state), Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + vertical: UiConstants.space6, + ), child: _buildCurrentStep(context, state), ), ), @@ -118,9 +124,9 @@ class _FormI9PageState extends State { backgroundColor: UiColors.background, body: Center( child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(UiConstants.space6), child: Container( - padding: const EdgeInsets.all(32), + padding: const EdgeInsets.all(UiConstants.space8), decoration: BoxDecoration( color: UiColors.bgPopup, borderRadius: BorderRadius.circular(24), @@ -132,40 +138,40 @@ class _FormI9PageState extends State { Container( width: 64, height: 64, - decoration: const BoxDecoration( - color: Color(0xFFDCFCE7), + decoration: BoxDecoration( + color: UiColors.tagSuccess, shape: BoxShape.circle, ), child: const Icon( UiIcons.success, - color: Color(0xFF16A34A), + color: UiColors.textSuccess, size: 32, ), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Text( 'Form I-9 Submitted!', - style: UiTypography.headline4m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline4m.textPrimary, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( 'Your employment eligibility verification has been submitted.', textAlign: TextAlign.center, - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Modular.to.pop(true), style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, - foregroundColor: UiColors.bgPopup, - padding: const EdgeInsets.symmetric(vertical: 16), + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), elevation: 0, ), @@ -183,7 +189,12 @@ class _FormI9PageState extends State { Widget _buildHeader(BuildContext context, FormI9State state) { return Container( color: UiColors.primary, - padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20), + padding: const EdgeInsets.only( + top: 60, + bottom: UiConstants.space6, + left: UiConstants.space5, + right: UiConstants.space5, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -193,31 +204,34 @@ class _FormI9PageState extends State { onTap: () => Modular.to.pop(), child: const Icon( UiIcons.arrowLeft, - color: UiColors.bgPopup, + color: UiColors.white, size: 24, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Form I-9', - style: UiTypography.headline4m.copyWith( - color: UiColors.bgPopup, - ), + style: UiTypography.headline4m.white, ), Text( 'Employment Eligibility Verification', - style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), + style: UiTypography.body3r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), + ), ), ], ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Row( - children: _steps.asMap().entries.map((MapEntry> entry) { + children: _steps + .asMap() + .entries + .map((MapEntry> entry) { final int idx = entry.key; final bool isLast = idx == _steps.length - 1; return Expanded( @@ -228,8 +242,8 @@ class _FormI9PageState extends State { height: 4, decoration: BoxDecoration( color: idx <= state.currentStep - ? UiColors.bgPopup - : UiColors.bgPopup.withOpacity(0.3), + ? UiColors.white + : UiColors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(2), ), ), @@ -240,20 +254,21 @@ class _FormI9PageState extends State { ); }).toList(), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Step ${state.currentStep + 1} of ${_steps.length}', - style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), + style: UiTypography.body3r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), + ), ), Expanded( child: Text( _steps[state.currentStep]['title']!, textAlign: TextAlign.end, - style: UiTypography.body3m.copyWith( - color: UiColors.bgPopup, + style: UiTypography.body3m.white.copyWith( fontWeight: FontWeight.w500, ), ), @@ -292,8 +307,7 @@ class _FormI9PageState extends State { children: [ Text( label, - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, + style: UiTypography.body3m.textSecondary.copyWith( fontWeight: FontWeight.w500, ), ), @@ -305,26 +319,26 @@ class _FormI9PageState extends State { ), onChanged: onChanged, keyboardType: keyboardType, - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, decoration: InputDecoration( hintText: placeholder, - hintStyle: TextStyle(color: Colors.grey[400]), + hintStyle: const TextStyle(color: UiColors.textPlaceholder), filled: true, fillColor: UiColors.bgPopup, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + horizontal: UiConstants.space4, + vertical: UiConstants.space4, ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.primary), ), ), @@ -455,15 +469,15 @@ class _FormI9PageState extends State { children: [ Text( 'State *', - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, + style: UiTypography.body3m.textSecondary.copyWith( fontWeight: FontWeight.w500, ), ), const SizedBox(height: 6), DropdownButtonFormField( value: state.state.isEmpty ? null : state.state, - onChanged: (String? val) => context.read().stateChanged(val ?? ''), + onChanged: (String? val) => + context.read().stateChanged(val ?? ''), items: _usStates.map((String stateAbbr) { return DropdownMenuItem( value: stateAbbr, @@ -473,13 +487,15 @@ class _FormI9PageState extends State { decoration: InputDecoration( filled: true, fillColor: UiColors.bgPopup, - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + contentPadding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), ), @@ -507,9 +523,9 @@ class _FormI9PageState extends State { children: [ Text( 'I attest, under penalty of perjury, that I am (check one of the following boxes):', - style: UiTypography.body2m.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2m.textPrimary, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), _buildRadioOption( context, state, @@ -578,15 +594,21 @@ class _FormI9PageState extends State { ); } - Widget _buildRadioOption(BuildContext context, FormI9State state, String value, String label, {Widget? child}) { + Widget _buildRadioOption( + BuildContext context, + FormI9State state, + String value, + String label, { + Widget? child, + }) { final bool isSelected = state.citizenshipStatus == value; return GestureDetector( onTap: () => context.read().citizenshipStatusChanged(value), child: Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, border: Border.all( color: isSelected ? UiColors.primary : UiColors.border, width: isSelected ? 2 : 1, @@ -602,18 +624,16 @@ class _FormI9PageState extends State { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: isSelected ? UiColors.primary : Colors.grey, + color: isSelected ? UiColors.primary : UiColors.border, width: isSelected ? 6 : 2, ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Text( label, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), ), ], @@ -630,10 +650,10 @@ class _FormI9PageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Column( @@ -643,15 +663,21 @@ class _FormI9PageState extends State { 'Summary', style: UiTypography.headline4m.copyWith(fontSize: 14), ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), _buildSummaryRow('Name', '${state.firstName} ${state.lastName}'), _buildSummaryRow('Address', '${state.address}, ${state.city}'), - _buildSummaryRow('SSN', '***-**-${state.ssn.length >= 4 ? state.ssn.substring(state.ssn.length - 4) : '****'}'), - _buildSummaryRow('Citizenship', _getReadableCitizenship(state.citizenshipStatus)), + _buildSummaryRow( + 'SSN', + '***-**-${state.ssn.length >= 4 ? state.ssn.substring(state.ssn.length - 4) : '****'}', + ), + _buildSummaryRow( + 'Citizenship', + _getReadableCitizenship(state.citizenshipStatus), + ), ], ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), CheckboxListTile( value: state.preparerUsed, onChanged: (bool? val) { @@ -660,29 +686,27 @@ class _FormI9PageState extends State { contentPadding: EdgeInsets.zero, title: Text( 'I used a preparer or translator', - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, ), controlAffinity: ListTileControlAffinity.leading, activeColor: UiColors.primary, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.amber[50], - borderRadius: BorderRadius.circular(12), + color: UiColors.accent.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, ), - child: const Text( + child: Text( 'I am aware that federal law provides for imprisonment and/or fines for false statements or use of false documents in connection with the completion of this form.', - style: TextStyle(fontSize: 12, color: Color(0xFFB45309)), + style: UiTypography.body3r.textWarning.copyWith(fontSize: 12), ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Text( 'Signature (type your full name) *', - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3m.textSecondary, ), const SizedBox(height: 6), TextField( @@ -690,44 +714,46 @@ class _FormI9PageState extends State { ..selection = TextSelection.fromPosition( TextPosition(offset: state.signature.length), ), - onChanged: (String val) => context.read().signatureChanged(val), + onChanged: (String val) => + context.read().signatureChanged(val), decoration: InputDecoration( hintText: 'Type your full name', filled: true, fillColor: UiColors.bgPopup, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + horizontal: UiConstants.space4, + vertical: UiConstants.space4, ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.primary), ), ), style: const TextStyle(fontFamily: 'Cursive', fontSize: 18), ), - const SizedBox(height: 16), - Text( + const SizedBox(height: UiConstants.space4), + Text( 'Date', - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3m.textSecondary, ), const SizedBox(height: 6), Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space4, + ), decoration: BoxDecoration( - color: const Color(0xFFF3F4F6), - borderRadius: BorderRadius.circular(12), + color: UiColors.bgSecondary, + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Text( @@ -747,15 +773,13 @@ class _FormI9PageState extends State { children: [ Text( label, - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), Expanded( child: Text( value, textAlign: TextAlign.end, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), ), ], @@ -780,7 +804,7 @@ class _FormI9PageState extends State { Widget _buildFooter(BuildContext context, FormI9State state) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: const BoxDecoration( color: UiColors.bgPopup, border: Border(top: BorderSide(color: UiColors.border)), @@ -791,24 +815,30 @@ class _FormI9PageState extends State { if (state.currentStep > 0) Expanded( child: Padding( - padding: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.only(right: UiConstants.space3), child: OutlinedButton( onPressed: () => _handleBack(context), style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), side: const BorderSide(color: UiColors.border), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(UiIcons.arrowLeft, size: 16, color: UiColors.textPrimary), + const Icon( + UiIcons.arrowLeft, + size: 16, + color: UiColors.textPrimary, + ), const SizedBox(width: 8), Text( 'Back', - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, ), ], ), @@ -818,16 +848,20 @@ class _FormI9PageState extends State { Expanded( flex: 2, child: ElevatedButton( - onPressed: (_canProceed(state) && state.status != FormI9Status.submitting) + onPressed: ( + _canProceed(state) && + state.status != FormI9Status.submitting) ? () => _handleNext(context, state.currentStep) : null, style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, - disabledBackgroundColor: Colors.grey[300], - foregroundColor: UiColors.bgPopup, - padding: const EdgeInsets.symmetric(vertical: 16), + disabledBackgroundColor: UiColors.bgSecondary, + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), elevation: 0, ), @@ -836,7 +870,7 @@ class _FormI9PageState extends State { width: 20, height: 20, child: CircularProgressIndicator( - color: UiColors.bgPopup, + color: UiColors.white, strokeWidth: 2, ), ) @@ -850,7 +884,7 @@ class _FormI9PageState extends State { ), if (state.currentStep < _steps.length - 1) ...[ const SizedBox(width: 8), - const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup), + const Icon(UiIcons.arrowRight, size: 16, color: UiColors.white), ], ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_w4_page.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_w4_page.dart index 7d147b91..c8969568 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_w4_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/form_w4_page.dart @@ -146,7 +146,10 @@ class _FormW4PageState extends State { _buildHeader(context, state), Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + vertical: UiConstants.space6, + ), child: _buildCurrentStep(context, state), ), ), @@ -164,9 +167,9 @@ class _FormW4PageState extends State { backgroundColor: UiColors.background, body: Center( child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(UiConstants.space6), child: Container( - padding: const EdgeInsets.all(32), + padding: const EdgeInsets.all(UiConstants.space8), decoration: BoxDecoration( color: UiColors.bgPopup, borderRadius: BorderRadius.circular(24), @@ -178,40 +181,40 @@ class _FormW4PageState extends State { Container( width: 64, height: 64, - decoration: const BoxDecoration( - color: Color(0xFFDCFCE7), + decoration: BoxDecoration( + color: UiColors.tagSuccess, shape: BoxShape.circle, ), child: const Icon( UiIcons.success, - color: Color(0xFF16A34A), + color: UiColors.textSuccess, size: 32, ), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Text( 'Form W-4 Submitted!', - style: UiTypography.headline4m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline4m.textPrimary, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( 'Your withholding certificate has been submitted to your employer.', textAlign: TextAlign.center, - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Modular.to.pop(true), style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, - foregroundColor: UiColors.bgPopup, - padding: const EdgeInsets.symmetric(vertical: 16), + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), elevation: 0, ), @@ -229,7 +232,12 @@ class _FormW4PageState extends State { Widget _buildHeader(BuildContext context, FormW4State state) { return Container( color: UiColors.primary, - padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20), + padding: const EdgeInsets.only( + top: 60, + bottom: UiConstants.space6, + left: UiConstants.space5, + right: UiConstants.space5, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -239,31 +247,34 @@ class _FormW4PageState extends State { onTap: () => Modular.to.pop(), child: const Icon( UiIcons.arrowLeft, - color: UiColors.bgPopup, + color: UiColors.white, size: 24, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Form W-4', - style: UiTypography.headline4m.copyWith( - color: UiColors.bgPopup, - ), + style: UiTypography.headline4m.white, ), Text( 'Employee\'s Withholding Certificate', - style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), + style: UiTypography.body3r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), + ), ), ], ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Row( - children: _steps.asMap().entries.map((MapEntry> entry) { + children: _steps + .asMap() + .entries + .map((MapEntry> entry) { final int idx = entry.key; final bool isLast = idx == _steps.length - 1; return Expanded( @@ -274,8 +285,8 @@ class _FormW4PageState extends State { height: 4, decoration: BoxDecoration( color: idx <= state.currentStep - ? UiColors.bgPopup - : UiColors.bgPopup.withOpacity(0.3), + ? UiColors.white + : UiColors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(2), ), ), @@ -286,18 +297,19 @@ class _FormW4PageState extends State { ); }).toList(), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Step ${state.currentStep + 1} of ${_steps.length}', - style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), + style: UiTypography.body3r.copyWith( + color: UiColors.white.withValues(alpha: 0.7), + ), ), Text( _steps[state.currentStep]['title']!, - style: UiTypography.body3m.copyWith( - color: UiColors.bgPopup, + style: UiTypography.body3m.white.copyWith( fontWeight: FontWeight.w500, ), ), @@ -339,8 +351,7 @@ class _FormW4PageState extends State { children: [ Text( label, - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, + style: UiTypography.body3m.textSecondary.copyWith( fontWeight: FontWeight.w500, ), ), @@ -352,26 +363,26 @@ class _FormW4PageState extends State { ), onChanged: onChanged, keyboardType: keyboardType, - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, decoration: InputDecoration( hintText: placeholder, - hintStyle: TextStyle(color: Colors.grey[400]), + hintStyle: const TextStyle(color: UiColors.textPlaceholder), filled: true, fillColor: UiColors.bgPopup, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + horizontal: UiConstants.space4, + vertical: UiConstants.space4, ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.primary), ), ), @@ -438,25 +449,25 @@ class _FormW4PageState extends State { return Column( children: [ Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(12), + color: UiColors.tagActive, + borderRadius: UiConstants.radiusLg, ), child: Row( - children: const [ - Icon(UiIcons.info, color: Color(0xFF2563EB), size: 20), - SizedBox(width: 12), + children: [ + const Icon(UiIcons.info, color: UiColors.primary, size: 20), + const SizedBox(width: UiConstants.space3), Expanded( child: Text( 'Your filing status determines your standard deduction and tax rates.', - style: TextStyle(fontSize: 14, color: Color(0xFF1D4ED8)), + style: UiTypography.body2r.textPrimary, ), ), ], ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), _buildRadioOption( context, state, @@ -484,15 +495,21 @@ class _FormW4PageState extends State { ); } - Widget _buildRadioOption(BuildContext context, FormW4State state, String value, String label, String? subLabel) { + Widget _buildRadioOption( + BuildContext context, + FormW4State state, + String value, + String label, + String? subLabel, + ) { final bool isSelected = state.filingStatus == value; return GestureDetector( onTap: () => context.read().filingStatusChanged(value), child: Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, border: Border.all( color: isSelected ? UiColors.primary : UiColors.border, width: isSelected ? 2 : 1, @@ -508,29 +525,25 @@ class _FormW4PageState extends State { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: isSelected ? UiColors.primary : Colors.grey, + color: isSelected ? UiColors.primary : UiColors.border, width: isSelected ? 6 : 2, ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), if (subLabel != null) ...[ const SizedBox(height: 4), Text( subLabel, - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, ), ], ], @@ -546,36 +559,32 @@ class _FormW4PageState extends State { return Column( children: [ Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.amber[50], - borderRadius: BorderRadius.circular(12), + color: UiColors.accent.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, ), - child: const Row( + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( + const Icon( UiIcons.help, - color: Color(0xFFD97706), + color: UiColors.accent, size: 20, ), - SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'When to complete this step?', - style: TextStyle( - fontWeight: FontWeight.w600, - color: Color(0xFF92400E), - fontSize: 14, - ), + style: UiTypography.body2m.accent, ), - SizedBox(height: 4), + const SizedBox(height: 4), Text( 'Complete this step only if you hold more than one job at a time, or are married filing jointly and your spouse also works.', - style: TextStyle(fontSize: 12, color: Color(0xFFB45309)), + style: UiTypography.body3r.accent, ), ], ), @@ -583,18 +592,17 @@ class _FormW4PageState extends State { ], ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), GestureDetector( - onTap: () => context.read().multipleJobsChanged(!state.multipleJobs), + onTap: () => + context.read().multipleJobsChanged(!state.multipleJobs), child: Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, border: Border.all( - color: state.multipleJobs - ? UiColors.primary - : UiColors.border, + color: state.multipleJobs ? UiColors.primary : UiColors.border, ), ), child: Row( @@ -604,20 +612,16 @@ class _FormW4PageState extends State { width: 24, height: 24, decoration: BoxDecoration( - color: state.multipleJobs - ? UiColors.primary - : UiColors.bgPopup, + color: state.multipleJobs ? UiColors.primary : UiColors.bgPopup, borderRadius: BorderRadius.circular(6), border: Border.all( - color: state.multipleJobs - ? UiColors.primary - : Colors.grey, + color: state.multipleJobs ? UiColors.primary : UiColors.border, ), ), child: state.multipleJobs ? const Icon( UiIcons.check, - color: UiColors.bgPopup, + color: UiColors.white, size: 16, ) : null, @@ -629,16 +633,12 @@ class _FormW4PageState extends State { children: [ Text( 'I have multiple jobs or my spouse works', - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), const SizedBox(height: 4), Text( 'Check this box if there are only two jobs total', - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, ), ], ), @@ -651,7 +651,7 @@ class _FormW4PageState extends State { Text( 'If this does not apply, you can continue to the next step', textAlign: TextAlign.center, - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body3r.textSecondary, ), ], ); @@ -661,30 +661,30 @@ class _FormW4PageState extends State { return Column( children: [ Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.blue[50], // Same note about blue migration - borderRadius: BorderRadius.circular(12), + color: UiColors.tagActive, + borderRadius: UiConstants.radiusLg, ), - child: const Row( + child: Row( children: [ - Icon(UiIcons.info, color: Color(0xFF2563EB), size: 20), - SizedBox(width: 12), + const Icon(UiIcons.info, color: UiColors.primary, size: 20), + const SizedBox(width: UiConstants.space3), Expanded( child: Text( 'If your total income will be \$200,000 or less (\$400,000 if married filing jointly), you may claim credits for dependents.', - style: TextStyle(fontSize: 14, color: Color(0xFF1D4ED8)), + style: UiTypography.body2r.textPrimary, ), ), ], ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(16), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Column( @@ -715,10 +715,10 @@ class _FormW4PageState extends State { if (_totalCredits(state) > 0) ...[ const SizedBox(height: 16), Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: const Color(0xFFDCFCE7), - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -770,16 +770,12 @@ class _FormW4PageState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFDCFCE7), - borderRadius: BorderRadius.circular(12), + color: UiColors.tagSuccess, + borderRadius: UiConstants.radiusLg, ), child: Text( badge, - style: const TextStyle( - fontSize: 10, - color: Color(0xFF15803D), - fontWeight: FontWeight.bold, - ), + style: UiTypography.footnote2b.textSuccess, ), ), ], @@ -834,7 +830,7 @@ class _FormW4PageState extends State { children: [ Text( 'These adjustments are optional. You can skip them if they don\'t apply.', - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), const SizedBox(height: 24), _buildTextField( @@ -848,7 +844,7 @@ class _FormW4PageState extends State { padding: const EdgeInsets.only(top: 4, bottom: 16), child: Text( 'Include interest, dividends, retirement income', - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body3r.textSecondary, ), ), @@ -863,7 +859,7 @@ class _FormW4PageState extends State { padding: const EdgeInsets.only(top: 4, bottom: 16), child: Text( 'If you expect to claim deductions other than the standard deduction', - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body3r.textSecondary, ), ), @@ -878,7 +874,7 @@ class _FormW4PageState extends State { padding: const EdgeInsets.only(top: 4, bottom: 16), child: Text( 'Any additional tax you want withheld each pay period', - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body3r.textSecondary, ), ), ], @@ -890,10 +886,10 @@ class _FormW4PageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Column( @@ -927,22 +923,20 @@ class _FormW4PageState extends State { ), const SizedBox(height: 24), Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.amber[50], - borderRadius: BorderRadius.circular(12), + color: UiColors.accent.withValues(alpha: 0.1), + borderRadius: UiConstants.radiusLg, ), - child: const Text( + child: Text( 'Under penalties of perjury, I declare that this certificate, to the best of my knowledge and belief, is true, correct, and complete.', - style: TextStyle(fontSize: 12, color: Color(0xFFB45309)), + style: UiTypography.body3r.textWarning.copyWith(fontSize: 12), ), ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), Text( 'Signature (type your full name) *', - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3m.textSecondary, ), const SizedBox(height: 6), TextField( @@ -950,44 +944,46 @@ class _FormW4PageState extends State { ..selection = TextSelection.fromPosition( TextPosition(offset: state.signature.length), ), - onChanged: (String val) => context.read().signatureChanged(val), + onChanged: (String val) => + context.read().signatureChanged(val), decoration: InputDecoration( hintText: 'Type your full name', filled: true, fillColor: UiColors.bgPopup, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + horizontal: UiConstants.space4, + vertical: UiConstants.space4, ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.border), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, borderSide: const BorderSide(color: UiColors.primary), ), ), style: const TextStyle(fontFamily: 'Cursive', fontSize: 18), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Text( 'Date', - style: UiTypography.body3m.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3m.textSecondary, ), const SizedBox(height: 6), Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space4, + ), decoration: BoxDecoration( - color: const Color(0xFFF3F4F6), - borderRadius: BorderRadius.circular(12), + color: UiColors.bgSecondary, + borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), ), child: Text( @@ -1007,7 +1003,7 @@ class _FormW4PageState extends State { children: [ Text( label, - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), Text( value, @@ -1035,7 +1031,7 @@ class _FormW4PageState extends State { Widget _buildFooter(BuildContext context, FormW4State state) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: const BoxDecoration( color: UiColors.bgPopup, border: Border(top: BorderSide(color: UiColors.border)), @@ -1046,24 +1042,30 @@ class _FormW4PageState extends State { if (state.currentStep > 0) Expanded( child: Padding( - padding: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.only(right: UiConstants.space3), child: OutlinedButton( onPressed: () => _handleBack(context), style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), side: const BorderSide(color: UiColors.border), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(UiIcons.arrowLeft, size: 16, color: UiColors.textPrimary), + const Icon( + UiIcons.arrowLeft, + size: 16, + color: UiColors.textPrimary, + ), const SizedBox(width: 8), Text( 'Back', - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, ), ], ), @@ -1073,16 +1075,20 @@ class _FormW4PageState extends State { Expanded( flex: 2, child: ElevatedButton( - onPressed: (_canProceed(state) && state.status != FormW4Status.submitting) + onPressed: ( + _canProceed(state) && + state.status != FormW4Status.submitting) ? () => _handleNext(context, state.currentStep) : null, style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, - disabledBackgroundColor: Colors.grey[300], - foregroundColor: UiColors.bgPopup, - padding: const EdgeInsets.symmetric(vertical: 16), + disabledBackgroundColor: UiColors.bgSecondary, + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: UiConstants.radiusLg, ), elevation: 0, ), @@ -1091,7 +1097,7 @@ class _FormW4PageState extends State { width: 20, height: 20, child: CircularProgressIndicator( - color: UiColors.bgPopup, + color: UiColors.white, strokeWidth: 2, ), ) @@ -1105,7 +1111,7 @@ class _FormW4PageState extends State { ), if (state.currentStep < _steps.length - 1) ...[ const SizedBox(width: 8), - const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup), + const Icon(UiIcons.arrowRight, size: 16, color: UiColors.white), ], ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/tax_forms_page.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/tax_forms_page.dart index 826518eb..b1d6c6ac 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/tax_forms_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/tax_forms/lib/src/presentation/pages/tax_forms_page.dart @@ -22,7 +22,7 @@ class TaxFormsPage extends StatelessWidget { ), title: Text( 'Tax Documents', - style: UiTypography.headline3m.copyWith(color: UiColors.bgPopup), + style: UiTypography.headline3m.textSecondary, ), bottom: PreferredSize( preferredSize: const Size.fromHeight(24), @@ -38,7 +38,7 @@ class TaxFormsPage extends StatelessWidget { child: Text( 'Complete required forms to start working', style: UiTypography.body3r.copyWith( - color: UiColors.bgPopup.withOpacity(0.8), + color: UiColors.primaryForeground.withValues(alpha: 0.8), ), ), ), @@ -120,15 +120,11 @@ class TaxFormsPage extends StatelessWidget { children: [ Text( 'Document Progress', - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), Text( '$completedCount/$totalCount', - style: UiTypography.body2m.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body2m.textSecondary, ), ], ), @@ -180,7 +176,7 @@ class TaxFormsPage extends StatelessWidget { width: 48, height: 48, decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.1), + color: UiColors.primary.withValues(alpha: 0.1), borderRadius: UiConstants.radiusLg, ), child: Center(child: Text(icon, style: UiTypography.headline1m)), @@ -195,9 +191,7 @@ class TaxFormsPage extends StatelessWidget { children: [ Text( form.title, - style: UiTypography.headline4m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline4m.textPrimary, ), _buildStatusBadge(form.status), ], @@ -205,17 +199,14 @@ class TaxFormsPage extends StatelessWidget { const SizedBox(height: UiConstants.space1), Text( form.subtitle ?? '', - style: UiTypography.body2m.copyWith( + style: UiTypography.body2m.textSecondary.copyWith( fontWeight: FontWeight.w500, - color: UiColors.textSecondary, ), ), const SizedBox(height: UiConstants.space1), Text( form.description ?? '', - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, ), ], ), @@ -256,9 +247,7 @@ class TaxFormsPage extends StatelessWidget { const SizedBox(width: UiConstants.space1), Text( 'Completed', - style: UiTypography.footnote2b.copyWith( - color: UiColors.textSuccess, - ), + style: UiTypography.footnote2b.textSuccess, ), ], ), @@ -280,9 +269,7 @@ class TaxFormsPage extends StatelessWidget { const SizedBox(width: UiConstants.space1), Text( 'In Progress', - style: UiTypography.footnote2b.copyWith( - color: UiColors.textWarning, - ), + style: UiTypography.footnote2b.textWarning, ), ], ), @@ -299,9 +286,7 @@ class TaxFormsPage extends StatelessWidget { ), child: Text( 'Not Started', - style: UiTypography.footnote2b.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote2b.textSecondary, ), ); } @@ -325,16 +310,12 @@ class TaxFormsPage extends StatelessWidget { children: [ Text( 'Why are these needed?', - style: UiTypography.headline4m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline4m.textPrimary, ), const SizedBox(height: UiConstants.space1), Text( 'I-9 and W-4 forms are required by federal law to verify your employment eligibility and set up correct tax withholding.', - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/pages/bank_account_page.dart b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/pages/bank_account_page.dart index d3900e2b..a3fcb942 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/pages/bank_account_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/pages/bank_account_page.dart @@ -37,7 +37,7 @@ class BankAccountPage extends StatelessWidget { ), title: Text( strings.title, - style: UiTypography.headline3m.copyWith(color: UiColors.textPrimary), + style: UiTypography.headline3m.textPrimary, ), bottom: PreferredSize( preferredSize: const Size.fromHeight(1.0), @@ -165,7 +165,7 @@ class BankAccountPage extends StatelessWidget { return Container( padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.08), + color: UiColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: Row( @@ -179,15 +179,12 @@ class BankAccountPage extends StatelessWidget { children: [ Text( strings.secure_title, - style: UiTypography.body2r.copyWith( // Was body2 - fontWeight: FontWeight.w500, - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), const SizedBox(height: 2), Text( strings.secure_subtitle, - style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), // Was bodySmall + style: UiTypography.body3r.textSecondary, ), ], ), @@ -221,7 +218,7 @@ class BankAccountPage extends StatelessWidget { width: 48, height: 48, decoration: BoxDecoration( - color: primaryColor.withOpacity(0.1), + color: primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: const Center( @@ -238,10 +235,7 @@ class BankAccountPage extends StatelessWidget { children: [ Text( account.bankName, - style: UiTypography.body2r.copyWith( // Was body2 - fontWeight: FontWeight.w500, - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), Text( strings.account_ending( @@ -249,9 +243,7 @@ class BankAccountPage extends StatelessWidget { ? account.last4! : '----', ), - style: UiTypography.body2r.copyWith( // Was body2 - color: UiColors.textSecondary, - ), + style: UiTypography.body2r.textSecondary, ), ], ), @@ -261,7 +253,7 @@ class BankAccountPage extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: primaryColor.withOpacity(0.15), + color: primaryColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(20), ), child: Row( @@ -270,10 +262,7 @@ class BankAccountPage extends StatelessWidget { const SizedBox(width: 4), Text( strings.primary, - style: UiTypography.body3r.copyWith( // Was bodySmall - fontWeight: FontWeight.w500, - color: primaryColor, - ), + style: UiTypography.body3m.primary, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/widgets/add_account_form.dart b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/widgets/add_account_form.dart index a7ad00c9..4858511d 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/widgets/add_account_form.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/staff_bank_account/lib/src/presentation/widgets/add_account_form.dart @@ -43,7 +43,7 @@ class _AddAccountFormState extends State { children: [ Text( widget.strings.add_new_account, - style: UiTypography.headline4m.copyWith(color: UiColors.textPrimary), // Was header4 + style: UiTypography.headline4m.textPrimary, // Was header4 ), const SizedBox(height: UiConstants.space4), UiTextField( @@ -71,8 +71,7 @@ class _AddAccountFormState extends State { padding: const EdgeInsets.only(bottom: UiConstants.space2), child: Text( widget.strings.account_type, - style: UiTypography.body2r.copyWith( // Was body2 - color: UiColors.textSecondary, fontWeight: FontWeight.w500), + style: UiTypography.body2m.textSecondary, ), ), Row( @@ -122,7 +121,7 @@ class _AddAccountFormState extends State { padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: isSelected - ? UiColors.primary.withOpacity(0.05) + ? UiColors.primary.withValues(alpha: 0.05) : UiColors.bgPopup, // Was surface borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all( @@ -133,8 +132,7 @@ class _AddAccountFormState extends State { child: Center( child: Text( label, - style: UiTypography.body2r.copyWith( // Was body2 - fontWeight: FontWeight.w600, + style: UiTypography.body2b.copyWith( color: isSelected ? UiColors.primary : UiColors.textSecondary, ), ), diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart index e5555575..820073c0 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart @@ -41,9 +41,7 @@ class _TimeCardPageState extends State { ), title: Text( t.staff_time_card.title, - style: UiTypography.headline4m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline4m.textPrimary, ), bottom: PreferredSize( preferredSize: const Size.fromHeight(1.0), diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart index 4e8d7351..70f707e2 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart @@ -19,22 +19,22 @@ class TimesheetCard extends StatelessWidget { switch (status) { case TimeCardStatus.approved: - statusBg = UiColors.textSuccess.withOpacity(0.12); + statusBg = UiColors.tagSuccess; statusColor = UiColors.textSuccess; statusText = t.staff_time_card.status.approved; break; case TimeCardStatus.disputed: - statusBg = UiColors.destructive.withOpacity(0.12); + statusBg = UiColors.destructive.withValues(alpha: 0.12); statusColor = UiColors.destructive; statusText = t.staff_time_card.status.disputed; break; case TimeCardStatus.paid: - statusBg = UiColors.primary.withOpacity(0.12); + statusBg = UiColors.primary.withValues(alpha: 0.12); statusColor = UiColors.primary; statusText = t.staff_time_card.status.paid; break; case TimeCardStatus.pending: - statusBg = UiColors.textWarning.withOpacity(0.12); + statusBg = UiColors.tagPending; statusColor = UiColors.textWarning; statusText = t.staff_time_card.status.pending; break; @@ -61,15 +61,11 @@ class TimesheetCard extends StatelessWidget { children: [ Text( timesheet.shiftTitle, - style: UiTypography.body1m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body1m.textPrimary, ), Text( timesheet.clientName, - style: UiTypography.body2r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body2r.textSecondary, ), ], ), @@ -116,13 +112,11 @@ class TimesheetCard extends StatelessWidget { children: [ Text( '${timesheet.totalHours.toStringAsFixed(1)} ${t.staff_time_card.hours} @ \$${timesheet.hourlyRate.toStringAsFixed(2)}${t.staff_time_card.per_hr}', - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), Text( '\$${timesheet.totalPay.toStringAsFixed(2)}', - style: UiTypography.title2b.copyWith( - color: UiColors.primary, - ), + style: UiTypography.title2b.primary, ), ], ), @@ -163,7 +157,7 @@ class _IconText extends StatelessWidget { const SizedBox(width: UiConstants.space1), Text( text, - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), ], ); diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_page.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_page.dart index 776f5f77..44cde53f 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_page.dart @@ -32,9 +32,7 @@ class AttirePage extends StatelessWidget { ), title: Text( t.staff_profile_attire.title, - style: UiTypography.headline3m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline3m.textPrimary, ), bottom: PreferredSize( preferredSize: const Size.fromHeight(1.0), diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_info_card.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_info_card.dart index 656d1626..92606c1c 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_info_card.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_info_card.dart @@ -11,8 +11,8 @@ class AttireInfoCard extends StatelessWidget { return Container( padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.08), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + color: UiColors.primary.withValues(alpha: 0.08), + borderRadius: UiConstants.radiusLg, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -26,16 +26,12 @@ class AttireInfoCard extends StatelessWidget { children: [ Text( t.staff_profile_attire.info_card.title, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, ), const SizedBox(height: 2), Text( t.staff_profile_attire.info_card.description, - style: UiTypography.body2r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body2r.textSecondary, ), ], ), diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart index d96f5d4c..c64ca751 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart @@ -24,12 +24,12 @@ class EmergencyContactScreen extends StatelessWidget { appBar: AppBar( elevation: 0, leading: IconButton( - icon: Icon(UiIcons.chevronLeft, color: UiColors.textSecondary), + icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary), onPressed: () => Modular.to.pop(), ), title: Text( 'Emergency Contact', - style: UiTypography.title1m.copyWith(color: UiColors.textPrimary), + style: UiTypography.title1m.textPrimary, ), bottom: PreferredSize( preferredSize: const Size.fromHeight(1.0), @@ -62,11 +62,11 @@ class EmergencyContactScreen extends StatelessWidget { children: [ Expanded( child: SingleChildScrollView( - padding: EdgeInsets.all(UiConstants.space6), + padding: const EdgeInsets.all(UiConstants.space6), child: Column( children: [ const EmergencyContactInfoBanner(), - SizedBox(height: UiConstants.space6), + const SizedBox(height: UiConstants.space6), ...state.contacts.asMap().entries.map( (entry) => EmergencyContactFormItem( index: entry.key, @@ -75,7 +75,7 @@ class EmergencyContactScreen extends StatelessWidget { ), ), const EmergencyContactAddButton(), - SizedBox(height: UiConstants.space16), + const SizedBox(height: UiConstants.space16), ], ), ), diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_add_button.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_add_button.dart index 40f9b81e..769c709b 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_add_button.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_add_button.dart @@ -12,20 +12,20 @@ class EmergencyContactAddButton extends StatelessWidget { child: TextButton.icon( onPressed: () => context.read().add(EmergencyContactAdded()), - icon: Icon(UiIcons.add, size: 20.0), + icon: const Icon(UiIcons.add, size: 20.0), label: Text( 'Add Another Contact', style: UiTypography.title2b, ), style: TextButton.styleFrom( foregroundColor: UiColors.primary, - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: UiConstants.space6, vertical: UiConstants.space3, ), shape: RoundedRectangleBorder( borderRadius: UiConstants.radiusFull, - side: BorderSide(color: UiColors.primary), + side: const BorderSide(color: UiColors.primary), ), ), ), diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_form_item.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_form_item.dart index 8dbf2063..4117a5d3 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_form_item.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_form_item.dart @@ -19,8 +19,8 @@ class EmergencyContactFormItem extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - margin: EdgeInsets.only(bottom: UiConstants.space4), - padding: EdgeInsets.all(UiConstants.space4), + margin: const EdgeInsets.only(bottom: UiConstants.space4), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( color: UiColors.bgPopup, borderRadius: UiConstants.radiusLg, @@ -30,33 +30,27 @@ class EmergencyContactFormItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context), - SizedBox(height: UiConstants.space4), + const SizedBox(height: UiConstants.space4), _buildLabel('Full Name'), _buildTextField( initialValue: contact.name, hint: 'Contact name', icon: UiIcons.user, onChanged: (val) => context.read().add( - EmergencyContactUpdated( - index, - contact.copyWith(name: val), - ), - ), + EmergencyContactUpdated(index, contact.copyWith(name: val)), + ), ), - SizedBox(height: UiConstants.space4), + const SizedBox(height: UiConstants.space4), _buildLabel('Phone Number'), _buildTextField( initialValue: contact.phone, hint: '+1 (555) 000-0000', icon: UiIcons.phone, onChanged: (val) => context.read().add( - EmergencyContactUpdated( - index, - contact.copyWith(phone: val), - ), - ), + EmergencyContactUpdated(index, contact.copyWith(phone: val)), + ), ), - SizedBox(height: UiConstants.space4), + const SizedBox(height: UiConstants.space4), _buildLabel('Relationship'), _buildDropdown( context, @@ -65,11 +59,11 @@ class EmergencyContactFormItem extends StatelessWidget { onChanged: (val) { if (val != null) { context.read().add( - EmergencyContactUpdated( - index, - contact.copyWith(relationship: val), - ), - ); + EmergencyContactUpdated( + index, + contact.copyWith(relationship: val), + ), + ); } }, ), @@ -85,7 +79,7 @@ class EmergencyContactFormItem extends StatelessWidget { required ValueChanged onChanged, }) { return Container( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: UiConstants.space4, vertical: UiConstants.space2, ), @@ -99,13 +93,13 @@ class EmergencyContactFormItem extends StatelessWidget { value: value, isExpanded: true, dropdownColor: UiColors.bgPopup, - icon: Icon(UiIcons.chevronDown, color: UiColors.iconSecondary), + icon: const Icon(UiIcons.chevronDown, color: UiColors.iconSecondary), items: items.map((type) { return DropdownMenuItem( value: type, child: Text( _formatRelationship(type), - style: UiTypography.body1r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body1r.textPrimary, ), ); }).toList(), @@ -116,11 +110,15 @@ class EmergencyContactFormItem extends StatelessWidget { } String _formatRelationship(RelationshipType type) { - switch(type) { - case RelationshipType.family: return 'Family'; - case RelationshipType.spouse: return 'Spouse'; - case RelationshipType.friend: return 'Friend'; - case RelationshipType.other: return 'Other'; + switch (type) { + case RelationshipType.family: + return 'Family'; + case RelationshipType.spouse: + return 'Spouse'; + case RelationshipType.friend: + return 'Friend'; + case RelationshipType.other: + return 'Other'; } } @@ -128,22 +126,17 @@ class EmergencyContactFormItem extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Contact ${index + 1}', - style: UiTypography.title2m.copyWith( - color: UiColors.textPrimary, - ), - ), + Text('Contact ${index + 1}', style: UiTypography.title2m.textPrimary), if (totalContacts > 1) IconButton( - icon: Icon( + icon: const Icon( UiIcons.delete, color: UiColors.textError, size: 20.0, ), - onPressed: () => context - .read() - .add(EmergencyContactRemoved(index)), + onPressed: () => context.read().add( + EmergencyContactRemoved(index), + ), ), ], ); @@ -151,13 +144,8 @@ class EmergencyContactFormItem extends StatelessWidget { Widget _buildLabel(String label) { return Padding( - padding: EdgeInsets.only(bottom: UiConstants.space2), - child: Text( - label, - style: UiTypography.body2m.copyWith( - color: UiColors.textSecondary, - ), - ), + padding: const EdgeInsets.only(bottom: UiConstants.space2), + child: Text(label, style: UiTypography.body2m.textSecondary), ); } @@ -169,16 +157,16 @@ class EmergencyContactFormItem extends StatelessWidget { }) { return TextFormField( initialValue: initialValue, - style: UiTypography.body1r.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body1r.textPrimary, decoration: InputDecoration( hintText: hint, - hintStyle: TextStyle(color: UiColors.textPlaceholder), + hintStyle: const TextStyle(color: UiColors.textPlaceholder), prefixIcon: Icon(icon, color: UiColors.textSecondary, size: 20.0), filled: true, fillColor: UiColors.bgPopup, - contentPadding: EdgeInsets.symmetric(vertical: UiConstants.space4), + contentPadding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + ), border: OutlineInputBorder( borderRadius: UiConstants.radiusLg, borderSide: BorderSide(color: UiColors.border), @@ -196,4 +184,3 @@ class EmergencyContactFormItem extends StatelessWidget { ); } } - diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_info_banner.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_info_banner.dart index 975529be..e8d26179 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_info_banner.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_info_banner.dart @@ -7,14 +7,14 @@ class EmergencyContactInfoBanner extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.all(UiConstants.space4), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: UiColors.accent.withOpacity(0.2), - borderRadius: BorderRadius.circular(UiConstants.radiusBase), + color: UiColors.accent.withValues(alpha: 0.2), + borderRadius: UiConstants.radiusLg, ), child: Text( 'Please provide at least one emergency contact. This information will only be used in case of an emergency during your shifts.', - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, ), ); } diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_save_button.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_save_button.dart index e21e94f2..5f219cad 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_save_button.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_save_button.dart @@ -30,19 +30,18 @@ class EmergencyContactSaveButton extends StatelessWidget { builder: (context, state) { final isLoading = state.status == EmergencyContactStatus.saving; return Container( - padding: EdgeInsets.all(UiConstants.space4), - decoration: BoxDecoration( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: const BoxDecoration( color: UiColors.bgPopup, border: Border(top: BorderSide(color: UiColors.border)), ), child: SafeArea( child: UiButton.primary( fullWidth: true, - onPressed: state.isValid && !isLoading - ? () => _onSave(context) - : null, + onPressed: + state.isValid && !isLoading ? () => _onSave(context) : null, child: isLoading - ? SizedBox( + ? const SizedBox( height: 20.0, width: 20.0, child: CircularProgressIndicator( diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/presentation/pages/experience_page.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/presentation/pages/experience_page.dart index f826b943..80a5e077 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/presentation/pages/experience_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/experience/lib/src/presentation/pages/experience_page.dart @@ -80,16 +80,16 @@ class ExperiencePage extends StatelessWidget { children: [ Expanded( child: SingleChildScrollView( - padding: EdgeInsets.all(UiConstants.space5), + padding: const EdgeInsets.all(UiConstants.space5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ExperienceSectionTitle(title: i18n.industries_title), Text( i18n.industries_subtitle, - style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2m.textSecondary, ), - SizedBox(height: UiConstants.space3), + const SizedBox(height: UiConstants.space3), Wrap( spacing: UiConstants.space2, runSpacing: UiConstants.space2, @@ -97,23 +97,26 @@ class ExperiencePage extends StatelessWidget { .map( (i) => UiChip( label: _getIndustryLabel(i18n.industries, i), - isSelected: state.selectedIndustries.contains(i), - onTap: () => BlocProvider.of(context) - .add(ExperienceIndustryToggled(i)), - variant: state.selectedIndustries.contains(i) - ? UiChipVariant.primary - : UiChipVariant.secondary, + isSelected: + state.selectedIndustries.contains(i), + onTap: () => + BlocProvider.of(context) + .add(ExperienceIndustryToggled(i)), + variant: + state.selectedIndustries.contains(i) + ? UiChipVariant.primary + : UiChipVariant.secondary, ), ) .toList(), ), - SizedBox(height: UiConstants.space6), + const SizedBox(height: UiConstants.space6), ExperienceSectionTitle(title: i18n.skills_title), Text( i18n.skills_subtitle, - style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2m.textSecondary, ), - SizedBox(height: UiConstants.space3), + const SizedBox(height: UiConstants.space3), Wrap( spacing: UiConstants.space2, runSpacing: UiConstants.space2, @@ -121,19 +124,22 @@ class ExperiencePage extends StatelessWidget { .map( (s) => UiChip( label: _getSkillLabel(i18n.skills, s), - isSelected: state.selectedSkills.contains(s.value), - onTap: () => BlocProvider.of(context) - .add(ExperienceSkillToggled(s.value)), - variant: state.selectedSkills.contains(s.value) - ? UiChipVariant.primary - : UiChipVariant.secondary, + isSelected: + state.selectedSkills.contains(s.value), + onTap: () => + BlocProvider.of(context) + .add(ExperienceSkillToggled(s.value)), + variant: + state.selectedSkills.contains(s.value) + ? UiChipVariant.primary + : UiChipVariant.secondary, ), ) .toList(), ), ], ), - ), + ), ), _buildSaveButton(context, state, i18n), ], @@ -155,9 +161,9 @@ class ExperiencePage extends StatelessWidget { children: [ Text( i18n.custom_skills_title, - style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2m.textSecondary, ), - SizedBox(height: UiConstants.space2), + const SizedBox(height: UiConstants.space2), Wrap( spacing: UiConstants.space2, runSpacing: UiConstants.space2, @@ -172,10 +178,14 @@ class ExperiencePage extends StatelessWidget { ); } - Widget _buildSaveButton(BuildContext context, ExperienceState state, dynamic i18n) { + Widget _buildSaveButton( + BuildContext context, + ExperienceState state, + dynamic i18n, + ) { return Container( - padding: EdgeInsets.all(UiConstants.space4), - decoration: BoxDecoration( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: const BoxDecoration( color: UiColors.bgPopup, border: Border(top: BorderSide(color: UiColors.border)), ), @@ -183,16 +193,21 @@ class ExperiencePage extends StatelessWidget { child: UiButton.primary( onPressed: state.status == ExperienceStatus.loading ? null - : () => BlocProvider.of(context).add(ExperienceSubmitted()), + : () => BlocProvider.of(context) + .add(ExperienceSubmitted()), fullWidth: true, - text: state.status == ExperienceStatus.loading ? null : i18n.save_button, + text: state.status == ExperienceStatus.loading + ? null + : i18n.save_button, child: state.status == ExperienceStatus.loading - ? SizedBox( + ? const SizedBox( height: 20.0, width: 20.0, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(UiColors.white), // UiColors.primaryForeground is white mostly + valueColor: AlwaysStoppedAnimation( + UiColors.white, + ), // UiColors.primaryForeground is white mostly ), ) : null, diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/pages/personal_info_page.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/pages/personal_info_page.dart index 78f27ab7..c1700848 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/pages/personal_info_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/pages/personal_info_page.dart @@ -1,4 +1,3 @@ - import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; @@ -56,13 +55,16 @@ class PersonalInfoPage extends StatelessWidget { backgroundColor: UiColors.bgPopup, elevation: 0, leading: IconButton( - icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary), + icon: const Icon( + UiIcons.chevronLeft, + color: UiColors.textSecondary, + ), onPressed: () => Modular.to.pop(), tooltip: MaterialLocalizations.of(context).backButtonTooltip, ), title: Text( i18n.title, - style: UiTypography.title1m.copyWith(color: UiColors.textPrimary), + style: UiTypography.title1m.textPrimary, ), bottom: PreferredSize( preferredSize: const Size.fromHeight(1.0), @@ -86,9 +88,7 @@ class PersonalInfoPage extends StatelessWidget { return Center( child: Text( 'Failed to load personal information', - style: UiTypography.body1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body1r.textSecondary, ), ); } diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/personal_info_form.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/personal_info_form.dart index ee4e84b5..6ae1fc46 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/personal_info_form.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/personal_info_form.dart @@ -93,7 +93,7 @@ class _FieldLabel extends StatelessWidget { Widget build(BuildContext context) { return Text( text, - style: UiTypography.body2m.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2m.textPrimary, ); } } @@ -101,7 +101,6 @@ class _FieldLabel extends StatelessWidget { /// A read-only field widget for displaying non-editable information. /// A read-only field widget for displaying non-editable information. class _ReadOnlyField extends StatelessWidget { - const _ReadOnlyField({required this.value}); final String value; @@ -120,7 +119,7 @@ class _ReadOnlyField extends StatelessWidget { ), child: Text( value, - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, ), ); } @@ -129,7 +128,6 @@ class _ReadOnlyField extends StatelessWidget { /// An editable text field widget. /// An editable text field widget. class _EditableField extends StatelessWidget { - const _EditableField({ required this.controller, required this.hint, @@ -150,10 +148,10 @@ class _EditableField extends StatelessWidget { enabled: enabled, keyboardType: keyboardType, autofillHints: autofillHints, - style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), + style: UiTypography.body2r.textPrimary, decoration: InputDecoration( hintText: hint, - hintStyle: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + hintStyle: UiTypography.body2r.textSecondary, contentPadding: const EdgeInsets.symmetric( horizontal: UiConstants.space3, vertical: UiConstants.space3, diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/profile_photo_widget.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/profile_photo_widget.dart index dc681266..0abb3513 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/profile_photo_widget.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/presentation/widgets/profile_photo_widget.dart @@ -28,7 +28,8 @@ class ProfilePhotoWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final TranslationsStaffOnboardingPersonalInfoEn i18n = t.staff.onboarding.personal_info; + final TranslationsStaffOnboardingPersonalInfoEn i18n = + t.staff.onboarding.personal_info; return Column( children: [ @@ -41,7 +42,7 @@ class ProfilePhotoWidget extends StatelessWidget { height: 96, decoration: BoxDecoration( shape: BoxShape.circle, - color: UiColors.primary.withOpacity(0.1), + color: UiColors.primary.withValues(alpha: 0.1), ), child: photoUrl != null ? ClipOval( @@ -53,9 +54,7 @@ class ProfilePhotoWidget extends StatelessWidget { : Center( child: Text( fullName.isNotEmpty ? fullName[0].toUpperCase() : '?', - style: UiTypography.displayL.copyWith( - color: UiColors.primary, - ), + style: UiTypography.displayL.primary, ), ), ), @@ -71,7 +70,7 @@ class ProfilePhotoWidget extends StatelessWidget { border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: UiColors.textPrimary.withOpacity(0.1), + color: UiColors.textPrimary.withValues(alpha: 0.1), blurRadius: UiConstants.space1, offset: const Offset(0, 2), ), @@ -92,7 +91,7 @@ class ProfilePhotoWidget extends StatelessWidget { const SizedBox(height: UiConstants.space3), Text( i18n.change_photo_hint, - style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + style: UiTypography.body2r.textSecondary, ), ], ); diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart index b56b0c15..a1588633 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart @@ -176,7 +176,7 @@ class ShiftsRepositoryImpl final DateTime? endDt = _toDateTime(app.shiftRole.endTime); final DateTime? createdDt = _toDateTime(app.createdAt); - // Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED) + // Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED) final bool hasCheckIn = app.checkInTime != null; final bool hasCheckOut = app.checkOutTime != null; dc.ApplicationStatus? appStatus; @@ -187,7 +187,7 @@ class ShiftsRepositoryImpl ? 'completed' : hasCheckIn ? 'checked_in' - : _mapStatus(appStatus ?? dc.ApplicationStatus.ACCEPTED); + : _mapStatus(appStatus ?? dc.ApplicationStatus.CONFIRMED); shifts.add( Shift( id: app.shift.id, @@ -223,7 +223,6 @@ class ShiftsRepositoryImpl String _mapStatus(dc.ApplicationStatus status) { switch (status) { - case dc.ApplicationStatus.ACCEPTED: case dc.ApplicationStatus.CONFIRMED: return 'confirmed'; case dc.ApplicationStatus.PENDING: @@ -477,7 +476,7 @@ class ShiftsRepositoryImpl shiftId: shiftId, staffId: staffId, roleId: targetRoleId, - status: dc.ApplicationStatus.ACCEPTED, + status: dc.ApplicationStatus.CONFIRMED, origin: dc.ApplicationOrigin.STAFF, ) // TODO: this should be PENDING so a vendor can accept it. @@ -518,7 +517,7 @@ class ShiftsRepositoryImpl @override Future acceptShift(String shiftId) async { - await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED); + await _updateApplicationStatus(shiftId, dc.ApplicationStatus.CONFIRMED); } @override diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart index 1aa10435..6a8c1c43 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart @@ -281,15 +281,20 @@ class ShiftsBloc extends Bloc List _filterPastShifts(List shifts) { final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); return shifts.where((shift) { if (shift.date.isEmpty) return false; try { final shiftDate = DateTime.parse(shift.date); - return shiftDate.isAfter(now); + final dateOnly = DateTime( + shiftDate.year, + shiftDate.month, + shiftDate.day, + ); + return !dateOnly.isBefore(today); } catch (_) { return false; } }).toList(); } } - diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart index 2a9314ea..1b04a87c 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart @@ -64,10 +64,10 @@ class _ShiftDetailsPageState extends State { Widget _buildStatCard(IconData icon, String value, String label) { return Container( - padding: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), decoration: BoxDecoration( - color: const Color(0xFFF8FAFC), - borderRadius: BorderRadius.circular(16), + color: UiColors.background, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all(color: UiColors.border), ), child: Column( @@ -76,21 +76,19 @@ class _ShiftDetailsPageState extends State { width: 40, height: 40, decoration: const BoxDecoration( - color: Colors.white, + color: UiColors.white, shape: BoxShape.circle, ), child: Icon(icon, size: 20, color: UiColors.iconSecondary), ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Text( value, - style: UiTypography.title1m.copyWith(color: UiColors.textPrimary), + style: UiTypography.title1m.textPrimary, ), Text( label, - style: UiTypography.footnote2r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote2r.textSecondary, ), ], ), @@ -99,29 +97,21 @@ class _ShiftDetailsPageState extends State { Widget _buildTimeBox(String label, String time) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: const Color(0xFFF8FAFC), - borderRadius: BorderRadius.circular(16), + color: UiColors.background, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: Column( children: [ Text( label, - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: UiColors.textSecondary, - letterSpacing: 0.5, - ), + style: UiTypography.titleUppercase4b.textSecondary, ), - const SizedBox(height: 4), + const SizedBox(height: UiConstants.space1), Text( _formatTime(time), - style: UiTypography.display2m.copyWith( - fontSize: 20, - color: UiColors.textPrimary, - ), + style: UiTypography.headline2m.textPrimary, ), ], ), @@ -149,7 +139,7 @@ class _ShiftDetailsPageState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), - backgroundColor: UiColors.tagSuccess, + backgroundColor: UiColors.success, ), ); Modular.to.toShifts(selectedDate: state.shiftDate); @@ -158,7 +148,7 @@ class _ShiftDetailsPageState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), - backgroundColor: const Color(0xFFEF4444), + backgroundColor: UiColors.destructive, ), ); } @@ -203,7 +193,7 @@ class _ShiftDetailsPageState extends State { children: [ Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.all(UiConstants.space5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -211,16 +201,11 @@ class _ShiftDetailsPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "VENDOR", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: UiColors.textSecondary, - letterSpacing: 0.5, - ), + style: UiTypography.titleUppercase4b.textSecondary, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Row( children: [ SizedBox( @@ -229,7 +214,7 @@ class _ShiftDetailsPageState extends State { child: displayShift.logoUrl != null ? ClipRRect( borderRadius: BorderRadius.circular( - 6, + UiConstants.radiusMdValue, ), child: Image.network( displayShift.logoUrl!, @@ -244,33 +229,26 @@ class _ShiftDetailsPageState extends State { ), ), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Text( displayShift.clientName, - style: UiTypography.headline5m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline5m.textPrimary, ), ], ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Date Section Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "SHIFT DATE", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: UiColors.textSecondary, - letterSpacing: 0.5, - ), + style: UiTypography.titleUppercase4b.textSecondary, ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Row( children: [ const Icon( @@ -278,104 +256,44 @@ class _ShiftDetailsPageState extends State { size: 20, color: UiColors.primary, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Text( _formatDate(displayShift.date), - style: UiTypography.headline5m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.headline5m.textPrimary, ), ], ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), // Worker Capacity / Open Slots if ((displayShift.requiredSlots ?? 0) > 0) Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: const Color(0xFFF0FDF4), // green-50 - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: const Color(0xFFBBF7D0), - ), // green-200 + color: UiColors.success.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: Row( children: [ const Icon( - Icons.people_alt_outlined, - size: 20, - color: Color(0xFF15803D), - ), // green-700, using Material Icon as generic fallback - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "$openSlots spots remaining", - style: UiTypography.body2b.copyWith( - color: const Color(0xFF15803D), - ), - ), - Text( - "${displayShift.filledSlots ?? 0} filled out of ${displayShift.requiredSlots}", - style: UiTypography.body3r.copyWith( - color: const Color(0xFF166534), - ), - ), - ], - ), + UiIcons.users, + size: 16, + color: UiColors.success, ), - SizedBox( - width: 60, - child: LinearProgressIndicator( - value: (displayShift.requiredSlots! > 0) - ? (displayShift.filledSlots ?? 0) / - displayShift.requiredSlots! - : 0, - backgroundColor: Colors.white, - color: const Color(0xFF15803D), - minHeight: 6, - borderRadius: BorderRadius.circular(3), - ), + const SizedBox(width: UiConstants.space2), + Text( + "$openSlots slots remaining", + style: UiTypography.footnote1m.textSuccess, ), ], ), ), - const SizedBox(height: 24), - // Stats Grid - GridView.count( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - crossAxisCount: 3, - crossAxisSpacing: 12, - childAspectRatio: 0.72, - children: [ - _buildStatCard( - UiIcons.dollar, - "\$${estimatedTotal.toStringAsFixed(0)}", - "Total Pay", - ), - _buildStatCard( - UiIcons.dollar, - "\$${displayShift.hourlyRate.toInt()}", - "Per Hour", - ), - _buildStatCard( - UiIcons.clock, - "${duration.toInt()}h", - "Duration", - ), - ], - ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), - // Shift Timing + // Time Section Row( children: [ Expanded( @@ -384,7 +302,7 @@ class _ShiftDetailsPageState extends State { displayShift.startTime, ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space4), Expanded( child: _buildTimeBox( "END TIME", @@ -393,129 +311,142 @@ class _ShiftDetailsPageState extends State { ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space6), - // Location - Column( - crossAxisAlignment: CrossAxisAlignment.start, + // Quick Info Grid + Row( children: [ - const Text( - "LOCATION", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: UiColors.textSecondary, - letterSpacing: 0.5, + Expanded( + child: _buildStatCard( + UiIcons.dollar, + "\$${displayShift.hourlyRate.toStringAsFixed(0)}/hr", + "Base Rate", ), ), - const SizedBox(height: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - displayShift.location.isEmpty - ? "TBD" - : displayShift.location, - style: UiTypography.title1m.copyWith( - color: UiColors.textPrimary, - ), - ), - Text( - displayShift.location.isEmpty - ? "TBD" - : displayShift.locationAddress, - style: UiTypography.title1m.copyWith( - color: UiColors.textPrimary, - ), - ), - ], + const SizedBox(width: UiConstants.space4), + Expanded( + child: _buildStatCard( + UiIcons.clock, + "${duration.toInt()} hours", + "Duration", + ), + ), + const SizedBox(width: UiConstants.space4), + Expanded( + child: _buildStatCard( + UiIcons.wallet, + "\$${estimatedTotal.toStringAsFixed(0)}", + "Est. Total", + ), ), ], ), - const SizedBox(height: 24), + const SizedBox(height: UiConstants.space8), - // Additional Info - if (displayShift.description != null) ...[ - SizedBox( - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "ADDITIONAL INFO", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: UiColors.textSecondary, - letterSpacing: 0.5, - ), - ), - const SizedBox(height: 8), - Text( - displayShift.description!, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), - ), - ], + // Location Section + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "LOCATION", + style: UiTypography.titleUppercase4b.textSecondary, ), - ), - ], - const SizedBox(height: 20), - if (displayShift.status != 'confirmed' && - displayShift.hasApplied != true && - (displayShift.requiredSlots == null || - displayShift.filledSlots == null || - displayShift.filledSlots! < - displayShift.requiredSlots!)) - Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: () => _declineShift( - context, - displayShift!.id, - ), - style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFFEF4444), - side: const BorderSide( - color: Color(0xFFEF4444), - ), - padding: const EdgeInsets.symmetric( - vertical: 16, - ), - ), - child: const Text("Decline"), - ), + const SizedBox(height: UiConstants.space3), + Container( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + border: Border.all(color: UiColors.border), ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton( - onPressed: () => _bookShift( - context, - displayShift!, + child: Column( + children: [ + Row( + children: [ + const Icon( + UiIcons.mapPin, + color: UiColors.primary, + size: 20, + ), + const SizedBox(width: UiConstants.space3), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + displayShift.location, + style: UiTypography.body2b.textPrimary, + ), + Text( + displayShift.locationAddress, + style: UiTypography.body3r.textSecondary, + ), + ], + ), + ), + ], ), - style: ElevatedButton.styleFrom( - backgroundColor: const Color( - 0xFF10B981, + const SizedBox(height: UiConstants.space4), + const Divider(), + const SizedBox(height: UiConstants.space2), + TextButton.icon( + onPressed: () {}, + icon: const Icon( + UiIcons.arrowRight, + size: 16, ), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - vertical: 16, + label: const Text("Open in Maps"), + style: TextButton.styleFrom( + foregroundColor: UiColors.primary, + padding: EdgeInsets.zero, ), ), - child: const Text("Book Shift"), - ), + ], ), - ], - ), - SizedBox( - height: MediaQuery.of(context).padding.bottom + 10, + ), + ], ), + const SizedBox(height: UiConstants.space8), + + // Description / Instructions + if ((displayShift.description ?? '').isNotEmpty) ...[ + Text( + "JOB DESCRIPTION", + style: UiTypography.titleUppercase4b.textSecondary, + ), + const SizedBox(height: UiConstants.space2), + Text( + displayShift.description!, + style: UiTypography.body2r.textSecondary, + ), + const SizedBox(height: UiConstants.space8), + ], ], ), ), ), + // Bottom Action Bar + Container( + padding: EdgeInsets.fromLTRB( + UiConstants.space5, + UiConstants.space4, + UiConstants.space5, + MediaQuery.of(context).padding.bottom + UiConstants.space4, + ), + decoration: BoxDecoration( + color: UiColors.white, + border: Border(top: BorderSide(color: UiColors.border)), + boxShadow: [ + BoxShadow( + color: UiColors.black.withValues(alpha: 0.05), + blurRadius: 10, + offset: const Offset(0, -4), + ), + ], + ), + child: _buildBottomButton(displayShift, context), + ), ], ), ); @@ -552,7 +483,7 @@ class _ShiftDetailsPageState extends State { ); }, style: TextButton.styleFrom( - foregroundColor: const Color(0xFF10B981), + foregroundColor: UiColors.success, ), child: const Text('Book'), ), @@ -581,7 +512,7 @@ class _ShiftDetailsPageState extends State { ).add(DeclineShiftDetailsEvent(id)); }, style: TextButton.styleFrom( - foregroundColor: const Color(0xFFEF4444), + foregroundColor: UiColors.destructive, ), child: const Text('Decline'), ), @@ -608,29 +539,23 @@ class _ShiftDetailsPageState extends State { width: 36, child: CircularProgressIndicator(), ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), Text( shift.title, - style: UiTypography.body2b.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2b.textPrimary, textAlign: TextAlign.center, ), const SizedBox(height: 6), Text( '${_formatDate(shift.date)} • ${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}', - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, textAlign: TextAlign.center, ), if (shift.clientName.isNotEmpty) ...[ const SizedBox(height: 6), Text( shift.clientName, - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, textAlign: TextAlign.center, ), ], @@ -647,4 +572,150 @@ class _ShiftDetailsPageState extends State { Navigator.of(context, rootNavigator: true).pop(); _actionDialogOpen = false; } + + Widget _buildBottomButton(Shift shift, BuildContext context) { + final String status = shift.status ?? 'open'; + + if (status == 'confirmed') { + return Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => _openCancelDialog(context), + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.destructive, + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + elevation: 0, + ), + child: Text("CANCEL SHIFT", style: UiTypography.body2b.white), + ), + ), + const SizedBox(width: UiConstants.space4), + Expanded( + child: ElevatedButton( + onPressed: () => Modular.to.toClockIn(), + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.success, + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + elevation: 0, + ), + child: Text("CLOCK IN", style: UiTypography.body2b.white), + ), + ), + ], + ); + } + + if (status == 'pending') { + return Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => BlocProvider.of(context).add(DeclineShiftDetailsEvent(shift.id)), + style: OutlinedButton.styleFrom( + foregroundColor: UiColors.destructive, + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + side: const BorderSide(color: UiColors.destructive), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + ), + child: Text("DECLINE", style: UiTypography.body2b.textError), + ), + ), + const SizedBox(width: UiConstants.space4), + Expanded( + child: ElevatedButton( + onPressed: () => BlocProvider.of(context).add(BookShiftDetailsEvent(shift.id, roleId: shift.roleId)), + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + elevation: 0, + ), + child: Text("ACCEPT SHIFT", style: UiTypography.body2b.white), + ), + ), + ], + ); + } + + if (status == 'open' || status == 'available') { + return Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => _declineShift(context, shift.id), + style: OutlinedButton.styleFrom( + foregroundColor: UiColors.textSecondary, + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + side: const BorderSide(color: UiColors.border), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + ), + child: Text("DECLINE", style: UiTypography.body2b.textSecondary), + ), + ), + const SizedBox(width: UiConstants.space4), + Expanded( + child: ElevatedButton( + onPressed: () => _bookShift(context, shift), + style: ElevatedButton.styleFrom( + backgroundColor: UiColors.primary, + foregroundColor: UiColors.white, + padding: const EdgeInsets.symmetric(vertical: UiConstants.space4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiConstants.radiusBase), + ), + elevation: 0, + ), + child: Text("APPLY NOW", style: UiTypography.body2b.white), + ), + ), + ], + ); + } + + return const SizedBox(); + } + + void _openCancelDialog(BuildContext context) { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Cancel Shift'), + content: const Text('Are you sure you want to cancel this shift?'), + actions: [ + TextButton( + onPressed: () => Modular.to.pop(), + child: const Text('No'), + ), + TextButton( + onPressed: () { + Modular.to.pop(); + BlocProvider.of(context).add( + DeclineShiftDetailsEvent(widget.shiftId), + ); + }, + style: TextButton.styleFrom( + foregroundColor: UiColors.destructive, + ), + child: const Text('Yes, cancel it'), + ), + ], + ), + ); + } } diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart index f4a65338..fb32be0c 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart @@ -8,7 +8,6 @@ import '../blocs/shifts/shifts_bloc.dart'; import '../widgets/tabs/my_shifts_tab.dart'; import '../widgets/tabs/find_shifts_tab.dart'; import '../widgets/tabs/history_shifts_tab.dart'; -import '../styles/shifts_styles.dart'; class ShiftsPage extends StatefulWidget { final String? initialTab; @@ -119,29 +118,25 @@ class _ShiftsPageState extends State { // Note: Calendar logic moved to MyShiftsTab return Scaffold( - backgroundColor: AppColors.krowBackground, + backgroundColor: UiColors.background, body: Column( children: [ // Header (Blue) Container( - color: AppColors.krowBlue, + color: UiColors.primary, padding: EdgeInsets.fromLTRB( - 20, - MediaQuery.of(context).padding.top + 10, - 20, - 20, + UiConstants.space5, + MediaQuery.of(context).padding.top + UiConstants.space2, + UiConstants.space5, + UiConstants.space5, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - spacing: 16, + spacing: UiConstants.space4, children: [ Text( t.staff_shifts.title, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + style: UiTypography.display1b.white, ), // Tabs @@ -155,17 +150,16 @@ class _ShiftsPageState extends State { showCount: myShiftsLoaded, enabled: !blockTabsForFind, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), _buildTab( "find", t.staff_shifts.tabs.find_work, UiIcons.search, - availableJobs - .length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs. + availableJobs.length, showCount: availableLoaded, enabled: baseLoaded, ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), _buildTab( "history", t.staff_shifts.tabs.history, @@ -276,12 +270,15 @@ class _ShiftsPageState extends State { } }, child: Container( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8), + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space2, + horizontal: UiConstants.space2, + ), decoration: BoxDecoration( color: isActive - ? Colors.white - : Colors.white.withAlpha((0.2 * 255).round()), - borderRadius: BorderRadius.circular(8), + ? UiColors.white + : UiColors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -291,23 +288,17 @@ class _ShiftsPageState extends State { icon, size: 14, color: !enabled - ? Colors.white.withAlpha((0.5 * 255).round()) + ? UiColors.white.withValues(alpha: 0.5) : isActive - ? AppColors.krowBlue - : Colors.white, + ? UiColors.primary + : UiColors.white, ), - const SizedBox(width: 6), + const SizedBox(width: UiConstants.space1), Flexible( child: Text( label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: !enabled - ? Colors.white.withAlpha((0.5 * 255).round()) - : isActive - ? AppColors.krowBlue - : Colors.white, + style: (isActive ? UiTypography.body3m.copyWith(color: UiColors.primary) : UiTypography.body3m.white).copyWith( + color: !enabled ? UiColors.white.withValues(alpha: 0.5) : null, ), overflow: TextOverflow.ellipsis, ), @@ -316,23 +307,21 @@ class _ShiftsPageState extends State { const SizedBox(width: 4), Container( padding: const EdgeInsets.symmetric( - horizontal: 6, + horizontal: UiConstants.space1, vertical: 2, ), constraints: const BoxConstraints(minWidth: 18), decoration: BoxDecoration( color: isActive - ? AppColors.krowBlue.withAlpha((0.1 * 255).round()) - : Colors.white.withAlpha((0.2 * 255).round()), - borderRadius: BorderRadius.circular(999), + ? UiColors.primary.withValues(alpha: 0.1) + : UiColors.white.withValues(alpha: 0.2), + borderRadius: UiConstants.radiusFull, ), child: Center( child: Text( "$count", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: isActive ? AppColors.krowBlue : Colors.white, + style: UiTypography.footnote1b.copyWith( + color: isActive ? UiColors.primary : UiColors.white, ), ), ), diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart index 2b07e2a0..9ca03298 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart @@ -118,14 +118,14 @@ class _MyShiftCardState extends State { Modular.to.pushShiftDetails(widget.shift); }, child: Container( - margin: const EdgeInsets.only(bottom: 12), + margin: const EdgeInsets.only(bottom: UiConstants.space3), decoration: BoxDecoration( - color: Colors.white, + color: UiColors.white, borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -142,7 +142,7 @@ class _MyShiftCardState extends State { // Status Badge if (statusText.isNotEmpty) Padding( - padding: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.only(bottom: UiConstants.space2), child: Row( children: [ if (statusIcon != null) @@ -173,14 +173,14 @@ class _MyShiftCardState extends State { ), // Shift Type Badge for available/pending shifts if (status == 'open' || status == 'pending') ...[ - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( - color: UiColors.primary.withOpacity(0.1), + color: UiColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Text( @@ -205,20 +205,20 @@ class _MyShiftCardState extends State { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - UiColors.primary.withOpacity(0.09), - UiColors.primary.withOpacity(0.03), + UiColors.primary.withValues(alpha: 0.09), + UiColors.primary.withValues(alpha: 0.03), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all( - color: UiColors.primary.withOpacity(0.09), + color: UiColors.primary.withValues(alpha: 0.09), ), ), child: widget.shift.logoUrl != null ? ClipRRect( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), child: Image.network( widget.shift.logoUrl!, fit: BoxFit.contain, @@ -232,7 +232,7 @@ class _MyShiftCardState extends State { ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), // Details Expanded( @@ -249,42 +249,34 @@ class _MyShiftCardState extends State { children: [ Text( widget.shift.title, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, overflow: TextOverflow.ellipsis, ), Text( widget.shift.clientName, - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, overflow: TextOverflow.ellipsis, ), ], ), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( "\$${estimatedTotal.toStringAsFixed(0)}", - style: UiTypography.title1m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.title1m.textPrimary, ), Text( "\$${widget.shift.hourlyRate.toInt()}/hr Ā· ${duration.toInt()}h", - style: UiTypography.footnote2r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote2r.textSecondary, ), ], ), ], ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), // Date & Time - Multi-Day or Single Day if (widget.shift.durationDays != null && @@ -332,11 +324,9 @@ class _MyShiftCardState extends State { const SizedBox(width: 4), Text( _formatDate(widget.shift.date), - style: UiTypography.footnote1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote1r.textSecondary, ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), const Icon( UiIcons.clock, size: 12, @@ -345,9 +335,7 @@ class _MyShiftCardState extends State { const SizedBox(width: 4), Text( "${_formatTime(widget.shift.startTime)} - ${_formatTime(widget.shift.endTime)}", - style: UiTypography.footnote1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote1r.textSecondary, ), ], ), @@ -368,9 +356,7 @@ class _MyShiftCardState extends State { widget.shift.locationAddress.isNotEmpty ? widget.shift.locationAddress : widget.shift.location, - style: UiTypography.footnote1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote1r.textSecondary, overflow: TextOverflow.ellipsis, ), ), diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_assignment_card.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_assignment_card.dart index d46eb14a..086571e2 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_assignment_card.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_assignment_card.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:krow_domain/krow_domain.dart'; import 'package:design_system/design_system.dart'; -import 'package:core_localization/core_localization.dart'; class ShiftAssignmentCard extends StatelessWidget { final Shift shift; @@ -66,12 +65,12 @@ class ShiftAssignmentCard extends StatelessWidget { return Container( decoration: BoxDecoration( - color: Colors.white, + color: UiColors.white, borderRadius: UiConstants.radiusLg, border: Border.all(color: UiColors.border), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: UiColors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), @@ -81,7 +80,7 @@ class ShiftAssignmentCard extends StatelessWidget { children: [ // Header Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -97,20 +96,20 @@ class ShiftAssignmentCard extends StatelessWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - UiColors.primary.withOpacity(0.09), - UiColors.primary.withOpacity(0.03), + UiColors.primary.withValues(alpha: 0.09), + UiColors.primary.withValues(alpha: 0.03), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all( - color: UiColors.primary.withOpacity(0.09), + color: UiColors.primary.withValues(alpha: 0.09), ), ), child: shift.logoUrl != null ? ClipRRect( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), child: Image.network( shift.logoUrl!, fit: BoxFit.contain, @@ -124,7 +123,7 @@ class ShiftAssignmentCard extends StatelessWidget { ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), // Details Expanded( @@ -140,42 +139,34 @@ class ShiftAssignmentCard extends StatelessWidget { children: [ Text( shift.title, - style: UiTypography.body2m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.body2m.textPrimary, overflow: TextOverflow.ellipsis, ), Text( shift.clientName, - style: UiTypography.body3r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.body3r.textSecondary, overflow: TextOverflow.ellipsis, ), ], ), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( "\$${totalPay.toStringAsFixed(0)}", - style: UiTypography.title1m.copyWith( - color: UiColors.textPrimary, - ), + style: UiTypography.title1m.textPrimary, ), Text( "\$${shift.hourlyRate.toInt()}/hr Ā· ${hours.toInt()}h", - style: UiTypography.footnote2r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote2r.textSecondary, ), ], ), ], ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), // Date & Time Row( @@ -188,11 +179,9 @@ class ShiftAssignmentCard extends StatelessWidget { const SizedBox(width: 4), Text( _formatDate(shift.date), - style: UiTypography.footnote1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote1r.textSecondary, ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), const Icon( UiIcons.clock, size: 12, @@ -201,9 +190,7 @@ class ShiftAssignmentCard extends StatelessWidget { const SizedBox(width: 4), Text( "${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}", - style: UiTypography.footnote1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote1r.textSecondary, ), ], ), @@ -223,9 +210,7 @@ class ShiftAssignmentCard extends StatelessWidget { shift.locationAddress.isNotEmpty ? shift.locationAddress : shift.location, - style: UiTypography.footnote1r.copyWith( - color: UiColors.textSecondary, - ), + style: UiTypography.footnote1r.textSecondary, overflow: TextOverflow.ellipsis, ), ), @@ -240,38 +225,55 @@ class ShiftAssignmentCard extends StatelessWidget { ), ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + // Actions + Container( + padding: const EdgeInsets.all(UiConstants.space2), + decoration: const BoxDecoration( + color: UiColors.secondary, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(UiConstants.radiusBase), + bottomRight: Radius.circular(UiConstants.radiusBase), + ), + ), child: Row( children: [ Expanded( - child: OutlinedButton( + child: TextButton( onPressed: onDecline, - style: OutlinedButton.styleFrom( - foregroundColor: UiColors.iconSecondary, - side: const BorderSide(color: UiColors.border), - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + style: TextButton.styleFrom( + foregroundColor: UiColors.destructive, + ), + child: Text( + "Decline", // Fallback if translation is broken + style: UiTypography.body2m.textError, ), - child: Text(t.staff_shifts.action.decline), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space2), Expanded( child: ElevatedButton( - onPressed: onConfirm, + onPressed: isConfirming ? null : onConfirm, style: ElevatedButton.styleFrom( backgroundColor: UiColors.primary, - foregroundColor: Colors.white, + foregroundColor: UiColors.white, elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), ), ), - child: Text(t.staff_shifts.action.confirm), + child: isConfirming + ? const SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + color: UiColors.white, + ), + ) + : Text( + "Accept", // Fallback + style: UiTypography.body2m.white, + ), ), ), ], diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart index 1b75a48c..6422c312 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart @@ -2,7 +2,6 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:krow_domain/krow_domain.dart'; -import '../../styles/shifts_styles.dart'; import '../my_shift_card.dart'; import '../shared/empty_state_view.dart'; @@ -27,22 +26,21 @@ class _FindShiftsTabState extends State { return GestureDetector( onTap: () => setState(() => _jobType = id), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space2, + ), decoration: BoxDecoration( - color: isSelected ? AppColors.krowBlue : Colors.white, - borderRadius: BorderRadius.circular(999), + color: isSelected ? UiColors.primary : UiColors.white, + borderRadius: UiConstants.radiusFull, border: Border.all( - color: isSelected ? AppColors.krowBlue : const Color(0xFFE2E8F0), + color: isSelected ? UiColors.primary : UiColors.border, ), ), child: Text( label, textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: isSelected ? Colors.white : const Color(0xFF64748B), - ), + style: (isSelected ? UiTypography.footnote2m.white : UiTypography.footnote2m.textSecondary), ), ), ); @@ -73,8 +71,11 @@ class _FindShiftsTabState extends State { children: [ // Search and Filters Container( - color: Colors.white, - padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), + color: UiColors.white, + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + vertical: UiConstants.space4, + ), child: Column( children: [ // Search Bar @@ -83,12 +84,12 @@ class _FindShiftsTabState extends State { Expanded( child: Container( height: 48, - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3), decoration: BoxDecoration( - color: const Color(0xFFF8FAFC), - borderRadius: BorderRadius.circular(12), + color: UiColors.background, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all( - color: const Color(0xFFE2E8F0), + color: UiColors.border, ), ), child: Row( @@ -96,20 +97,17 @@ class _FindShiftsTabState extends State { const Icon( UiIcons.search, size: 20, - color: Color(0xFF94A3B8), + color: UiColors.textInactive, ), - const SizedBox(width: 10), + const SizedBox(width: UiConstants.space2), Expanded( child: TextField( onChanged: (v) => setState(() => _searchQuery = v), - decoration: const InputDecoration( + decoration: InputDecoration( border: InputBorder.none, hintText: "Search jobs, location...", - hintStyle: TextStyle( - color: Color(0xFF94A3B8), - fontSize: 14, - ), + hintStyle: UiTypography.body2r.textPlaceholder, ), ), ), @@ -117,37 +115,37 @@ class _FindShiftsTabState extends State { ), ), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Container( height: 48, width: 48, decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all( - color: const Color(0xFFE2E8F0), + color: UiColors.border, ), ), child: const Icon( UiIcons.filter, size: 18, - color: Color(0xFF64748B), + color: UiColors.textSecondary, ), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: UiConstants.space4), // Filter Tabs SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFilterTab('all', 'All Jobs'), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), _buildFilterTab('one-day', 'One Day'), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), _buildFilterTab('multi-day', 'Multi-Day'), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), _buildFilterTab('long-term', 'Long Term'), ], ), @@ -158,19 +156,19 @@ class _FindShiftsTabState extends State { Expanded( child: filteredJobs.isEmpty - ? EmptyStateView( + ? const EmptyStateView( icon: UiIcons.search, title: "No jobs available", subtitle: "Check back later", ) : SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5), child: Column( children: [ - const SizedBox(height: 20), + const SizedBox(height: UiConstants.space5), ...filteredJobs.map( (shift) => Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: UiConstants.space3), child: MyShiftCard( shift: shift, ), diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/history_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/history_shifts_tab.dart index 75f78284..6b325194 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/history_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/history_shifts_tab.dart @@ -17,7 +17,7 @@ class HistoryShiftsTab extends StatelessWidget { @override Widget build(BuildContext context) { if (historyShifts.isEmpty) { - return EmptyStateView( + return const EmptyStateView( icon: UiIcons.clock, title: "No shift history", subtitle: "Completed shifts appear here", @@ -25,13 +25,13 @@ class HistoryShiftsTab extends StatelessWidget { } return SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5), child: Column( children: [ - const SizedBox(height: 20), + const SizedBox(height: UiConstants.space5), ...historyShifts.map( (shift) => Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: UiConstants.space3), child: GestureDetector( onTap: () => Modular.to.pushShiftDetails(shift), child: MyShiftCard( diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/my_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/my_shifts_tab.dart index d2aecd1f..f184df7c 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/my_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/my_shifts_tab.dart @@ -1,14 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import 'package:design_system/design_system.dart'; import 'package:krow_domain/krow_domain.dart'; import '../../blocs/shifts/shifts_bloc.dart'; import '../my_shift_card.dart'; import '../shift_assignment_card.dart'; import '../shared/empty_state_view.dart'; -import '../../styles/shifts_styles.dart'; class MyShiftsTab extends StatefulWidget { final List myShifts; @@ -118,14 +116,14 @@ class _MyShiftsTabState extends State { Navigator.of(context).pop(); context.read().add(AcceptShiftEvent(id)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Shift confirmed!'), - backgroundColor: Color(0xFF10B981), + SnackBar( + content: const Text('Shift confirmed!'), + backgroundColor: UiColors.success, ), ); }, style: TextButton.styleFrom( - foregroundColor: const Color(0xFF10B981), + foregroundColor: UiColors.success, ), child: const Text('Accept'), ), @@ -152,14 +150,14 @@ class _MyShiftsTabState extends State { Navigator.of(context).pop(); context.read().add(DeclineShiftEvent(id)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Shift declined.'), - backgroundColor: Color(0xFFEF4444), + SnackBar( + content: const Text('Shift declined.'), + backgroundColor: UiColors.destructive, ), ); }, style: TextButton.styleFrom( - foregroundColor: const Color(0xFFEF4444), + foregroundColor: UiColors.destructive, ), child: const Text('Decline'), ), @@ -212,12 +210,15 @@ class _MyShiftsTabState extends State { children: [ // Calendar Selector Container( - color: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + color: UiColors.white, + padding: const EdgeInsets.symmetric( + vertical: UiConstants.space4, + horizontal: UiConstants.space4, + ), child: Column( children: [ Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: UiConstants.space3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -225,7 +226,7 @@ class _MyShiftsTabState extends State { icon: const Icon( UiIcons.chevronLeft, size: 20, - color: AppColors.krowCharcoal, + color: UiColors.textPrimary, ), onPressed: () => setState(() { _weekOffset--; @@ -237,17 +238,13 @@ class _MyShiftsTabState extends State { ), Text( DateFormat('MMMM yyyy').format(weekStartDate), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.krowCharcoal, - ), + style: UiTypography.title1m.textPrimary, ), IconButton( icon: const Icon( UiIcons.chevronRight, size: 20, - color: AppColors.krowCharcoal, + color: UiColors.textPrimary, ), onPressed: () => setState(() { _weekOffset++; @@ -284,13 +281,13 @@ class _MyShiftsTabState extends State { height: 60, decoration: BoxDecoration( color: isSelected - ? AppColors.krowBlue - : Colors.white, - borderRadius: BorderRadius.circular(12), + ? UiColors.primary + : UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase), border: Border.all( color: isSelected - ? AppColors.krowBlue - : AppColors.krowBorder, + ? UiColors.primary + : UiColors.border, width: 1, ), ), @@ -299,31 +296,25 @@ class _MyShiftsTabState extends State { children: [ Text( date.day.toString().padLeft(2, '0'), - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: isSelected - ? Colors.white - : AppColors.krowCharcoal, - ), + style: isSelected + ? UiTypography.body1b.white + : UiTypography.body1b.textPrimary, ), Text( DateFormat('E').format(date), - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - color: isSelected - ? Colors.white.withOpacity(0.8) - : AppColors.krowMuted, + style: (isSelected + ? UiTypography.footnote2m.white + : UiTypography.footnote2m.textSecondary).copyWith( + color: isSelected ? UiColors.white.withValues(alpha: 0.8) : null, ), ), if (hasShifts && !isSelected) Container( - margin: const EdgeInsets.only(top: 4), + margin: const EdgeInsets.only(top: UiConstants.space1), width: 4, height: 4, decoration: const BoxDecoration( - color: AppColors.krowBlue, + color: UiColors.primary, shape: BoxShape.circle, ), ), @@ -338,22 +329,22 @@ class _MyShiftsTabState extends State { ], ), ), - const Divider(height: 1, color: AppColors.krowBorder), + const Divider(height: 1, color: UiColors.border), Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5), child: Column( children: [ - const SizedBox(height: 20), + const SizedBox(height: UiConstants.space5), if (widget.pendingAssignments.isNotEmpty) ...[ _buildSectionHeader( "Awaiting Confirmation", - const Color(0xFFF59E0B), + UiColors.textWarning, ), ...widget.pendingAssignments.map( (shift) => Padding( - padding: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.only(bottom: UiConstants.space4), child: ShiftAssignmentCard( shift: shift, onConfirm: () => _confirmShift(shift.id), @@ -362,14 +353,14 @@ class _MyShiftsTabState extends State { ), ), ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), ], if (visibleCancelledShifts.isNotEmpty) ...[ - _buildSectionHeader("Cancelled Shifts", AppColors.krowMuted), + _buildSectionHeader("Cancelled Shifts", UiColors.textSecondary), ...visibleCancelledShifts.map( (shift) => Padding( - padding: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.only(bottom: UiConstants.space4), child: _buildCancelledCard( title: shift.title, client: shift.clientName, @@ -383,15 +374,15 @@ class _MyShiftsTabState extends State { ), ), ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), ], // Confirmed Shifts if (visibleMyShifts.isNotEmpty) ...[ - _buildSectionHeader("Confirmed Shifts", AppColors.krowMuted), + _buildSectionHeader("Confirmed Shifts", UiColors.textSecondary), ...visibleMyShifts.map( (shift) => Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: UiConstants.space3), child: MyShiftCard(shift: shift), ), ), @@ -417,7 +408,7 @@ class _MyShiftsTabState extends State { Widget _buildSectionHeader(String title, Color dotColor) { return Padding( - padding: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.only(bottom: UiConstants.space4), child: Row( children: [ Container( @@ -425,16 +416,12 @@ class _MyShiftsTabState extends State { height: 8, decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle), ), - const SizedBox(width: 8), + const SizedBox(width: UiConstants.space2), Text( title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: dotColor == AppColors.krowMuted - ? AppColors.krowMuted - : dotColor, - ), + style: (dotColor == UiColors.textSecondary + ? UiTypography.body2b.textSecondary + : UiTypography.body2b.copyWith(color: dotColor)), ), ], ), @@ -455,11 +442,11 @@ class _MyShiftsTabState extends State { return GestureDetector( onTap: onTap, child: Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColors.krowBorder), + color: UiColors.white, + borderRadius: BorderRadius.circular(UiConstants.radiusBase + 4), + border: Border.all(color: UiColors.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -470,33 +457,25 @@ class _MyShiftsTabState extends State { width: 6, height: 6, decoration: const BoxDecoration( - color: Color(0xFFEF4444), + color: UiColors.destructive, shape: BoxShape.circle, ), ), const SizedBox(width: 6), - const Text( + Text( "CANCELLED", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Color(0xFFEF4444), - ), + style: UiTypography.footnote2b.textError, ), if (isLastMinute) ...[ const SizedBox(width: 4), - const Text( + Text( "• 4hr compensation", - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - color: Color(0xFF10B981), - ), + style: UiTypography.footnote2m.textSuccess, ), ], ], ), - const SizedBox(height: 12), + const SizedBox(height: UiConstants.space3), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -504,18 +483,18 @@ class _MyShiftsTabState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: AppColors.krowBlue.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), + color: UiColors.primary.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(UiConstants.radiusBase), ), child: const Center( child: Icon( - LucideIcons.briefcase, - color: AppColors.krowBlue, + UiIcons.briefcase, + color: UiColors.primary, size: 20, ), ), ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -529,18 +508,11 @@ class _MyShiftsTabState extends State { children: [ Text( title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.krowCharcoal, - ), + style: UiTypography.body2b.textPrimary, ), Text( client, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), + style: UiTypography.footnote1r.textSecondary, ), ], ), @@ -550,52 +522,39 @@ class _MyShiftsTabState extends State { children: [ Text( pay, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.krowCharcoal, - ), + style: UiTypography.headline4m.textPrimary, ), Text( rate, - style: const TextStyle( - fontSize: 10, - color: AppColors.krowMuted, - ), + style: UiTypography.footnote2r.textSecondary, ), ], ), ], ), - const SizedBox(height: 8), + const SizedBox(height: UiConstants.space2), Row( children: [ const Icon( - LucideIcons.calendar, + UiIcons.calendar, size: 12, - color: AppColors.krowMuted, + color: UiColors.textSecondary, ), const SizedBox(width: 4), Text( date, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), + style: UiTypography.footnote1r.textSecondary, ), - const SizedBox(width: 12), + const SizedBox(width: UiConstants.space3), const Icon( - LucideIcons.clock, + UiIcons.clock, size: 12, - color: AppColors.krowMuted, + color: UiColors.textSecondary, ), const SizedBox(width: 4), Text( time, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), + style: UiTypography.footnote1r.textSecondary, ), ], ), @@ -603,18 +562,15 @@ class _MyShiftsTabState extends State { Row( children: [ const Icon( - LucideIcons.mapPin, + UiIcons.mapPin, size: 12, - color: AppColors.krowMuted, + color: UiColors.textSecondary, ), const SizedBox(width: 4), Expanded( child: Text( address, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), + style: UiTypography.footnote1r.textSecondary, overflow: TextOverflow.ellipsis, ), ), diff --git a/apps/web/package.json b/apps/web/package.json index adc206a8..cfbdab04 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -10,9 +10,13 @@ "preview": "vite preview" }, "dependencies": { + "@dataconnect/generated": "link:src/dataconnect-generated", "@firebase/analytics": "^0.10.19", "@firebase/data-connect": "^0.3.12", + "@hello-pangea/dnd": "^18.0.1", + "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-slot": "^1.2.4", @@ -31,12 +35,16 @@ "date-fns": "^4.1.0", "firebase": "^12.8.0", "framer-motion": "^12.29.2", + "i18next": "^25.8.4", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", "lucide-react": "^0.563.0", "react": "^19.2.0", "react-datepicker": "^9.1.0", "react-day-picker": "^9.13.0", "react-dom": "^19.2.0", "react-hook-form": "^7.71.1", + "react-i18next": "^16.5.4", "react-redux": "^9.2.0", "react-router-dom": "^7.13.0", "recharts": "^3.7.0", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 1f511454..4b1de6e6 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -6,20 +6,33 @@ settings: overrides: dataconnect-generated: link:../../../../../AppData/Local/pnpm/global/5/node_modules/src/dataconnect-generated + '@dataconnect/generated': link:src/dataconnect-generated importers: .: dependencies: + '@dataconnect/generated': + specifier: link:src/dataconnect-generated + version: link:src/dataconnect-generated '@firebase/analytics': specifier: ^0.10.19 version: 0.10.19(@firebase/app@0.14.7) '@firebase/data-connect': specifier: ^0.3.12 version: 0.3.12(@firebase/app@0.14.7) + '@hello-pangea/dnd': + specifier: ^18.0.1 + version: 18.0.1(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-avatar': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-label': specifier: ^2.1.7 version: 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -74,6 +87,15 @@ importers: framer-motion: specifier: ^12.29.2 version: 12.29.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + i18next: + specifier: ^25.8.4 + version: 25.8.4(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.2.0 + version: 8.2.0 + i18next-http-backend: + specifier: ^3.0.2 + version: 3.0.2 lucide-react: specifier: ^0.563.0 version: 0.563.0(react@19.2.4) @@ -92,6 +114,9 @@ importers: react-hook-form: specifier: ^7.71.1 version: 7.71.1(react@19.2.4) + react-i18next: + specifier: ^16.5.4 + version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-redux: specifier: ^9.2.0 version: 9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1) @@ -230,6 +255,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -679,6 +708,12 @@ packages: engines: {node: '>=6'} hasBin: true + '@hello-pangea/dnd@18.0.1': + resolution: {integrity: sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -828,6 +863,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-avatar@1.1.11': + resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-checkbox@1.3.3': resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} peerDependencies: @@ -898,6 +946,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.1.15': resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: @@ -1517,79 +1574,66 @@ packages: resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.0': resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.0': resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.0': resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.0': resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.0': resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.0': resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.0': resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.0': resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.0': resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.0': resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.0': resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.0': resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.57.0': resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==} @@ -1665,28 +1709,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1974,10 +2014,16 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-box-model@1.2.1: + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -2298,9 +2344,26 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + i18next-browser-languagedetector@8.2.0: + resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==} + + i18next-http-backend@3.0.2: + resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==} + + i18next@25.8.4: + resolution: {integrity: sha512-a9A0MnUjKvzjEN/26ZY1okpra9kA8MEwzYEz1BNm+IyxUKPRH6ihf0p7vj8YvULwZHKHl3zkJ6KOt4hewxBecQ==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -2417,28 +2480,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -2516,6 +2575,15 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2585,6 +2653,9 @@ packages: '@types/react-dom': optional: true + raf-schd@4.0.3: + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + react-datepicker@9.1.0: resolution: {integrity: sha512-lOp+m5bc+ttgtB5MHEjwiVu4nlp4CvJLS/PG1OiOe5pmg9kV73pEqO8H0Geqvg2E8gjqTaL9eRhSe+ZpeKP3nA==} peerDependencies: @@ -2612,6 +2683,22 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-i18next@16.5.4: + resolution: {integrity: sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==} + peerDependencies: + i18next: '>= 25.6.2' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} @@ -2785,6 +2872,9 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -2894,9 +2984,16 @@ packages: yaml: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + web-vitals@4.2.4: resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -2905,6 +3002,9 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3037,6 +3137,8 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -3541,6 +3643,18 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 + '@hello-pangea/dnd@18.0.1(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + css-box-model: 1.2.1 + raf-schd: 4.0.3 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-redux: 9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1) + redux: 5.0.1 + transitivePeerDependencies: + - '@types/react' + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -3671,6 +3785,19 @@ snapshots: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -3741,6 +3868,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.10 + '@radix-ui/react-context@1.1.3(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4831,12 +4964,22 @@ snapshots: cookie@1.1.1: {} + cross-fetch@4.0.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css-box-model@1.2.1: + dependencies: + tiny-invariant: 1.3.3 + csstype@3.2.3: {} d3-array@3.2.4: @@ -5188,8 +5331,28 @@ snapshots: dependencies: hermes-estree: 0.25.1 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + http-parser-js@0.5.10: {} + i18next-browser-languagedetector@8.2.0: + dependencies: + '@babel/runtime': 7.28.6 + + i18next-http-backend@3.0.2: + dependencies: + cross-fetch: 4.0.0 + transitivePeerDependencies: + - encoding + + i18next@25.8.4(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + optionalDependencies: + typescript: 5.9.3 + idb@7.1.1: {} ignore@5.3.2: {} @@ -5345,6 +5508,10 @@ snapshots: natural-compare@1.4.0: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.27: {} optionator@0.9.4: @@ -5468,6 +5635,8 @@ snapshots: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) + raf-schd@4.0.3: {} + react-datepicker@9.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@floating-ui/react': 0.27.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -5492,6 +5661,17 @@ snapshots: dependencies: react: 19.2.4 + react-i18next@16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.28.6 + html-parse-stringify: 3.0.1 + i18next: 25.8.4(typescript@5.9.3) + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + typescript: 5.9.3 + react-is@19.2.4: {} react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1): @@ -5664,6 +5844,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tr46@0.0.3: {} + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -5751,8 +5933,12 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 + void-elements@3.1.0: {} + web-vitals@4.2.4: {} + webidl-conversions@3.0.1: {} + websocket-driver@0.7.4: dependencies: http-parser-js: 0.5.10 @@ -5761,6 +5947,11 @@ snapshots: websocket-extensions@0.1.4: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/apps/web/pnpm-workspace.yaml b/apps/web/pnpm-workspace.yaml index 62ff1aa4..13edf779 100644 --- a/apps/web/pnpm-workspace.yaml +++ b/apps/web/pnpm-workspace.yaml @@ -1,3 +1,3 @@ overrides: - + '@dataconnect/generated': link:src/dataconnect-generated dataconnect-generated: link:../../../../../AppData/Local/pnpm/global/5/node_modules/src/dataconnect-generated diff --git a/apps/web/src/common/components/ui/avatar.tsx b/apps/web/src/common/components/ui/avatar.tsx new file mode 100644 index 00000000..7c0eb5ff --- /dev/null +++ b/apps/web/src/common/components/ui/avatar.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/apps/web/src/common/components/ui/dropdown-menu.tsx b/apps/web/src/common/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..a97a3995 --- /dev/null +++ b/apps/web/src/common/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Dot } from "lucide-react" +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroups = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = + DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +function DropdownMenuShortcut({ + className, + ...props +}: React.HTMLAttributes) { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroups as DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/apps/web/src/features/finance/invoices/InvoiceDetail.tsx b/apps/web/src/features/finance/invoices/InvoiceDetail.tsx new file mode 100644 index 00000000..6c8c5313 --- /dev/null +++ b/apps/web/src/features/finance/invoices/InvoiceDetail.tsx @@ -0,0 +1,281 @@ +import { Badge } from "@/common/components/ui/badge"; +import { Button } from "@/common/components/ui/button"; +import { Card, CardContent } from "@/common/components/ui/card"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/common/components/ui/table"; +import { InvoiceStatus } from "@/dataconnect-generated"; +import { useGetInvoiceById, useUpdateInvoice, useListRecentPaymentsByInvoiceId } from "@/dataconnect-generated/react"; +import { dataConnect } from "@/features/auth/firebase"; +import DashboardLayout from "@/features/layouts/DashboardLayout"; +import type { RootState } from "@/store/store"; +import { format, parseISO } from "date-fns"; +import { ArrowLeft, Download, Mail, CheckCircle, FileText, User, Calendar, MapPin, DollarSign } from "lucide-react"; +import { useSelector } from "react-redux"; +import { useNavigate, useParams } from "react-router-dom"; + +const statusConfig: Record = { + DRAFT: { label: "Draft", className: "bg-slate-100 text-slate-600 border-transparent" }, + PENDING: { label: "Sent", className: "bg-blue-50 text-blue-700 border-blue-200" }, + PENDING_REVIEW: { label: "Pending Review", className: "bg-amber-50 text-amber-700 border-amber-200" }, + APPROVED: { label: "Approved", className: "bg-emerald-50 text-emerald-700 border-emerald-200" }, + DISPUTED: { label: "Disputed", className: "bg-red-50 text-red-700 border-red-200" }, + OVERDUE: { label: "Overdue", className: "bg-rose-50 text-rose-700 border-rose-200" }, + PAID: { label: "Paid", className: "bg-emerald-50 text-emerald-700 border-emerald-200" }, +}; + +export default function InvoiceDetail() { + const navigate = useNavigate(); + const { id: invoiceId } = useParams<{ id: string }>(); + const { user } = useSelector((state: RootState) => state.auth); + + // Fetch Invoice Data + const { data: invoiceData, isLoading: loadingInvoice } = useGetInvoiceById(dataConnect, { id: invoiceId! }); + const invoice = invoiceData?.invoice; + + // Fetch Payment History + const { data: paymentsData, isLoading: loadingPayments } = useListRecentPaymentsByInvoiceId(dataConnect, { invoiceId: invoiceId! }); + const payments = paymentsData?.recentPayments || []; + + // Mutations + const { mutate: updateInvoice } = useUpdateInvoice(dataConnect); + + const handleMarkAsPaid = () => { + if (!invoiceId) return; + updateInvoice({ id: invoiceId, status: InvoiceStatus.PAID }); + }; + + const handleSendInvoice = () => { + // Logic for sending invoice (e.g., email service) + console.log("Sending invoice..."); + updateInvoice({ id: invoiceId!, status: InvoiceStatus.PENDING }); + }; + + const handleDownloadPDF = () => { + window.print(); + }; + + if (loadingInvoice) { + return ( + +
+
+
+
+ ); + } + + if (!invoice) { + return ( + +
+

Invoice not found

+ +
+
+ ); + } + + const status = statusConfig[invoice.status] || { label: invoice.status, className: "" }; + const issueDate = invoice.issueDate ? format(parseISO(invoice.issueDate as string), "MMM d, yyyy") : "—"; + const dueDate = invoice.dueDate ? format(parseISO(invoice.dueDate as string), "MMM d, yyyy") : "—"; + + // Parse JSON data for roles/charges (assuming they are JSON from the schema) + const lineItems = (invoice.roles as any[]) || []; + + return ( + navigate("/invoices")} leadingIcon={}> + Back + + } + actions={ +
+ + {invoice.status === "DRAFT" && ( + + )} + {invoice.status !== "PAID" && ( + + )} +
+ } + > +
+
+ {/* Header & Client Info */} + + +
+
+
+

Client Information

+
+
+ +
+
+

{invoice.business?.businessName}

+

{invoice.business?.email}

+

{invoice.business?.phone}

+
+
+
+
+ + {invoice.hub || (invoice.order as any)?.teamHub?.hubName} +
+
+ +
+
+ Status + {status.label} +
+
+ Issued + {issueDate} +
+
+ Due Date + {dueDate} +
+
+
+
+
+ + {/* Line Items Table */} + +
+

Line Items (Shifts & Staff)

+
+ + + + Staff / Role + Hours + Rate + Total + + + + {lineItems.length === 0 ? ( + + + No line items recorded for this invoice. + + + ) : ( + lineItems.map((item: any, idx: number) => ( + + +

{item.staffName || "Staff Member"}

+

{item.roleName || "Support"}

+
+ {item.hours || 0}h + ${(item.rate || 0).toFixed(2)} + ${(item.total || 0).toFixed(2)} +
+ )) + )} +
+
+
+ + {/* Payment History */} + +
+

Payment History

+
+
+ {payments.length === 0 ? ( +
+ No payment records found. +
+ ) : ( + + + {payments.map((payment) => ( + + +
+
+ +
+
+

Payment Received

+

+ {payment.createdAt ? format(parseISO(payment.createdAt as string), "MMM d, yyyy") : "—"} +

+
+
+
+ + + {payment.status} + + +
+ ))} +
+
+ )} +
+
+
+ + {/* Financial Summary Sidebar */} +
+ + +

Financial Summary

+
+
+ Subtotal + ${(invoice.subtotal || 0).toLocaleString(undefined, { minimumFractionDigits: 2 })} +
+
+ Fees + ${(invoice.otherCharges || 0).toLocaleString(undefined, { minimumFractionDigits: 2 })} +
+
+
+

Grand Total

+

+ ${invoice.amount.toLocaleString(undefined, { minimumFractionDigits: 2 })} +

+
+
+
+
+
+ + + +

Internal Notes

+

+ {invoice.notes || "No internal notes provided for this invoice."} +

+
+
+ +
+ +

+ For any questions regarding this invoice, please contact support@krowworkforce.com +

+
+
+
+
+ ); +} diff --git a/apps/web/src/features/finance/invoices/InvoiceEditor.tsx b/apps/web/src/features/finance/invoices/InvoiceEditor.tsx new file mode 100644 index 00000000..e4d48597 --- /dev/null +++ b/apps/web/src/features/finance/invoices/InvoiceEditor.tsx @@ -0,0 +1,1318 @@ +import { Badge } from "@/common/components/ui/badge"; +import { Button } from "@/common/components/ui/button"; +import { Card } from "@/common/components/ui/card"; +import { Input } from "@/common/components/ui/input"; +import { Label } from "@/common/components/ui/label"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/common/components/ui/popover"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/common/components/ui/select"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/common/components/ui/table"; +import { Textarea } from "@/common/components/ui/textarea"; +import { useToast } from "@/common/components/ui/use-toast"; +import { InvoiceStatus, InovicePaymentTerms } from "@/dataconnect-generated"; +import { + useCreateInvoice, + useCreateInvoiceTemplate, + useDeleteInvoiceTemplate, + useGetInvoiceById, + useListBusinesses, + useListInvoices, + useListInvoiceTemplates, + useListOrders, + useListStaff, + useListVendorRates, + useUpdateInvoice +} from "@/dataconnect-generated/react"; +import { dataConnect } from "@/features/auth/firebase"; +import DashboardLayout from "@/features/layouts/DashboardLayout"; +import { addDays, format, parseISO } from "date-fns"; +import { ArrowLeft, Building2, Calendar, Clock, FileText, Mail, Phone, Plus, Trash2, User } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; + +// Helper to convert 24h to 12h format +const convertTo12Hour = (time24?: string) => { + if (!time24) return "09:00 AM"; + const [hours, minutes] = time24.split(':'); + const hour = parseInt(hours); + const ampm = hour >= 12 ? 'PM' : 'AM'; + const hour12 = hour % 12 || 12; + return `${String(hour12).padStart(2, '0')}:${minutes} ${ampm}`; +}; + +export default function InvoiceEditor() { + const navigate = useNavigate(); + const { id: pathId } = useParams<{ id: string }>(); + const { toast } = useToast(); + const [searchParams] = useSearchParams(); + + const invoiceId = searchParams.get('id'); + const disputedIndices = searchParams.get('disputed')?.split(',').map(Number).filter(n => !isNaN(n)) || []; + + const effectiveInvoiceId = invoiceId || (pathId !== 'new' ? pathId : null); + const isEdit = !!effectiveInvoiceId; + + // Data Connect Queries + const { data: invoicesData } = useListInvoices(dataConnect); + const invoices = invoicesData?.invoices || []; + + const { data: eventsData } = useListOrders(dataConnect); + const events = eventsData?.orders || []; + + const { data: businessesData } = useListBusinesses(dataConnect); + const businesses = businessesData?.businesses || []; + + const { data: vendorRatesData } = useListVendorRates(dataConnect); + const vendorRates = vendorRatesData?.vendorRates || []; + + const { data: staffData } = useListStaff(dataConnect); + const staffDirectory = staffData?.staffs || []; + + const { data: templatesData, refetch: refetchTemplates } = useListInvoiceTemplates(dataConnect); + const templates = templatesData?.invoiceTemplates || []; + + const { data: currentInvoiceData } = useGetInvoiceById(dataConnect, { id: effectiveInvoiceId || "" }, { enabled: isEdit && !!effectiveInvoiceId }); + const existingInvoice = currentInvoiceData?.invoice; + + const [selectedClientId, setSelectedClientId] = useState(""); + + useEffect(() => { + if (existingInvoice) { + setSelectedClientId(existingInvoice.businessId); + } + }, [existingInvoice]); + + // Generate sequential invoice number based on client prefix + const generateInvoiceNumber = (clientName: string, existingInvoices: any[]) => { + const extractCompanyName = (name: string) => { + if (!name) return ''; + return name.split(/\s*[-–]\s*/)[0].trim(); + }; + + const generatePrefix = (name: string) => { + if (!name) return 'INV'; + const companyName = extractCompanyName(name); + const words = companyName.trim().split(/\s+/).filter(w => w.length > 0); + if (words.length === 0) return 'INV'; + if (words.length === 1) { + return words[0].substring(0, 4).toUpperCase(); + } else { + return words.slice(0, 4).map(w => w[0]).join('').toUpperCase(); + } + }; + + const prefix = generatePrefix(clientName); + + const existingNumbers = (existingInvoices || []) + .filter(inv => inv.invoiceNumber?.startsWith(prefix + '-')) + .map(inv => { + const match = inv.invoiceNumber.match(new RegExp(`^${prefix}-(\\d+)$`)); + return match ? parseInt(match[1]) : 0; + }) + .filter(n => !isNaN(n)); + + const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1001; + return `${prefix}-${nextNumber}`; + }; + + const [formData, setFormData] = useState({ + invoice_number: "", + event_id: "", + event_name: "", + invoice_date: format(new Date(), 'yyyy-MM-dd'), + due_date: format(addDays(new Date(), 45), 'yyyy-MM-dd'), + payment_terms: "NET_45", + hub: "", + manager: "", + vendor_id: "", + department: "", + po_reference: "", + from_company: { + name: "KROW Workforce", + address: "848 E Gish Rd Ste 1, San Jose, CA 95112", + phone: "(408) 936-0180", + email: "billing@krowworkforce.com" + }, + to_company: { + name: "", + phone: "", + email: "", + address: "", + manager_name: "", + hub_name: "", + vendor_id: "" + }, + staff_entries: [], + charges: [], + other_charges: 0, + notes: "", + }); + + useEffect(() => { + if (existingInvoice) { + setFormData({ + invoice_number: existingInvoice.invoiceNumber, + event_id: existingInvoice.orderId || "", + event_name: existingInvoice.order?.eventName || "", + invoice_date: existingInvoice.issueDate ? format(parseISO(existingInvoice.issueDate as string), 'yyyy-MM-dd') : format(new Date(), 'yyyy-MM-dd'), + due_date: existingInvoice.dueDate ? format(parseISO(existingInvoice.dueDate as string), 'yyyy-MM-dd') : format(addDays(new Date(), 45), 'yyyy-MM-dd'), + payment_terms: existingInvoice.paymentTerms || "NET_45", + hub: existingInvoice.hub || "", + manager: existingInvoice.managerName || "", + vendor_id: existingInvoice.vendorNumber || "", + department: existingInvoice.order?.deparment || "", + po_reference: existingInvoice.order?.poReference || "", + from_company: { + name: existingInvoice.vendor?.companyName || "KROW Workforce", + address: existingInvoice.vendor?.address || "848 E Gish Rd Ste 1, San Jose, CA 95112", + phone: existingInvoice.vendor?.phone || "(408) 936-0180", + email: existingInvoice.vendor?.email || "billing@krowworkforce.com" + }, + to_company: { + name: existingInvoice.business?.businessName || "", + phone: existingInvoice.business?.phone || "", + email: existingInvoice.business?.email || "", + address: existingInvoice.business?.address || "", + manager_name: existingInvoice.business?.contactName || "", + hub_name: existingInvoice.hub || "", + vendor_id: existingInvoice.vendorNumber || "" + }, + staff_entries: existingInvoice.roles || [], + charges: existingInvoice.charges || [], + other_charges: existingInvoice.otherCharges || 0, + notes: existingInvoice.notes || "", + }); + } else if (invoices.length > 0 && !formData.invoice_number) { + setFormData((prev: any) => ({ ...prev, invoice_number: generateInvoiceNumber('', invoices) })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [existingInvoice, invoices]); + + const [timePickerOpen, setTimePickerOpen] = useState(null); + const [selectedTime, setSelectedTime] = useState({ hours: "09", minutes: "00", period: "AM" }); + + const getRateForPosition = (position: string) => { + const selectedBusiness = businesses.find(b => b.id === selectedClientId); + if (!selectedBusiness || !position) return 0; + + const businessName = selectedBusiness.businessName || ""; + const extractCompanyName = (name: string) => { + if (!name) return ''; + return name.split(/\s*[-–]\s*/)[0].trim(); + }; + const mainCompanyName = extractCompanyName(businessName); + + // Logic similar to MVP + const clientSpecificRate = vendorRates.find(rate => + rate.roleName?.toLowerCase() === position.toLowerCase() && + rate.client_name === businessName // This field might be different in Data Connect, checking listVendorRates output + ); + + if (clientSpecificRate) return clientSpecificRate.clientRate || 0; + + const defaultRate = vendorRates.find(rate => + rate.roleName?.toLowerCase() === position.toLowerCase() + ); + + return defaultRate?.clientRate || 0; + }; + + const handleClientSelect = (clientId: string) => { + setSelectedClientId(clientId); + const selectedBusiness = businesses.find(b => b.id === clientId); + + if (selectedBusiness) { + const hubName = selectedBusiness.hubBuilding || ""; + const newInvoiceNumber = generateInvoiceNumber(selectedBusiness.businessName, invoices); + + setFormData((prev: any) => ({ + ...prev, + invoice_number: isEdit ? prev.invoice_number : newInvoiceNumber, + to_company: { + name: selectedBusiness.businessName || "", + phone: selectedBusiness.phone || "", + email: selectedBusiness.email || "", + address: selectedBusiness.address || "", + manager_name: selectedBusiness.contactName || "", + hub_name: hubName, + vendor_id: "" // erp_vendor_id was not in business query, assuming empty for now + }, + hub: hubName, + manager: selectedBusiness.contactName || "", + department: "", + po_reference: "" + })); + } + }; + + const handleImportFromEvent = (event: any) => { + const staffEntries = (event.assignedStaff || []).map((staff: any) => { + const shift = (event.shifts as any[])?.[0]; + const role = shift?.roles?.find((r: any) => r.role === staff.role) || shift?.roles?.[0]; + + return { + name: staff.fullName || staff.staff_name || "", + date: event.date ? format(parseISO(event.date as string), 'MM/dd/yyyy') : format(new Date(), 'MM/dd/yyyy'), + position: staff.role || role?.role || "", + check_in: role?.start_time ? convertTo12Hour(role.start_time) : "09:00 AM", + check_out: role?.end_time ? convertTo12Hour(role.end_time) : "05:00 PM", + lunch: role?.break_minutes || 30, + worked_hours: role?.hours || 8, + regular_hours: Math.min(role?.hours || 8, 8), + ot_hours: Math.max(0, (role?.hours || 8) - 8), + dt_hours: 0, + rate: role?.cost_per_hour || getRateForPosition(staff.role || '') || 0, + regular_value: 0, + ot_value: 0, + dt_value: 0, + total: 0 + }; + }); + + staffEntries.forEach((entry: any) => { + entry.regular_value = entry.regular_hours * entry.rate; + entry.ot_value = entry.ot_hours * entry.rate * 1.5; + entry.dt_value = entry.dt_hours * entry.rate * 2; + entry.total = entry.regular_value + entry.ot_value + entry.dt_value; + }); + + const business = businesses.find(b => b.id === event.businessId); + + setFormData((prev: any) => ({ + ...prev, + event_id: event.id, + event_name: event.eventName || "", + hub: event.teamHub?.hubName || "", + manager: event.business?.contactName || "", + po_reference: event.poReference || "", + to_company: business ? { + name: business.businessName || "", + phone: business.phone || "", + email: business.email || "", + address: business.address || "", + manager_name: business.contactName || "", + hub_name: event.teamHub?.hubName || "", + vendor_id: "" + } : { + ...prev.to_company, + name: event.business?.businessName || prev.to_company.name, + hub_name: event.teamHub?.hubName || "" + }, + staff_entries: staffEntries.length > 0 ? staffEntries : prev.staff_entries + })); + + if (business) { + setSelectedClientId(business.id); + } + + toast({ + title: "āœ… Event Imported", + description: `Imported ${staffEntries.length} staff entries from ${event.eventName}`, + }); + }; + + const handleDuplicateInvoice = (invoice: any) => { + const newStaffEntries = (invoice.roles || []).map((entry: any) => ({ + ...entry, + date: format(new Date(), 'MM/dd/yyyy') + })); + + const newInvoiceNumber = generateInvoiceNumber(invoice.business?.businessName || '', invoices); + + setFormData({ + invoice_number: newInvoiceNumber, + event_id: "", + event_name: invoice.order?.eventName || "", + invoice_date: format(new Date(), 'yyyy-MM-dd'), + due_date: format(addDays(new Date(), 45), 'yyyy-MM-dd'), + payment_terms: invoice.paymentTerms || "NET_45", + hub: invoice.hub || "", + manager: invoice.managerName || "", + vendor_id: invoice.vendorNumber || "", + department: invoice.order?.deparment || "", + po_reference: invoice.order?.poReference || "", + from_company: { + name: invoice.vendor?.companyName || formData.from_company.name, + address: invoice.vendor?.address || formData.from_company.address, + phone: invoice.vendor?.phone || formData.from_company.phone, + email: invoice.vendor?.email || formData.from_company.email, + }, + to_company: { + name: invoice.business?.businessName || "", + phone: invoice.business?.phone || "", + email: invoice.business?.email || "", + address: invoice.business?.address || "", + manager_name: invoice.business?.contactName || "", + hub_name: invoice.hub || "", + vendor_id: invoice.vendorNumber || "" + }, + staff_entries: newStaffEntries, + charges: invoice.charges || [], + other_charges: invoice.otherCharges || 0, + notes: invoice.notes || "", + }); + + if (invoice.businessId) { + setSelectedClientId(invoice.businessId); + } + + toast({ + title: "āœ… Invoice Duplicated", + description: `Copied from ${invoice.invoiceNumber} - update dates and details as needed`, + }); + }; + + const { mutate: createTemplate } = useCreateInvoiceTemplate(dataConnect); + const { mutate: deleteTemplate } = useDeleteInvoiceTemplate(dataConnect); + + const handleUseTemplate = (template: any) => { + const newStaffEntries = (template.roles || []).map((entry: any) => ({ + ...entry, + name: "", + date: format(new Date(), 'MM/dd/yyyy'), + worked_hours: 0, + regular_hours: 0, + ot_hours: 0, + dt_hours: 0, + regular_value: 0, + ot_value: 0, + dt_value: 0, + total: 0 + })); + + const newInvoiceNumber = generateInvoiceNumber(template.business?.businessName || '', invoices); + + setFormData((prev: any) => ({ + ...prev, + invoice_number: newInvoiceNumber, + invoice_date: format(new Date(), 'yyyy-MM-dd'), + due_date: format(addDays(new Date(), 45), 'yyyy-MM-dd'), + payment_terms: template.paymentTerms || "NET_45", + hub: template.hub || "", + department: "", + po_reference: template.order?.poReference || "", + from_company: { + name: template.vendor?.companyName || prev.from_company.name, + address: template.vendor?.address || prev.from_company.address, + phone: template.vendor?.phone || prev.from_company.phone, + email: template.vendor?.email || prev.from_company.email, + }, + to_company: { + name: template.business?.businessName || "", + phone: template.business?.phone || "", + email: template.business?.email || "", + address: template.business?.address || "", + manager_name: template.business?.contactName || "", + hub_name: template.hub || "", + vendor_id: template.vendorNumber || "" + }, + staff_entries: newStaffEntries, + charges: template.charges || [], + notes: template.notes || "", + })); + + if (template.businessId) { + setSelectedClientId(template.businessId); + } + + toast({ + title: "āœ… Template Applied", + description: `Applied "${template.name}" - fill in staff names and times`, + }); + }; + + const handleSaveTemplate = async (templateName: string) => { + const selectedBusiness = businesses.find(b => b.id === selectedClientId); + + await createTemplate({ + name: templateName, + ownerId: "00000000-0000-0000-0000-000000000000", // placeholder, usually from auth + businessId: selectedClientId || undefined, + vendorId: "00000000-0000-0000-0000-000000000000", // placeholder + paymentTerms: formData.payment_terms as InovicePaymentTerms, + invoiceNumber: formData.invoice_number, + issueDate: new Date(formData.invoice_date).toISOString(), + dueDate: new Date(formData.due_date).toISOString(), + hub: formData.hub, + managerName: formData.manager, + roles: formData.staff_entries, + charges: formData.charges, + otherCharges: parseFloat(formData.other_charges) || 0, + subtotal: totals.subtotal, + amount: totals.grandTotal, + notes: formData.notes, + staffCount: formData.staff_entries.length, + chargesCount: formData.charges.length, + }); + + refetchTemplates(); + + toast({ + title: "āœ… Template Saved", + description: `"${templateName}" can now be reused for future invoices`, + }); + }; + + const handleDeleteTemplate = async (templateId: string) => { + await deleteTemplate({ id: templateId }); + refetchTemplates(); + toast({ + title: "Template Deleted", + description: "Template has been removed", + }); + }; + + const parseTimeToMinutes = (timeStr: string) => { + if (!timeStr || timeStr === "hh:mm") return null; + const match = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i); + if (!match) return null; + let hours = parseInt(match[1]); + const minutes = parseInt(match[2]); + const period = match[3].toUpperCase(); + if (period === "PM" && hours !== 12) hours += 12; + if (period === "AM" && hours === 12) hours = 0; + return hours * 60 + minutes; + }; + + const calculateWorkedHours = (checkIn: string, checkOut: string, lunch: string | number) => { + const startMinutes = parseTimeToMinutes(checkIn); + const endMinutes = parseTimeToMinutes(checkOut); + if (startMinutes === null || endMinutes === null) return 0; + let totalMinutes = endMinutes - startMinutes; + if (totalMinutes < 0) totalMinutes += 24 * 60; + + if (lunch !== "NB" && (typeof lunch === 'number' ? lunch : parseInt(lunch)) >= 20) { + totalMinutes -= (typeof lunch === 'number' ? lunch : parseInt(lunch)); + } + return Math.max(0, totalMinutes / 60); + }; + + const calculateBillableHours = (checkIn: string, checkOut: string, lunch: string | number) => { + const startMinutes = parseTimeToMinutes(checkIn); + const endMinutes = parseTimeToMinutes(checkOut); + if (startMinutes === null || endMinutes === null) return { total: 0, nbPenalty: 0 }; + let totalMinutes = endMinutes - startMinutes; + if (totalMinutes < 0) totalMinutes += 24 * 60; + + let nbPenalty = 0; + if (lunch === "NB") { + nbPenalty = 1; + } else if ((typeof lunch === 'number' ? lunch : parseInt(lunch)) >= 20) { + totalMinutes -= (typeof lunch === 'number' ? lunch : parseInt(lunch)); + } + return { total: Math.max(0, totalMinutes / 60), nbPenalty }; + }; + + const handlePositionChange = (index: number, position: string) => { + const rate = getRateForPosition(position); + const newEntries = [...formData.staff_entries]; + newEntries[index] = { + ...newEntries[index], + position: position, + rate: rate || newEntries[index].rate + }; + + const entry = newEntries[index]; + entry.regular_value = (entry.regular_hours || 0) * (entry.rate || 0); + entry.ot_value = (entry.ot_hours || 0) * (entry.rate || 0) * 1.5; + entry.dt_value = (entry.dt_hours || 0) * (entry.rate || 0) * 2; + entry.total = entry.regular_value + entry.ot_value + entry.dt_value; + + setFormData({ ...formData, staff_entries: newEntries }); + }; + + const handleStaffChange = (index: number, field: string, value: any) => { + const newEntries = [...formData.staff_entries]; + newEntries[index] = { ...newEntries[index], [field]: value }; + + const entry = newEntries[index]; + + if (['check_in', 'check_out', 'lunch'].includes(field)) { + const workedHours = calculateWorkedHours(entry.check_in, entry.check_out, entry.lunch); + const { total: billableHours, nbPenalty } = calculateBillableHours(entry.check_in, entry.check_out, entry.lunch); + entry.worked_hours = parseFloat(workedHours.toFixed(2)); + + if (billableHours <= 8) { + entry.regular_hours = parseFloat(billableHours.toFixed(2)); + entry.ot_hours = 0; + entry.dt_hours = 0; + } else if (billableHours <= 12) { + entry.regular_hours = 8; + entry.ot_hours = parseFloat((billableHours - 8).toFixed(2)); + entry.dt_hours = 0; + } else { + entry.regular_hours = 8; + entry.ot_hours = 4; + entry.dt_hours = parseFloat((billableHours - 12).toFixed(2)); + } + + if (nbPenalty > 0) { + entry.regular_hours = parseFloat((entry.regular_hours + nbPenalty).toFixed(2)); + } + } + + if (['check_in', 'check_out', 'lunch', 'worked_hours', 'regular_hours', 'ot_hours', 'dt_hours', 'rate'].includes(field)) { + entry.regular_value = (entry.regular_hours || 0) * (entry.rate || 0); + entry.ot_value = (entry.ot_hours || 0) * (entry.rate || 0) * 1.5; + entry.dt_value = (entry.dt_hours || 0) * (entry.rate || 0) * 2; + entry.total = entry.regular_value + entry.ot_value + entry.dt_value; + } + + setFormData({ ...formData, staff_entries: newEntries }); + }; + + const handleChargeChange = (index: number, field: string, value: any) => { + const newCharges = [...formData.charges]; + newCharges[index] = { ...newCharges[index], [field]: value }; + + if (['qty', 'rate'].includes(field)) { + newCharges[index].price = (newCharges[index].qty || 0) * (newCharges[index].rate || 0); + } + + setFormData({ ...formData, charges: newCharges }); + }; + + const handleRemoveStaff = (index: number) => { + setFormData({ + ...formData, + staff_entries: formData.staff_entries.filter((_: any, i: number) => i !== index) + }); + }; + + const handleRemoveCharge = (index: number) => { + setFormData({ + ...formData, + charges: formData.charges.filter((_: any, i: number) => i !== index) + }); + }; + + const handleTimeSelect = (entryIndex: number, field: string) => { + const timeString = `${selectedTime.hours}:${selectedTime.minutes} ${selectedTime.period}`; + handleStaffChange(entryIndex, field, timeString); + setTimePickerOpen(null); + }; + + const handleAddStaffEntry = () => { + setFormData({ + ...formData, + staff_entries: [ + ...formData.staff_entries, + { + name: "", + date: format(new Date(), 'MM/dd/yyyy'), + position: "", + check_in: "hh:mm", + lunch: 0, + check_out: "", + worked_hours: 0, + regular_hours: 0, + ot_hours: 0, + dt_hours: 0, + rate: 0, + regular_value: 0, + ot_value: 0, + dt_value: 0, + total: 0 + } + ] + }); + }; + + const handleAddCharge = () => { + setFormData({ + ...formData, + charges: [ + ...formData.charges, + { + name: "Gas Compensation", + qty: 1, + rate: 0, + price: 0 + } + ] + }); + }; + + const calculateTotals = () => { + const staffTotal = formData.staff_entries.reduce((sum: number, entry: any) => sum + (entry.total || 0), 0); + const chargesTotal = formData.charges.reduce((sum: number, charge: any) => sum + (charge.price || 0), 0); + const subtotal = staffTotal + chargesTotal; + const otherCharges = parseFloat(formData.other_charges) || 0; + const grandTotal = subtotal + otherCharges; + + return { subtotal, otherCharges, grandTotal }; + }; + + const totals = calculateTotals(); + + const { mutateAsync: createInvoice, isPending: creating } = useCreateInvoice(dataConnect); + const { mutateAsync: updateInvoice, isPending: updating } = useUpdateInvoice(dataConnect); + + const handleSave = async (statusOverride?: string) => { + const data = formData; + const staffTotal = data.staff_entries.reduce((sum: number, entry: any) => sum + (entry.total || 0), 0); + const chargesTotal = data.charges.reduce((sum: number, charge: any) => sum + ((charge.qty * charge.rate) || 0), 0); + const subtotal = staffTotal + chargesTotal; + const total = subtotal + (parseFloat(data.other_charges) || 0); + + const invoiceStatus = (statusOverride || (existingInvoice?.status === InvoiceStatus.DISPUTED ? InvoiceStatus.PENDING_REVIEW : (existingInvoice?.status || InvoiceStatus.DRAFT))) as InvoiceStatus; + + const payload = { + status: invoiceStatus, + vendorId: "00000000-0000-0000-0000-000000000000", // placeholder + businessId: selectedClientId || "00000000-0000-0000-0000-000000000000", // placeholder + orderId: data.event_id || "00000000-0000-0000-0000-000000000000", // placeholder + paymentTerms: data.payment_terms as InovicePaymentTerms, + invoiceNumber: data.invoice_number, + issueDate: new Date(data.invoice_date).toISOString(), + dueDate: new Date(data.due_date).toISOString(), + hub: data.hub, + managerName: data.manager, + vendorNumber: data.vendor_id, + roles: data.staff_entries, + charges: data.charges, + subtotal: subtotal, + otherCharges: parseFloat(data.other_charges) || 0, + amount: total, + notes: data.notes, + staffCount: data.staff_entries.length, + chargesCount: data.charges.length, + }; + + try { + if (effectiveInvoiceId) { + await updateInvoice({ id: effectiveInvoiceId, ...payload }); + } else { + await createInvoice(payload); + } + + toast({ + title: isEdit ? "āœ… Invoice Updated" : "āœ… Invoice Created", + description: isEdit ? "Invoice has been updated successfully" : "Invoice has been created successfully", + }); + navigate("/invoices"); + } catch (error) { + console.error("Error saving invoice:", error); + toast({ + title: "āŒ Error", + description: "Failed to save invoice. Please try again.", + variant: "destructive" + }); + } + }; + + const isSaving = creating || updating; + + // Mock email sending + const handleSendToClient = async () => { + const invoiceData = { ...formData, status: InvoiceStatus.PENDING_REVIEW }; + await handleSave(InvoiceStatus.PENDING_REVIEW); + + // Simulate email + await new Promise(resolve => setTimeout(resolve, 1000)); + + toast({ + title: "šŸ“§ Email Sent", + description: `Invoice emailed to ${invoiceData.to_company?.email || 'client'}`, + }); + }; + + return ( + + {existingInvoice?.status || "Draft"} + + } + backAction={ + + } + > +
+ {/* Dispute Alert Banner */} + {existingInvoice?.status === InvoiceStatus.DISPUTED && disputedIndices.length > 0 && ( +
+
+
+ ! +
+
+

Disputed Items Highlighted

+

+ {disputedIndices.length} line item(s) have been disputed by the client. + Reason: {existingInvoice.dispute_reason || "Not specified"} +

+ {existingInvoice.dispute_details && ( +

+ "{existingInvoice.dispute_details}" +

+ )} +

+ Please review and correct the highlighted rows, then resubmit for approval. +

+
+
+
+ )} + +
+ {/* Quick Actions - Simplified */} + {!isEdit && ( +
+
+
+ +
+
+

Import from Event

+

Quickly fill invoice from a completed event's shifts

+
+
+ +
+ )} + + {/* Client Selection - Top Section */} + {!isEdit && ( +
+
+
+ +
+
+

Select Client

+

Choose a client to auto-fill company details and rates

+
+
+ +
+ )} + + {/* Invoice Details Header */} +
+
+
+
+ +
+
+

Invoice Details

+
+ + Event: {formData.event_name || "Internal Support"} +
+
+
+ +
+
Invoice Number
+
{formData.invoice_number}
+
+ +
+
+ + setFormData({ ...formData, invoice_date: e.target.value })} + className="mt-1" + /> +
+
+ + setFormData({ ...formData, due_date: e.target.value })} + className="mt-1" + /> +
+
+ +
+
+ + setFormData({ ...formData, hub: e.target.value })} + placeholder="Hub" + className="mt-1" + /> +
+
+ + setFormData({ ...formData, manager: e.target.value })} + placeholder="Manager Name" + className="mt-1" + /> +
+
+ +
+ + setFormData({ ...formData, vendor_id: e.target.value })} + placeholder="Vendor #" + className="mt-1" + /> +
+
+ +
+
+ +
+ {[ + { label: "30 days", value: "NET_30", days: 30 }, + { label: "45 days", value: "NET_45", days: 45 }, + { label: "60 days", value: "NET_60", days: 60 } + ].map(term => ( + setFormData({ + ...formData, + payment_terms: term.value, + due_date: format(addDays(new Date(formData.invoice_date), term.days), 'yyyy-MM-dd') + })} + > + {term.label} + + ))} +
+
+ +
+
+ Department: + setFormData({ ...formData, department: e.target.value })} + placeholder="Dept Code" + className="h-9 w-full md:w-48" + /> +
+
+ PO#: + setFormData({ ...formData, po_reference: e.target.value })} + placeholder="PO Number" + className="h-9 w-full md:w-48" + /> +
+
+
+
+ + {/* From and To - Compact */} +
+ {/* From Company */} +
+
+
F
+ From (Vendor) +
+ {/* Inputs for from_company */} +
+ {['name', 'address', 'phone', 'email'].map(field => ( +
+ setFormData({ ...formData, from_company: { ...formData.from_company, [field]: e.target.value } })} + className="w-full text-sm font-medium text-primary-text bg-transparent border-0 border-b border-transparent hover:border-border focus:border-primary focus:ring-0 px-0 py-1 transition-all placeholder:text-muted-foreground/50" + placeholder={field.charAt(0).toUpperCase() + field.slice(1)} + /> + {field === 'email' && } + {field === 'phone' && } +
+ ))} +
+
+ + {/* To Company */} +
+
+
T
+ To (Client) +
+
+ {['name', 'hub_name', 'department', 'po_reference', 'address', 'phone', 'email'].map(field => ( +
+ { + if (field === 'department' || field === 'po_reference') { + setFormData({ ...formData, [field]: e.target.value }); + } else { + setFormData({ ...formData, to_company: { ...formData.to_company, [field]: e.target.value } }); + } + }} + placeholder={field.replace('_', ' ').charAt(0).toUpperCase() + field.slice(1)} + className="w-full text-sm text-secondary-text bg-transparent border-0 border-b border-transparent hover:border-border focus:border-primary focus:ring-0 px-0 py-1 transition-all placeholder:text-muted-foreground/50" + /> +
+ ))} +
+
+
+ + {/* Staff Table */} +
+
+
+
+ +
+
+

Staff Entries

+

{formData.staff_entries.length} entries

+
+
+ +
+ + +
+ + + + # + Name + Position + ClockIn + Lunch + Checkout + Wrkd + Reg + OT + DT + Rate + Reg$ + OT$ + DT$ + Total + + + + + {formData.staff_entries.map((entry: any, idx: number) => ( + + {idx + 1} + + + + + + + { }} + /> +
+ {staffDirectory.map(staff => ( + + ))} +
+
+
+
+ + + + + setTimePickerOpen(open ? `checkin-${idx}` : null)}> + + + + +
+
+ + +
+ {['AM', 'PM'].map(p => ( + + ))} +
+
+ +
+
+
+
+ + + + + setTimePickerOpen(open ? `checkout-${idx}` : null)}> + + + + +
+
+ + +
+ {['AM', 'PM'].map(p => ( + + ))} +
+
+ +
+
+
+
+ + handleStaffChange(idx, 'worked_hours', parseFloat(e.target.value))} className="h-8 w-14 text-xs px-1 text-center font-medium bg-muted/20 border-0" /> + + + handleStaffChange(idx, 'regular_hours', parseFloat(e.target.value))} className="h-8 w-12 text-xs px-1 text-center" /> + + + handleStaffChange(idx, 'ot_hours', parseFloat(e.target.value))} className="h-8 w-12 text-xs px-1 text-center" /> + + + handleStaffChange(idx, 'dt_hours', parseFloat(e.target.value))} className="h-8 w-12 text-xs px-1 text-center" /> + + + handleStaffChange(idx, 'rate', parseFloat(e.target.value))} className="h-8 w-14 text-xs px-1 text-right" /> + + ${entry.regular_value?.toFixed(0) || "0"} + ${entry.ot_value?.toFixed(0) || "0"} + ${entry.dt_value?.toFixed(0) || "0"} + ${entry.total?.toFixed(2) || "0"} + + + +
+ ))} +
+
+
+
+
+ + {/* Charges */} +
+
+
+
+ šŸ’° +
+
+

Additional Charges

+

{formData.charges.length} charges

+
+
+ +
+ + +
+ + + + # + Name + QTY + Rate + Price + Actions + + + + {formData.charges.map((charge: any, idx: number) => ( + + {idx + 1} + + handleChargeChange(idx, 'name', e.target.value)} className="h-8 max-w-md" /> + + + handleChargeChange(idx, 'qty', parseFloat(e.target.value))} className="h-8 w-20" /> + + + handleChargeChange(idx, 'rate', parseFloat(e.target.value))} className="h-8 w-24" /> + + ${charge.price?.toFixed(2) || "0.00"} + + + + + ))} + +
+
+
+
+ + {/* Totals */} +
+
+
+
+ Sub total: + ${totals.subtotal.toFixed(2)} +
+
+ Other charges: + setFormData({ ...formData, other_charges: e.target.value })} className="h-8 w-32 text-right bg-white" /> +
+
+ Grand total: + ${totals.grandTotal.toFixed(2)} +
+
+
+
+ + {/* Notes */} +
+ +