feat: Refactor mobile UI components to adhere to design system tokens and improve loading and snackbar functionalities
This commit is contained in:
@@ -28,10 +28,13 @@
|
|||||||
|
|
||||||
- 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.
|
- 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).
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- rethink the order status, we need to simplify it.
|
||||||
|
|
||||||
- Validation layer
|
- Validation layer
|
||||||
- Profile info
|
- Profile info
|
||||||
- emergency contact
|
- emergency contact
|
||||||
@@ -43,4 +46,8 @@
|
|||||||
- there should be manual verification by the client even if the ai verification is passed.
|
- there should be manual verification by the client even if the ai verification is passed.
|
||||||
- to track false positives and false negatives.
|
- to track false positives and false negatives.
|
||||||
- documents
|
- documents
|
||||||
- tax forms
|
- 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.
|
||||||
@@ -12,3 +12,5 @@ export 'src/widgets/ui_button.dart';
|
|||||||
export 'src/widgets/ui_chip.dart';
|
export 'src/widgets/ui_chip.dart';
|
||||||
export 'src/widgets/ui_error_snackbar.dart';
|
export 'src/widgets/ui_error_snackbar.dart';
|
||||||
export 'src/widgets/ui_success_snackbar.dart';
|
export 'src/widgets/ui_success_snackbar.dart';
|
||||||
|
export 'src/widgets/ui_loading_page.dart';
|
||||||
|
export 'src/widgets/ui_snackbar.dart';
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class UiColors {
|
|||||||
static const Color textDescription = mutedForeground;
|
static const Color textDescription = mutedForeground;
|
||||||
|
|
||||||
/// Success text (#10B981)
|
/// Success text (#10B981)
|
||||||
static const Color textSuccess = Color(0xFF10B981);
|
static const Color textSuccess = Color(0xFF0A8159);
|
||||||
|
|
||||||
/// Error text (#F04444)
|
/// Error text (#F04444)
|
||||||
static const Color textError = destructive;
|
static const Color textError = destructive;
|
||||||
|
|||||||
@@ -225,4 +225,13 @@ class UiIcons {
|
|||||||
|
|
||||||
/// Globe icon
|
/// Globe icon
|
||||||
static const IconData globe = _IconLib.globe;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: <Widget>[
|
||||||
|
// 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()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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: <Widget>[
|
||||||
|
Icon(icon, color: textColor, size: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
message,
|
||||||
|
style: UiTypography.body2b.copyWith(color: textColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension;
|
import 'package:flutter_modular/flutter_modular.dart'
|
||||||
|
hide ModularWatchExtension;
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:lucide_icons/lucide_icons.dart';
|
|
||||||
|
|
||||||
import '../blocs/availability_bloc.dart';
|
import '../blocs/availability_bloc.dart';
|
||||||
import '../blocs/availability_event.dart';
|
import '../blocs/availability_event.dart';
|
||||||
@@ -44,7 +44,6 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _bloc,
|
value: _bloc,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: AppColors.krowBackground,
|
|
||||||
appBar: UiAppBar(
|
appBar: UiAppBar(
|
||||||
title: 'My Availability',
|
title: 'My Availability',
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
@@ -53,13 +52,18 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
body: BlocListener<AvailabilityBloc, AvailabilityState>(
|
body: BlocListener<AvailabilityBloc, AvailabilityState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is AvailabilityLoaded && state.successMessage != null) {
|
if (state is AvailabilityLoaded && state.successMessage != null) {
|
||||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
UiSnackbar.show(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
context,
|
||||||
SnackBar(
|
message: state.successMessage!,
|
||||||
content: Text(state.successMessage!),
|
type: UiSnackbarType.success,
|
||||||
backgroundColor: Colors.green,
|
);
|
||||||
behavior: SnackBarBehavior.floating,
|
}
|
||||||
),
|
|
||||||
|
if (state is AvailabilityError) {
|
||||||
|
UiSnackbar.show(
|
||||||
|
context,
|
||||||
|
message: state.message,
|
||||||
|
type: UiSnackbarType.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -78,16 +82,14 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 24,
|
||||||
children: [
|
children: [
|
||||||
_buildQuickSet(context),
|
_buildQuickSet(context),
|
||||||
const SizedBox(height: 24),
|
|
||||||
_buildWeekNavigation(context, state),
|
_buildWeekNavigation(context, state),
|
||||||
const SizedBox(height: 24),
|
|
||||||
_buildSelectedDayAvailability(
|
_buildSelectedDayAvailability(
|
||||||
context,
|
context,
|
||||||
state.selectedDayAvailability,
|
state.selectedDayAvailability,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
|
||||||
_buildInfoCard(),
|
_buildInfoCard(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -96,12 +98,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (state.isActionInProgress)
|
if (state.isActionInProgress)
|
||||||
Container(
|
const UiLoadingPage(), // Show loading overlay during actions
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
child: const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else if (state is AvailabilityError) {
|
} else if (state is AvailabilityError) {
|
||||||
@@ -119,45 +116,27 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.krowBlue.withOpacity(0.1),
|
color: UiColors.primary.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'Quick Set Availability',
|
'Quick Set Availability',
|
||||||
style: TextStyle(
|
style: UiTypography.body4r.copyWith(color: UiColors.textFilter),
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Color(0xFF333F48),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
Expanded(child: _buildQuickSetButton(context, 'All Week', 'all')),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuickSetButton(
|
child: _buildQuickSetButton(context, 'Weekdays', 'weekdays'),
|
||||||
context,
|
|
||||||
'All Week',
|
|
||||||
'all',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuickSetButton(
|
child: _buildQuickSetButton(context, 'Weekends', 'weekends'),
|
||||||
context,
|
|
||||||
'Weekdays',
|
|
||||||
'weekdays',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: _buildQuickSetButton(
|
|
||||||
context,
|
|
||||||
'Weekends',
|
|
||||||
'weekends',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -184,23 +163,24 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 32,
|
height: 32,
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () => context.read<AvailabilityBloc>().add(PerformQuickSet(type)),
|
onPressed: () =>
|
||||||
|
context.read<AvailabilityBloc>().add(PerformQuickSet(type)),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: isDestructive
|
color: isDestructive
|
||||||
? Colors.red.withOpacity(0.2)
|
? UiColors.destructive.withValues(alpha: 0.2)
|
||||||
: AppColors.krowBlue.withOpacity(0.2),
|
: UiColors.primary.withValues(alpha: 0.2),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
foregroundColor: isDestructive ? Colors.red : AppColors.krowBlue,
|
foregroundColor: isDestructive ? UiColors.destructive : UiColors.primary,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w500),
|
style: UiTypography.body4r,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -221,7 +201,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
border: Border.all(color: Colors.grey.shade100),
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: UiColors.black.withValues(alpha: 0.05),
|
||||||
blurRadius: 2,
|
blurRadius: 2,
|
||||||
offset: const Offset(0, 1),
|
offset: const Offset(0, 1),
|
||||||
),
|
),
|
||||||
@@ -236,20 +216,24 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildNavButton(
|
_buildNavButton(
|
||||||
LucideIcons.chevronLeft,
|
UiIcons.chevronLeft,
|
||||||
() => context.read<AvailabilityBloc>().add(const NavigateWeek(-1)),
|
() => context.read<AvailabilityBloc>().add(
|
||||||
|
const NavigateWeek(-1),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
monthYear,
|
monthYear,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppColors.krowCharcoal,
|
color: UiColors.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildNavButton(
|
_buildNavButton(
|
||||||
LucideIcons.chevronRight,
|
UiIcons.chevronRight,
|
||||||
() => context.read<AvailabilityBloc>().add(const NavigateWeek(1)),
|
() => context.read<AvailabilityBloc>().add(
|
||||||
|
const NavigateWeek(1),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -257,7 +241,9 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
// Days Row
|
// Days Row
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
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(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -274,12 +260,16 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
color: Color(0xFFF1F5F9), // slate-100
|
color: Color(0xFFF1F5F9), // slate-100
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Icon(icon, size: 20, color: AppColors.krowMuted),
|
child: Icon(icon, size: 20, color: UiColors.mutedForeground),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDayItem(BuildContext context, DayAvailability day, DateTime selectedDate) {
|
Widget _buildDayItem(
|
||||||
|
BuildContext context,
|
||||||
|
DayAvailability day,
|
||||||
|
DateTime selectedDate,
|
||||||
|
) {
|
||||||
final isSelected = AvailabilityLoaded.isSameDay(day.date, selectedDate);
|
final isSelected = AvailabilityLoaded.isSameDay(day.date, selectedDate);
|
||||||
final isAvailable = day.isAvailable;
|
final isAvailable = day.isAvailable;
|
||||||
final isToday = AvailabilityLoaded.isSameDay(day.date, DateTime.now());
|
final isToday = AvailabilityLoaded.isSameDay(day.date, DateTime.now());
|
||||||
@@ -292,22 +282,22 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColors.krowBlue
|
? UiColors.primary
|
||||||
: (isAvailable
|
: (isAvailable
|
||||||
? const Color(0xFFECFDF5)
|
? const Color(0xFFECFDF5)
|
||||||
: const Color(0xFFF8FAFC)), // emerald-50 or slate-50
|
: const Color(0xFFF8FAFC)), // emerald-50 or slate-50
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColors.krowBlue
|
? UiColors.primary
|
||||||
: (isAvailable
|
: (isAvailable
|
||||||
? const Color(0xFFA7F3D0)
|
? const Color(0xFFA7F3D0)
|
||||||
: Colors.transparent), // emerald-200
|
: Colors.transparent), // emerald-200
|
||||||
),
|
),
|
||||||
boxShadow: isSelected
|
boxShadow: isSelected
|
||||||
? [
|
? [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: AppColors.krowBlue.withOpacity(0.3),
|
color: UiColors.primary.withValues(alpha: 0.3),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 4),
|
||||||
),
|
),
|
||||||
@@ -326,22 +316,22 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white
|
? UiColors.white
|
||||||
: (isAvailable
|
: (isAvailable
|
||||||
? const Color(0xFF047857)
|
? const Color(0xFF047857)
|
||||||
: AppColors.krowMuted), // emerald-700
|
: UiColors.mutedForeground), // emerald-700
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
DateFormat('EEE').format(day.date),
|
DateFormat('EEE').format(day.date),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white.withOpacity(0.8)
|
? UiColors.white.withValues(alpha: 0.8)
|
||||||
: (isAvailable
|
: (isAvailable
|
||||||
? const Color(0xFF047857)
|
? const Color(0xFF047857)
|
||||||
: AppColors.krowMuted),
|
: UiColors.mutedForeground),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -353,7 +343,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: AppColors.krowBlue,
|
color: UiColors.primary,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -380,7 +370,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
border: Border.all(color: Colors.grey.shade100),
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: UiColors.black.withValues(alpha: 0.05),
|
||||||
blurRadius: 2,
|
blurRadius: 2,
|
||||||
offset: const Offset(0, 1),
|
offset: const Offset(0, 1),
|
||||||
),
|
),
|
||||||
@@ -400,22 +390,23 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppColors.krowCharcoal,
|
color: UiColors.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
isAvailable ? 'You are available' : 'Not available',
|
isAvailable ? 'You are available' : 'Not available',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: AppColors.krowMuted,
|
color: UiColors.mutedForeground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Switch(
|
Switch(
|
||||||
value: isAvailable,
|
value: isAvailable,
|
||||||
onChanged: (val) => context.read<AvailabilityBloc>().add(ToggleDayStatus(day)),
|
onChanged: (val) =>
|
||||||
activeColor: AppColors.krowBlue,
|
context.read<AvailabilityBloc>().add(ToggleDayStatus(day)),
|
||||||
|
activeColor: UiColors.primary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -426,37 +417,37 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
...day.slots.map((slot) {
|
...day.slots.map((slot) {
|
||||||
// Get UI config for this slot ID
|
// Get UI config for this slot ID
|
||||||
final uiConfig = _getSlotUiConfig(slot.id);
|
final uiConfig = _getSlotUiConfig(slot.id);
|
||||||
|
|
||||||
return _buildTimeSlotItem(context, day, slot, uiConfig);
|
return _buildTimeSlotItem(context, day, slot, uiConfig);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _getSlotUiConfig(String slotId) {
|
Map<String, dynamic> _getSlotUiConfig(String slotId) {
|
||||||
switch (slotId) {
|
switch (slotId) {
|
||||||
case 'morning':
|
case 'morning':
|
||||||
return {
|
return {
|
||||||
'icon': LucideIcons.sunrise,
|
'icon': UiIcons.sunrise,
|
||||||
'bg': const Color(0xFFE6EBF9), // bg-[#0032A0]/10
|
'bg': const Color(0xFFE6EBF9), // bg-[#0032A0]/10
|
||||||
'iconColor': const Color(0xFF0032A0),
|
'iconColor': const Color(0xFF0032A0),
|
||||||
};
|
};
|
||||||
case 'afternoon':
|
case 'afternoon':
|
||||||
return {
|
return {
|
||||||
'icon': LucideIcons.sun,
|
'icon': UiIcons.sun,
|
||||||
'bg': const Color(0xFFCCD6EC), // bg-[#0032A0]/20
|
'bg': const Color(0xFFCCD6EC), // bg-[#0032A0]/20
|
||||||
'iconColor': const Color(0xFF0032A0),
|
'iconColor': const Color(0xFF0032A0),
|
||||||
};
|
};
|
||||||
case 'evening':
|
case 'evening':
|
||||||
return {
|
return {
|
||||||
'icon': LucideIcons.moon,
|
'icon': UiIcons.moon,
|
||||||
'bg': const Color(0xFFEBEDEE), // bg-[#333F48]/10
|
'bg': const Color(0xFFEBEDEE), // bg-[#333F48]/10
|
||||||
'iconColor': const Color(0xFF333F48),
|
'iconColor': const Color(0xFF333F48),
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
'icon': LucideIcons.clock,
|
'icon': UiIcons.clock,
|
||||||
'bg': Colors.grey.shade100,
|
'bg': Colors.grey.shade100,
|
||||||
'iconColor': Colors.grey,
|
'iconColor': Colors.grey,
|
||||||
};
|
};
|
||||||
@@ -464,15 +455,15 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTimeSlotItem(
|
Widget _buildTimeSlotItem(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
DayAvailability day,
|
DayAvailability day,
|
||||||
AvailabilitySlot slot,
|
AvailabilitySlot slot,
|
||||||
Map<String, dynamic> uiConfig
|
Map<String, dynamic> uiConfig,
|
||||||
) {
|
) {
|
||||||
// Determine styles based on state
|
// Determine styles based on state
|
||||||
final isEnabled = day.isAvailable;
|
final isEnabled = day.isAvailable;
|
||||||
final isActive = slot.isAvailable;
|
final isActive = slot.isAvailable;
|
||||||
|
|
||||||
// Container style
|
// Container style
|
||||||
Color bgColor;
|
Color bgColor;
|
||||||
Color borderColor;
|
Color borderColor;
|
||||||
@@ -481,8 +472,8 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
bgColor = const Color(0xFFF8FAFC); // slate-50
|
bgColor = const Color(0xFFF8FAFC); // slate-50
|
||||||
borderColor = const Color(0xFFF1F5F9); // slate-100
|
borderColor = const Color(0xFFF1F5F9); // slate-100
|
||||||
} else if (isActive) {
|
} else if (isActive) {
|
||||||
bgColor = AppColors.krowBlue.withOpacity(0.05);
|
bgColor = UiColors.primary.withValues(alpha: 0.05);
|
||||||
borderColor = AppColors.krowBlue.withOpacity(0.2);
|
borderColor = UiColors.primary.withValues(alpha: 0.2);
|
||||||
} else {
|
} else {
|
||||||
bgColor = const Color(0xFFF8FAFC); // slate-50
|
bgColor = const Color(0xFFF8FAFC); // slate-50
|
||||||
borderColor = const Color(0xFFE2E8F0); // slate-200
|
borderColor = const Color(0xFFE2E8F0); // slate-200
|
||||||
@@ -490,14 +481,18 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
|
|
||||||
// Text colors
|
// Text colors
|
||||||
final titleColor = (isEnabled && isActive)
|
final titleColor = (isEnabled && isActive)
|
||||||
? AppColors.krowCharcoal
|
? UiColors.foreground
|
||||||
: AppColors.krowMuted;
|
: UiColors.mutedForeground;
|
||||||
final subtitleColor = (isEnabled && isActive)
|
final subtitleColor = (isEnabled && isActive)
|
||||||
? AppColors.krowMuted
|
? UiColors.mutedForeground
|
||||||
: Colors.grey.shade400;
|
: Colors.grey.shade400;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: isEnabled ? () => context.read<AvailabilityBloc>().add(ToggleSlotStatus(day, slot.id)) : null,
|
onTap: isEnabled
|
||||||
|
? () => context.read<AvailabilityBloc>().add(
|
||||||
|
ToggleSlotStatus(day, slot.id),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
@@ -539,10 +534,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
slot.timeRange,
|
slot.timeRange,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 12, color: subtitleColor),
|
||||||
fontSize: 12,
|
|
||||||
color: subtitleColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -553,14 +545,10 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: AppColors.krowBlue,
|
color: UiColors.primary,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(UiIcons.check, size: 16, color: UiColors.white),
|
||||||
LucideIcons.check,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
else if (isEnabled && !isActive)
|
else if (isEnabled && !isActive)
|
||||||
Container(
|
Container(
|
||||||
@@ -584,13 +572,13 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.krowBlue.withOpacity(0.05),
|
color: UiColors.primary.withValues(alpha: 0.05),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: const Row(
|
child: const Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(LucideIcons.clock, size: 20, color: AppColors.krowBlue),
|
Icon(UiIcons.clock, size: 20, color: UiColors.primary),
|
||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -601,13 +589,13 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: AppColors.krowCharcoal,
|
color: UiColors.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 2),
|
SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
"When enabled, you'll only be matched with shifts during your available times.",
|
"When enabled, you'll only be matched with shifts during your available times.",
|
||||||
style: TextStyle(fontSize: 12, color: AppColors.krowMuted),
|
style: TextStyle(fontSize: 12, color: UiColors.mutedForeground),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -618,14 +606,3 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
name: staff_availability
|
name: staff_availability
|
||||||
description: Staff Availability Feature
|
description: Staff Availability Feature
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
publish_to: 'none'
|
publish_to: "none"
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.10.0 <4.0.0'
|
sdk: ">=3.10.0 <4.0.0"
|
||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: 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
|
# Internal packages
|
||||||
core_localization:
|
core_localization:
|
||||||
path: ../../../core_localization
|
path: ../../../core_localization
|
||||||
@@ -28,6 +23,11 @@ dependencies:
|
|||||||
path: ../../../data_connect
|
path: ../../../data_connect
|
||||||
krow_core:
|
krow_core:
|
||||||
path: ../../../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_data_connect: ^0.2.2+2
|
||||||
firebase_auth: ^6.1.4
|
firebase_auth: ^6.1.4
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user