diff --git a/lib/data/datasources/customer_remote_datasource.dart b/lib/data/datasources/customer_remote_datasource.dart new file mode 100644 index 0000000..e68ba9d --- /dev/null +++ b/lib/data/datasources/customer_remote_datasource.dart @@ -0,0 +1,48 @@ +import 'dart:developer'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/network/dio_client.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import '../../core/constants/variables.dart'; +import 'auth_local_datasource.dart'; + +class CustomerRemoteDataSource { + final Dio dio = DioClient.instance; + + Future> getCustomers({ + int page = 1, + int limit = Variables.defaultLimit, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/customers'; + + final response = await dio.get( + url, + queryParameters: { + 'page': page, + 'limit': limit, + 'organization_id': authData.user?.organizationId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(CustomerResponseModel.fromMap(response.data)); + } else { + return const Left('Failed to get customers'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil customer'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/data/models/response/customer_response_model.dart b/lib/data/models/response/customer_response_model.dart new file mode 100644 index 0000000..693a39b --- /dev/null +++ b/lib/data/models/response/customer_response_model.dart @@ -0,0 +1,115 @@ +import 'dart:convert'; + +class CustomerResponseModel { + final bool? success; + final CustomerData? data; + final dynamic errors; + + CustomerResponseModel({ + this.success, + this.data, + this.errors, + }); + + factory CustomerResponseModel.fromJson(String str) => + CustomerResponseModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory CustomerResponseModel.fromMap(Map json) => + CustomerResponseModel( + success: json["success"], + data: json["data"] == null ? null : CustomerData.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class CustomerData { + final List? customers; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + CustomerData({ + this.customers, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory CustomerData.fromMap(Map json) => CustomerData( + customers: json["data"] == null + ? [] + : List.from(json["data"].map((x) => Customer.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "data": customers == null + ? [] + : List.from(customers!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, + }; +} + +class Customer { + final String? id; + final String? organizationId; + final String? name; + final bool? isDefault; + final bool? isActive; + final Map? metadata; + final DateTime? createdAt; + final DateTime? updatedAt; + + Customer({ + this.id, + this.organizationId, + this.name, + this.isDefault, + this.isActive, + this.metadata, + this.createdAt, + this.updatedAt, + }); + + factory Customer.fromMap(Map json) => Customer( + id: json["id"], + organizationId: json["organization_id"], + name: json["name"], + isDefault: json["is_default"], + isActive: json["is_active"], + metadata: json["metadata"] ?? {}, + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); + + Map toMap() => { + "id": id, + "organization_id": organizationId, + "name": name, + "is_default": isDefault, + "is_active": isActive, + "metadata": metadata, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; +} diff --git a/lib/main.dart b/lib/main.dart index 37c3f1f..b25c9c9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:developer'; import 'package:enaklo_pos/core/constants/theme.dart'; +import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart'; +import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; @@ -234,6 +236,9 @@ class _MyAppState extends State { BlocProvider( create: (context) => OutletLoaderBloc(OutletRemoteDataSource()), ), + BlocProvider( + create: (context) => CustomerLoaderBloc(CustomerRemoteDataSource()), + ), ], child: MaterialApp( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart new file mode 100644 index 0000000..f4c2824 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart @@ -0,0 +1,28 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'customer_loader_event.dart'; +part 'customer_loader_state.dart'; +part 'customer_loader_bloc.freezed.dart'; + +class CustomerLoaderBloc + extends Bloc { + final CustomerRemoteDataSource _customerRemoteDataSource; + CustomerLoaderBloc( + this._customerRemoteDataSource, + ) : super(CustomerLoaderState.initial()) { + on<_GetCustomer>((event, emit) async { + emit(const _Loading()); + final result = await _customerRemoteDataSource.getCustomers(); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Loaded( + r.data?.customers ?? [], + r.data?.totalCount ?? 0, + )), + ); + }); + } +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.freezed.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.freezed.dart new file mode 100644 index 0000000..4705a14 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.freezed.dart @@ -0,0 +1,809 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'customer_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$CustomerLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getCustomer, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCustomer value) getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCustomer value)? getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCustomer value)? getCustomer, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerLoaderEventCopyWith<$Res> { + factory $CustomerLoaderEventCopyWith( + CustomerLoaderEvent value, $Res Function(CustomerLoaderEvent) then) = + _$CustomerLoaderEventCopyWithImpl<$Res, CustomerLoaderEvent>; +} + +/// @nodoc +class _$CustomerLoaderEventCopyWithImpl<$Res, $Val extends CustomerLoaderEvent> + implements $CustomerLoaderEventCopyWith<$Res> { + _$CustomerLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetCustomerImplCopyWith<$Res> { + factory _$$GetCustomerImplCopyWith( + _$GetCustomerImpl value, $Res Function(_$GetCustomerImpl) then) = + __$$GetCustomerImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetCustomerImplCopyWithImpl<$Res> + extends _$CustomerLoaderEventCopyWithImpl<$Res, _$GetCustomerImpl> + implements _$$GetCustomerImplCopyWith<$Res> { + __$$GetCustomerImplCopyWithImpl( + _$GetCustomerImpl _value, $Res Function(_$GetCustomerImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetCustomerImpl implements _GetCustomer { + const _$GetCustomerImpl(); + + @override + String toString() { + return 'CustomerLoaderEvent.getCustomer()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetCustomerImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() getCustomer, + }) { + return getCustomer(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getCustomer, + }) { + return getCustomer?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getCustomer, + required TResult orElse(), + }) { + if (getCustomer != null) { + return getCustomer(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCustomer value) getCustomer, + }) { + return getCustomer(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCustomer value)? getCustomer, + }) { + return getCustomer?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCustomer value)? getCustomer, + required TResult orElse(), + }) { + if (getCustomer != null) { + return getCustomer(this); + } + return orElse(); + } +} + +abstract class _GetCustomer implements CustomerLoaderEvent { + const factory _GetCustomer() = _$GetCustomerImpl; +} + +/// @nodoc +mixin _$CustomerLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerLoaderStateCopyWith<$Res> { + factory $CustomerLoaderStateCopyWith( + CustomerLoaderState value, $Res Function(CustomerLoaderState) then) = + _$CustomerLoaderStateCopyWithImpl<$Res, CustomerLoaderState>; +} + +/// @nodoc +class _$CustomerLoaderStateCopyWithImpl<$Res, $Val extends CustomerLoaderState> + implements $CustomerLoaderStateCopyWith<$Res> { + _$CustomerLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, $Res Function(_$InitialImpl) then) = + __$$InitialImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$CustomerLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CustomerLoaderState.initial()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$InitialImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements CustomerLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CustomerLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CustomerLoaderState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements CustomerLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({List customers, int totalCustomer}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$CustomerLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? customers = null, + Object? totalCustomer = null, + }) { + return _then(_$LoadedImpl( + null == customers + ? _value._customers + : customers // ignore: cast_nullable_to_non_nullable + as List, + null == totalCustomer + ? _value.totalCustomer + : totalCustomer // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(final List customers, this.totalCustomer) + : _customers = customers; + + final List _customers; + @override + List get customers { + if (_customers is EqualUnmodifiableListView) return _customers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_customers); + } + + @override + final int totalCustomer; + + @override + String toString() { + return 'CustomerLoaderState.loaded(customers: $customers, totalCustomer: $totalCustomer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality() + .equals(other._customers, _customers) && + (identical(other.totalCustomer, totalCustomer) || + other.totalCustomer == totalCustomer)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_customers), totalCustomer); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadedImplCopyWith<_$LoadedImpl> get copyWith => + __$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return loaded(customers, totalCustomer); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(customers, totalCustomer); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(customers, totalCustomer); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class _Loaded implements CustomerLoaderState { + const factory _Loaded( + final List customers, final int totalCustomer) = _$LoadedImpl; + + List get customers; + int get totalCustomer; + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadedImplCopyWith<_$LoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ErrorImplCopyWith<$Res> { + factory _$$ErrorImplCopyWith( + _$ErrorImpl value, $Res Function(_$ErrorImpl) then) = + __$$ErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String message}); +} + +/// @nodoc +class __$$ErrorImplCopyWithImpl<$Res> + extends _$CustomerLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? message = null, + }) { + return _then(_$ErrorImpl( + null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$ErrorImpl implements _Error { + const _$ErrorImpl(this.message); + + @override + final String message; + + @override + String toString() { + return 'CustomerLoaderState.error(message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ErrorImpl && + (identical(other.message, message) || other.message == message)); + } + + @override + int get hashCode => Object.hash(runtimeType, message); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + __$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements CustomerLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_event.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_event.dart new file mode 100644 index 0000000..e4aa318 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_event.dart @@ -0,0 +1,6 @@ +part of 'customer_loader_bloc.dart'; + +@freezed +class CustomerLoaderEvent with _$CustomerLoaderEvent { + const factory CustomerLoaderEvent.getCustomer() = _GetCustomer; +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_state.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_state.dart new file mode 100644 index 0000000..ed968bf --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_state.dart @@ -0,0 +1,10 @@ +part of 'customer_loader_bloc.dart'; + +@freezed +class CustomerLoaderState with _$CustomerLoaderState { + const factory CustomerLoaderState.initial() = _Initial; + const factory CustomerLoaderState.loading() = _Loading; + const factory CustomerLoaderState.loaded( + List customers, int totalCustomer) = _Loaded; + const factory CustomerLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/customer/pages/customer_page.dart b/lib/presentation/customer/pages/customer_page.dart new file mode 100644 index 0000000..99e2511 --- /dev/null +++ b/lib/presentation/customer/pages/customer_page.dart @@ -0,0 +1,96 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/customer/widgets/customer_card.dart'; +import 'package:enaklo_pos/presentation/customer/widgets/customer_title.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CustomerPage extends StatefulWidget { + const CustomerPage({super.key}); + + @override + State createState() => _CustomerPageState(); +} + +class _CustomerPageState extends State { + Customer? _customer; + @override + void initState() { + super.initState(); + context + .read() + .add(const CustomerLoaderEvent.getCustomer()); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: AppColors.background, + body: Row( + children: [ + Expanded( + flex: 2, + child: Material( + color: AppColors.white, + child: Column( + children: [ + CustomerTitle( + title: 'Daftar Pelanggan', + onChanged: (value) {}, + ), + Expanded( + child: + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Center( + child: CircularProgressIndicator(), + ), + loading: () => Center( + child: CircularProgressIndicator(), + ), + error: (message) => Center( + child: Text( + message, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + loaded: (customers, totalCustomer) => + ListView.builder( + itemCount: customers.length, + itemBuilder: (context, index) { + final customer = customers[index]; + return CustomerCard( + customer: customer, + isActive: customer == _customer, + onTap: () { + setState(() { + _customer = customer; + }); + }, + ); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ), + Expanded( + flex: 4, + child: Container(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/customer/widgets/customer_card.dart b/lib/presentation/customer/widgets/customer_card.dart new file mode 100644 index 0000000..01cc073 --- /dev/null +++ b/lib/presentation/customer/widgets/customer_card.dart @@ -0,0 +1,65 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:flutter/material.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, vertical: 10), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: + isActive ? AppColors.primary.withOpacity(0.1) : AppColors.white, + border: Border.all( + color: isActive ? AppColors.primary : AppColors.stroke), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircleAvatar( + radius: 22, + backgroundColor: AppColors.primary, + child: Icon(Icons.person, color: Colors.white), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + customer.name ?? '', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/customer/widgets/customer_title.dart b/lib/presentation/customer/widgets/customer_title.dart new file mode 100644 index 0000000..8e6f965 --- /dev/null +++ b/lib/presentation/customer/widgets/customer_title.dart @@ -0,0 +1,113 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:flutter/material.dart'; + +class CustomerTitle extends StatelessWidget { + final String title; + final Function(String) onChanged; + + const CustomerTitle( + {super.key, + required this.onChanged, + required this.title}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: () => context.pop(), + icon: Icon(Icons.arrow_back, color: AppColors.black), + ), + SpaceWidth(16), + Expanded( + child: Text( + title, + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: SizedBox( + height: 40, + child: TextFormField( + onChanged: onChanged, + decoration: InputDecoration( + prefixIcon: Icon( + Icons.search, + ), + hintText: 'Cari Customer', + ), + ), + ), + ), + SpaceWidth(12), + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => Container(), + ), + child: Container( + height: 40, + width: 40, + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.add, + color: AppColors.white, + size: 20, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/home/widgets/home_right_title.dart b/lib/presentation/home/widgets/home_right_title.dart index 86f622a..e8028c0 100644 --- a/lib/presentation/home/widgets/home_right_title.dart +++ b/lib/presentation/home/widgets/home_right_title.dart @@ -3,6 +3,7 @@ import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/customer/pages/customer_page.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; import 'package:enaklo_pos/presentation/home/dialog/type_dialog.dart'; import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; @@ -64,9 +65,7 @@ class HomeRightTitle extends StatelessWidget { ), onPressed: () { if (table == null) { - context.push(DashboardPage( - index: 1, - )); + context.push(CustomerPage()); } }, label: 'Pelanggan', diff --git a/lib/presentation/sales/pages/sales_page.dart b/lib/presentation/sales/pages/sales_page.dart index 54ebe9f..5d266f8 100644 --- a/lib/presentation/sales/pages/sales_page.dart +++ b/lib/presentation/sales/pages/sales_page.dart @@ -1,7 +1,6 @@ import 'package:enaklo_pos/core/components/buttons.dart'; import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/data/models/response/order_response_model.dart'; -import 'package:enaklo_pos/presentation/home/models/order_model.dart'; import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart'; import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart';