customer page

This commit is contained in:
efrilm
2025-10-26 22:57:22 +07:00
parent 5c683eda8d
commit 0bffd92a08
22 changed files with 3727 additions and 3 deletions
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class DashedDivider extends StatelessWidget {
final double height;
final double dashWidth;
final double dashSpacing;
final Color color;
const DashedDivider({
super.key,
this.height = 1,
this.dashWidth = 5,
this.dashSpacing = 3,
this.color = Colors.grey,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
child: LayoutBuilder(
builder: (context, constraints) {
final boxWidth = constraints.constrainWidth();
final dashCount = (boxWidth / (dashWidth + dashSpacing)).floor();
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: height,
child: DecoratedBox(decoration: BoxDecoration(color: color)),
);
}),
);
},
),
);
}
}
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../../../domain/customer/customer.dart';
class CustomerCard extends StatelessWidget {
final Customer customer;
final bool isActive;
final Function() onTap;
const CustomerCard({
super.key,
required this.customer,
required this.isActive,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isActive ? AppColor.primary.withOpacity(0.1) : AppColor.white,
border: Border.all(
color: isActive ? AppColor.primary : AppColor.border,
),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 22,
backgroundColor: AppColor.primary,
child: Icon(Icons.person, color: Colors.white),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
customer.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
Text(
customer.email.isNotEmpty ? customer.email : '-',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
],
),
),
],
),
],
),
),
);
}
}
@@ -0,0 +1,63 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
import '../spaces/space.dart';
class PageTitle extends StatelessWidget {
final String title;
final String? subtitle;
final bool isBack;
final List<Widget>? actionWidget;
const PageTitle({
super.key,
required this.title,
this.subtitle,
this.isBack = true,
this.actionWidget,
});
@override
Widget build(BuildContext context) {
return Container(
height: context.deviceHeight * 0.123,
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
decoration: BoxDecoration(
color: AppColor.white,
border: Border(bottom: BorderSide(color: AppColor.border, width: 1.0)),
),
child: Row(
children: [
if (isBack) ...[
GestureDetector(
onTap: () => context.maybePop(),
child: Icon(Icons.arrow_back, color: AppColor.primary, size: 24),
),
SpaceWidth(16),
],
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppStyle.xxl.copyWith(fontWeight: FontWeight.w600),
),
if (subtitle != null) ...[
const SizedBox(height: 4.0),
Text(
subtitle!,
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
),
],
],
),
),
if (actionWidget != null) ...actionWidget!,
],
),
);
}
}
@@ -1,12 +1,269 @@
import 'package:apskel_pos_flutter_v2/domain/customer/customer.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/customer/customer_loader/customer_loader_bloc.dart';
import '../../../../../common/extension/extension.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../../injection.dart';
import '../../../../components/border/dashed_border.dart';
import '../../../../components/card/customer_card.dart';
import '../../../../components/card/error_card.dart';
import '../../../../components/loader/loader_with_text.dart';
import '../../../../components/page/page_title.dart';
import '../../../../components/spaces/space.dart';
@RoutePage()
class CustomerPage extends StatelessWidget {
class CustomerPage extends StatelessWidget implements AutoRouteWrapper {
const CustomerPage({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
return Scaffold(
backgroundColor: AppColor.background,
body: BlocBuilder<CustomerLoaderBloc, CustomerLoaderState>(
builder: (context, state) {
return Row(
children: [
Expanded(
flex: 2,
child: Material(
color: AppColor.white,
child: Column(
children: [
PageTitle(title: 'Daftar Pelanggan'),
Expanded(
child: state.isFetching
? Center(child: const LoaderWithText())
: state.failureOrOption.fold(
() => ListView.builder(
itemCount: state.customers.length,
itemBuilder: (context, index) {
final customer = state.customers[index];
return CustomerCard(
customer: customer,
isActive:
customer == state.selectedCustomer,
onTap: () {
context.read<CustomerLoaderBloc>().add(
CustomerLoaderEvent.setSelectedCustomer(
customer,
),
);
},
);
},
),
(f) => _buildErrorState(f, context),
),
),
],
),
),
),
Expanded(
flex: 4,
child: state.selectedCustomer == null
? Center(
child: Text(
"Belum ada pelanggan yang dipilih.",
style: AppStyle.lg.copyWith(
fontSize: 16.0,
fontWeight: FontWeight.w600,
),
),
)
: Center(
child: Container(
width: context.deviceHeight * 0.5,
height: context.deviceHeight * 0.6,
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 16.0,
),
child: Column(
children: [
CircleAvatar(
radius: 24,
backgroundColor: AppColor.primary,
child: Icon(
Icons.person,
color: Colors.white,
),
),
const SizedBox(height: 12),
Text(
state.selectedCustomer?.name ?? "",
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
DashedDivider(color: AppColor.border),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 16.0,
),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'No. Handphone',
style: TextStyle(fontSize: 12.0),
),
Text(
(state
.selectedCustomer
?.phone
.isEmpty ??
true)
? "-"
: state.selectedCustomer?.phone ??
"-",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
),
),
],
),
SpaceHeight(8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Email',
style: TextStyle(fontSize: 12.0),
),
Text(
(state
.selectedCustomer
?.email
.isEmpty ??
true)
? "-"
: state.selectedCustomer?.email ??
"-",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
),
),
],
),
SpaceHeight(8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Alamat',
style: TextStyle(fontSize: 12.0),
),
Text(
(state
.selectedCustomer
?.address
.isEmpty ??
true)
? "-"
: state
.selectedCustomer
?.address ??
"-",
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
),
],
),
),
),
),
],
);
},
),
);
}
Widget _buildErrorState(CustomerFailure f, BuildContext context) {
return f.maybeMap(
orElse: () => ErrorCard(
title: "Pelanggan",
message: "Terjadi kesalahan saat memuat pelanggan",
onTap: () {
context.read<CustomerLoaderBloc>().add(
CustomerLoaderEvent.fetched(isRefresh: true),
);
},
),
empty: (value) => ErrorCard(
title: "Pelanggan",
message: "Pelanggan masih kosong, silahkan tambahkan pelanggan",
onTap: () {
context.read<CustomerLoaderBloc>().add(
CustomerLoaderEvent.fetched(isRefresh: true),
);
},
),
serverError: (value) => ErrorCard(
title: "Pelanggan",
message: "Terjadi kesalahan saat memuat pelanggan",
onTap: () {
context.read<CustomerLoaderBloc>().add(
CustomerLoaderEvent.fetched(isRefresh: true),
);
},
),
dynamicErrorMessage: (value) => ErrorCard(
title: "Pelanggan",
message: value.erroMessage,
onTap: () {
context.read<CustomerLoaderBloc>().add(
CustomerLoaderEvent.fetched(isRefresh: true),
);
},
),
unexpectedError: (value) => ErrorCard(
title: "Pelanggan",
message: "Terjadi kesalahan saat memuat pelanggan",
onTap: () {
context.read<CustomerLoaderBloc>().add(
CustomerLoaderEvent.fetched(isRefresh: true),
);
},
),
);
}
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) =>
getIt<CustomerLoaderBloc>()
..add(CustomerLoaderEvent.fetched(isRefresh: true)),
child: this,
);
}
+1 -1
View File
@@ -40,7 +40,7 @@ class CustomerRoute extends _i10.PageRouteInfo<void> {
static _i10.PageInfo page = _i10.PageInfo(
name,
builder: (data) {
return const _i1.CustomerPage();
return _i10.WrappedRoute(child: const _i1.CustomerPage());
},
);
}