feat: add staff payments feature with mock data source and UI components
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../domain/entities/payment_summary.dart';
|
||||
import '../../domain/entities/payment_transaction.dart';
|
||||
import 'payments_remote_datasource.dart';
|
||||
|
||||
class PaymentsMockDataSource implements PaymentsRemoteDataSource {
|
||||
@override
|
||||
Future<PaymentSummary> fetchPaymentSummary() async {
|
||||
// Simulate network delay
|
||||
await Future.delayed(const Duration(milliseconds: 800));
|
||||
|
||||
// Mock data matching the prototype
|
||||
return const PaymentSummary(
|
||||
weeklyEarnings: 847.50,
|
||||
monthlyEarnings: 3240.0,
|
||||
pendingEarnings: 285.0,
|
||||
totalEarnings: 12450.0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PaymentTransaction>> fetchPaymentHistory(String period) async {
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
|
||||
// Mock data matching the prototype
|
||||
// In a real scenario, this would filter by 'period' (week/month/year)
|
||||
return [
|
||||
PaymentTransaction(
|
||||
id: '1',
|
||||
title: 'Cook',
|
||||
location: 'LA Convention Center',
|
||||
address: '1201 S Figueroa St, Los Angeles, CA 90015',
|
||||
workedTime: '2:00 PM - 10:00 PM',
|
||||
amount: 160.00,
|
||||
status: 'PAID',
|
||||
hours: 8,
|
||||
rate: 20.0,
|
||||
date: DateTime(2025, 12, 6), // "Sat, Dec 6" (Using future date to match context if needed, but keeping it simple)
|
||||
),
|
||||
PaymentTransaction(
|
||||
id: '2',
|
||||
title: 'Server',
|
||||
location: 'The Grand Hotel',
|
||||
address: '456 Main St, Los Angeles, CA 90012',
|
||||
workedTime: '5:00 PM - 11:00 PM',
|
||||
amount: 176.00,
|
||||
status: 'PAID',
|
||||
hours: 8,
|
||||
rate: 22.0,
|
||||
date: DateTime(2025, 12, 5), // "Fri, Dec 5"
|
||||
),
|
||||
PaymentTransaction(
|
||||
id: '3',
|
||||
title: 'Bartender',
|
||||
location: 'Club Luxe',
|
||||
address: '789 Sunset Blvd, Los Angeles, CA 90028',
|
||||
workedTime: '6:00 PM - 2:00 AM',
|
||||
amount: 225.00,
|
||||
status: 'PAID',
|
||||
hours: 9,
|
||||
rate: 25.0,
|
||||
date: DateTime(2025, 12, 4), // "Thu, Dec 4"
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import '../../domain/entities/payment_summary.dart';
|
||||
import '../../domain/entities/payment_transaction.dart';
|
||||
|
||||
abstract class PaymentsRemoteDataSource {
|
||||
Future<PaymentSummary> fetchPaymentSummary();
|
||||
Future<List<PaymentTransaction>> fetchPaymentHistory(String period);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// ignore: unused_import
|
||||
// import 'package:data_connect/data_connect.dart';
|
||||
import '../../domain/entities/payment_summary.dart';
|
||||
import '../../domain/entities/payment_transaction.dart';
|
||||
import '../../domain/repositories/payments_repository.dart';
|
||||
import '../datasources/payments_remote_datasource.dart';
|
||||
|
||||
class PaymentsRepositoryImpl implements PaymentsRepository {
|
||||
final PaymentsRemoteDataSource remoteDataSource;
|
||||
|
||||
PaymentsRepositoryImpl({required this.remoteDataSource});
|
||||
|
||||
@override
|
||||
Future<PaymentSummary> getPaymentSummary() async {
|
||||
return await remoteDataSource.fetchPaymentSummary();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PaymentTransaction>> getPaymentHistory(String period) async {
|
||||
return await remoteDataSource.fetchPaymentHistory(period);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class PaymentSummary extends Equatable {
|
||||
final double weeklyEarnings;
|
||||
final double monthlyEarnings;
|
||||
final double pendingEarnings;
|
||||
final double totalEarnings;
|
||||
|
||||
const PaymentSummary({
|
||||
required this.weeklyEarnings,
|
||||
required this.monthlyEarnings,
|
||||
required this.pendingEarnings,
|
||||
required this.totalEarnings,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
weeklyEarnings,
|
||||
monthlyEarnings,
|
||||
pendingEarnings,
|
||||
totalEarnings,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class PaymentTransaction extends Equatable {
|
||||
final String id;
|
||||
final String title;
|
||||
final String location;
|
||||
final String address;
|
||||
final DateTime date;
|
||||
final String workedTime;
|
||||
final double amount;
|
||||
final String status;
|
||||
final int hours;
|
||||
final double rate;
|
||||
|
||||
const PaymentTransaction({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.location,
|
||||
required this.address,
|
||||
required this.date,
|
||||
required this.workedTime,
|
||||
required this.amount,
|
||||
required this.status,
|
||||
required this.hours,
|
||||
required this.rate,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
title,
|
||||
location,
|
||||
address,
|
||||
date,
|
||||
workedTime,
|
||||
amount,
|
||||
status,
|
||||
hours,
|
||||
rate,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import '../entities/payment_summary.dart';
|
||||
import '../entities/payment_transaction.dart';
|
||||
|
||||
abstract class PaymentsRepository {
|
||||
/// Fetches the summary of earnings (weekly, monthly, total, pending).
|
||||
Future<PaymentSummary> getPaymentSummary();
|
||||
|
||||
/// Fetches the list of recent payment transactions (history).
|
||||
Future<List<PaymentTransaction>> getPaymentHistory(String period);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import '../entities/payment_transaction.dart';
|
||||
import '../repositories/payments_repository.dart';
|
||||
|
||||
class GetPaymentHistoryUseCase {
|
||||
final PaymentsRepository repository;
|
||||
|
||||
GetPaymentHistoryUseCase(this.repository);
|
||||
|
||||
Future<List<PaymentTransaction>> call({String period = 'week'}) async {
|
||||
return await repository.getPaymentHistory(period);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import '../entities/payment_summary.dart';
|
||||
import '../repositories/payments_repository.dart';
|
||||
|
||||
class GetPaymentSummaryUseCase {
|
||||
final PaymentsRepository repository;
|
||||
|
||||
GetPaymentSummaryUseCase(this.repository);
|
||||
|
||||
Future<PaymentSummary> call() async {
|
||||
return await repository.getPaymentSummary();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'domain/repositories/payments_repository.dart';
|
||||
import 'domain/usecases/get_payment_summary_usecase.dart';
|
||||
import 'domain/usecases/get_payment_history_usecase.dart';
|
||||
import 'data/datasources/payments_remote_datasource.dart';
|
||||
import 'data/datasources/payments_mock_datasource.dart';
|
||||
import 'data/repositories/payments_repository_impl.dart';
|
||||
import 'presentation/blocs/payments/payments_bloc.dart';
|
||||
import 'presentation/pages/payments_page.dart';
|
||||
|
||||
class StaffPaymentsModule extends Module {
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Data Sources
|
||||
i.add<PaymentsRemoteDataSource>(PaymentsMockDataSource.new);
|
||||
|
||||
// Repositories
|
||||
i.add<PaymentsRepository>(PaymentsRepositoryImpl.new);
|
||||
|
||||
// Use Cases
|
||||
i.add(GetPaymentSummaryUseCase.new);
|
||||
i.add(GetPaymentHistoryUseCase.new);
|
||||
|
||||
// Blocs
|
||||
i.add(PaymentsBloc.new);
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(RouteManager r) {
|
||||
r.child('/', child: (context) => const PaymentsPage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../domain/entities/payment_summary.dart';
|
||||
import '../../../domain/entities/payment_transaction.dart';
|
||||
import '../../../domain/usecases/get_payment_summary_usecase.dart';
|
||||
import '../../../domain/usecases/get_payment_history_usecase.dart';
|
||||
import 'payments_event.dart';
|
||||
import 'payments_state.dart';
|
||||
|
||||
class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
|
||||
final GetPaymentSummaryUseCase getPaymentSummary;
|
||||
final GetPaymentHistoryUseCase getPaymentHistory;
|
||||
|
||||
PaymentsBloc({
|
||||
required this.getPaymentSummary,
|
||||
required this.getPaymentHistory,
|
||||
}) : super(PaymentsInitial()) {
|
||||
on<LoadPaymentsEvent>(_onLoadPayments);
|
||||
on<ChangePeriodEvent>(_onChangePeriod);
|
||||
}
|
||||
|
||||
Future<void> _onLoadPayments(
|
||||
LoadPaymentsEvent event,
|
||||
Emitter<PaymentsState> emit,
|
||||
) async {
|
||||
emit(PaymentsLoading());
|
||||
try {
|
||||
final PaymentSummary summary = await getPaymentSummary();
|
||||
final List<PaymentTransaction> history = await getPaymentHistory(period: 'week');
|
||||
emit(PaymentsLoaded(
|
||||
summary: summary,
|
||||
history: history,
|
||||
activePeriod: 'week',
|
||||
));
|
||||
} catch (e) {
|
||||
emit(PaymentsError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onChangePeriod(
|
||||
ChangePeriodEvent event,
|
||||
Emitter<PaymentsState> emit,
|
||||
) async {
|
||||
final PaymentsState currentState = state;
|
||||
if (currentState is PaymentsLoaded) {
|
||||
if (currentState.activePeriod == event.period) return;
|
||||
|
||||
// Optimistic update or set loading state if expecting delay
|
||||
// For now, we'll keep the current data and fetch new history
|
||||
try {
|
||||
final List<PaymentTransaction> newHistory = await getPaymentHistory(period: event.period);
|
||||
emit(currentState.copyWith(
|
||||
history: newHistory,
|
||||
activePeriod: event.period,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(PaymentsError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class PaymentsEvent extends Equatable {
|
||||
const PaymentsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadPaymentsEvent extends PaymentsEvent {}
|
||||
|
||||
class ChangePeriodEvent extends PaymentsEvent {
|
||||
final String period;
|
||||
|
||||
const ChangePeriodEvent(this.period);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [period];
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../domain/entities/payment_summary.dart';
|
||||
import '../../../domain/entities/payment_transaction.dart';
|
||||
|
||||
abstract class PaymentsState extends Equatable {
|
||||
const PaymentsState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class PaymentsInitial extends PaymentsState {}
|
||||
|
||||
class PaymentsLoading extends PaymentsState {}
|
||||
|
||||
class PaymentsLoaded extends PaymentsState {
|
||||
final PaymentSummary summary;
|
||||
final List<PaymentTransaction> history;
|
||||
final String activePeriod;
|
||||
|
||||
const PaymentsLoaded({
|
||||
required this.summary,
|
||||
required this.history,
|
||||
this.activePeriod = 'week',
|
||||
});
|
||||
|
||||
PaymentsLoaded copyWith({
|
||||
PaymentSummary? summary,
|
||||
List<PaymentTransaction>? history,
|
||||
String? activePeriod,
|
||||
}) {
|
||||
return PaymentsLoaded(
|
||||
summary: summary ?? this.summary,
|
||||
history: history ?? this.history,
|
||||
activePeriod: activePeriod ?? this.activePeriod,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [summary, history, activePeriod];
|
||||
}
|
||||
|
||||
class PaymentsError extends PaymentsState {
|
||||
final String message;
|
||||
|
||||
const PaymentsError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
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 '../../domain/entities/payment_transaction.dart';
|
||||
import '../blocs/payments/payments_bloc.dart';
|
||||
import '../blocs/payments/payments_event.dart';
|
||||
import '../blocs/payments/payments_state.dart';
|
||||
import '../widgets/payment_stats_card.dart';
|
||||
import '../widgets/pending_pay_card.dart';
|
||||
import '../widgets/payment_history_item.dart';
|
||||
|
||||
class PaymentsPage extends StatefulWidget {
|
||||
const PaymentsPage({super.key});
|
||||
|
||||
@override
|
||||
State<PaymentsPage> createState() => _PaymentsPageState();
|
||||
}
|
||||
|
||||
class _PaymentsPageState extends State<PaymentsPage> {
|
||||
final PaymentsBloc _bloc = Modular.get<PaymentsBloc>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bloc.add(LoadPaymentsEvent());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<PaymentsBloc>.value(
|
||||
value: _bloc,
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFF8FAFC),
|
||||
body: BlocBuilder<PaymentsBloc, PaymentsState>(
|
||||
builder: (BuildContext context, PaymentsState state) {
|
||||
if (state is PaymentsLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is PaymentsError) {
|
||||
return Center(child: Text('Error: ${state.message}'));
|
||||
} else if (state is PaymentsLoaded) {
|
||||
return _buildContent(context, state);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, PaymentsLoaded state) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
// Header Section with Gradient
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: <Color>[Color(0xFF0032A0), Color(0xFF333F48)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
20,
|
||||
MediaQuery.of(context).padding.top + 24,
|
||||
20,
|
||||
32,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
"Earnings",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Main Balance
|
||||
Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
"Total Earnings",
|
||||
style: TextStyle(
|
||||
color: Color(0xFFF8E08E),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Period Tabs
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildTab("Week", 'week', state.activePeriod),
|
||||
_buildTab("Month", 'month', state.activePeriod),
|
||||
_buildTab("Year", 'year', state.activePeriod),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Main Content - Offset upwards
|
||||
Transform.translate(
|
||||
offset: const Offset(0, -16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Quick Stats
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: PaymentStatsCard(
|
||||
icon: LucideIcons.trendingUp,
|
||||
iconColor: const Color(0xFF059669),
|
||||
label: "This Week",
|
||||
amount: "\$${state.summary.weeklyEarnings}",
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: PaymentStatsCard(
|
||||
icon: LucideIcons.calendar,
|
||||
iconColor: const Color(0xFF2563EB),
|
||||
label: "This Month",
|
||||
amount: "\$${state.summary.monthlyEarnings.toStringAsFixed(0)}",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Pending Pay
|
||||
PendingPayCard(
|
||||
amount: state.summary.pendingEarnings,
|
||||
onCashOut: () {
|
||||
Modular.to.pushNamed('/early-pay');
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Recent Payments
|
||||
const Text(
|
||||
"Recent Payments",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Column(
|
||||
children: state.history.map((PaymentTransaction payment) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: PaymentHistoryItem(
|
||||
amount: payment.amount,
|
||||
title: payment.title,
|
||||
location: payment.location,
|
||||
address: payment.address,
|
||||
date: DateFormat('E, MMM d').format(payment.date),
|
||||
workedTime: payment.workedTime,
|
||||
hours: payment.hours,
|
||||
rate: payment.rate,
|
||||
status: payment.status,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Export History Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('PDF Exported'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(LucideIcons.download, size: 16),
|
||||
label: const Text("Export History"),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF0F172A),
|
||||
side: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(String label, String value, String activePeriod) {
|
||||
final bool isSelected = activePeriod == value;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _bloc.add(ChangePeriodEvent(value)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? Colors.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isSelected ? const Color(0xFF0032A0) : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
class PaymentHistoryItem extends StatelessWidget {
|
||||
final double amount;
|
||||
final String title;
|
||||
final String location;
|
||||
final String address;
|
||||
final String date;
|
||||
final String workedTime;
|
||||
final int hours;
|
||||
final double rate;
|
||||
final String status;
|
||||
|
||||
const PaymentHistoryItem({
|
||||
super.key,
|
||||
required this.amount,
|
||||
required this.title,
|
||||
required this.location,
|
||||
required this.address,
|
||||
required this.date,
|
||||
required this.workedTime,
|
||||
required this.hours,
|
||||
required this.rate,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Status Badge
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF3B82F6), // blue-500
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
"PAID",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF2563EB), // blue-600
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Icon
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9), // slate-100
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.dollarSign,
|
||||
color: Color(0xFF334155), // slate-700
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Content
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF0F172A), // slate-900
|
||||
),
|
||||
),
|
||||
Text(
|
||||
location,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF475569), // slate-600
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"\$${amount.toStringAsFixed(0)}",
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF0F172A), // slate-900
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"\$${rate.toStringAsFixed(0)}/hr · ${hours}h",
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Color(0xFF64748B), // slate-500
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Date and Time
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
LucideIcons.calendar,
|
||||
size: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
date,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(
|
||||
LucideIcons.clock,
|
||||
size: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
workedTime,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// Address
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
LucideIcons.mapPin,
|
||||
size: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
address,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
class PaymentStatsCard extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String amount;
|
||||
|
||||
const PaymentStatsCard({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.iconColor,
|
||||
required this.label,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: iconColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF64748B), // slate-500
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF0F172A), // slate-900
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
class PendingPayCard extends StatelessWidget {
|
||||
final double amount;
|
||||
final VoidCallback onCashOut;
|
||||
|
||||
const PendingPayCard({
|
||||
super.key,
|
||||
required this.amount,
|
||||
required this.onCashOut,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
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),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE8F0FF),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.dollarSign,
|
||||
color: Color(0xFF0047FF),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Pending",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF0F172A), // slate-900
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"\$${amount.toStringAsFixed(0)} available",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF475569), // slate-600
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export 'src/payments_module.dart';
|
||||
28
apps/mobile/packages/features/staff/payments/pubspec.yaml
Normal file
28
apps/mobile/packages/features/staff/payments/pubspec.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
name: staff_payments
|
||||
description: Staff Payments feature
|
||||
version: 0.0.1
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
flutter: ">=3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_modular: ^6.3.2
|
||||
lucide_icons: ^0.257.0
|
||||
intl: ^0.20.0
|
||||
|
||||
# Internal packages
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
krow_domain:
|
||||
path: ../../../domain
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
@@ -11,6 +11,7 @@ import 'package:staff_documents/staff_documents.dart';
|
||||
import 'package:staff_certificates/staff_certificates.dart';
|
||||
import 'package:staff_attire/staff_attire.dart';
|
||||
import 'package:staff_shifts/staff_shifts.dart';
|
||||
import 'package:staff_payments/staff_payements.dart';
|
||||
|
||||
import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart';
|
||||
import 'package:staff_main/src/presentation/constants/staff_main_routes.dart';
|
||||
@@ -33,10 +34,9 @@ class StaffMainModule extends Module {
|
||||
StaffMainRoutes.shifts,
|
||||
module: StaffShiftsModule(),
|
||||
),
|
||||
ChildRoute<dynamic>(
|
||||
ModuleRoute<dynamic>(
|
||||
StaffMainRoutes.payments,
|
||||
child: (BuildContext context) =>
|
||||
const PlaceholderPage(title: 'Payments'),
|
||||
module: StaffPaymentsModule(),
|
||||
),
|
||||
ModuleRoute<dynamic>(
|
||||
StaffMainRoutes.home,
|
||||
|
||||
@@ -45,8 +45,8 @@ dependencies:
|
||||
path: ../profile_sections/onboarding/attire
|
||||
staff_shifts:
|
||||
path: ../shifts
|
||||
# staff_payments:
|
||||
# path: ../payments
|
||||
staff_payments:
|
||||
path: ../payments
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -1100,6 +1100,13 @@ packages:
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
staff_payments:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "packages/features/staff/payments"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
staff_shifts:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user