diff --git a/lib/core/components/custom_modal_dialog.dart b/lib/core/components/custom_modal_dialog.dart index 3b1ac29..8d943be 100644 --- a/lib/core/components/custom_modal_dialog.dart +++ b/lib/core/components/custom_modal_dialog.dart @@ -102,9 +102,11 @@ class CustomModalDialog extends StatelessWidget { ], ), ), - Padding( - padding: contentPadding ?? EdgeInsets.zero, - child: child, + Flexible( + child: SingleChildScrollView( + padding: contentPadding ?? EdgeInsets.zero, + child: child, + ), ), ], ), diff --git a/lib/core/components/custom_text_field.dart b/lib/core/components/custom_text_field.dart index cbc6828..c256cf4 100644 --- a/lib/core/components/custom_text_field.dart +++ b/lib/core/components/custom_text_field.dart @@ -14,6 +14,8 @@ class CustomTextField extends StatelessWidget { final Widget? prefixIcon; final Widget? suffixIcon; final bool readOnly; + final int? maxLines; + final String? Function(String?)? validator; const CustomTextField({ super.key, @@ -28,6 +30,8 @@ class CustomTextField extends StatelessWidget { this.prefixIcon, this.suffixIcon, this.readOnly = false, + this.maxLines, + this.validator, }); @override @@ -53,17 +57,11 @@ class CustomTextField extends StatelessWidget { textInputAction: textInputAction, textCapitalization: textCapitalization ?? TextCapitalization.none, readOnly: readOnly, + maxLines: maxLines, + validator: validator, decoration: InputDecoration( prefixIcon: prefixIcon, suffixIcon: suffixIcon, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16.0), - borderSide: const BorderSide(color: Colors.grey), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16.0), - borderSide: const BorderSide(color: Colors.grey), - ), hintText: label, ), ), diff --git a/lib/core/constants/theme.dart b/lib/core/constants/theme.dart index 2c823a2..c749181 100644 --- a/lib/core/constants/theme.dart +++ b/lib/core/constants/theme.dart @@ -22,6 +22,9 @@ ThemeData getApplicationTheme = ThemeData( useMaterial3: true, inputDecorationTheme: InputDecorationTheme( contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), + hintStyle: const TextStyle( + color: AppColors.grey, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide(color: AppColors.primary), diff --git a/lib/data/datasources/customer_remote_datasource.dart b/lib/data/datasources/customer_remote_datasource.dart index e68ba9d..ec6fd05 100644 --- a/lib/data/datasources/customer_remote_datasource.dart +++ b/lib/data/datasources/customer_remote_datasource.dart @@ -45,4 +45,57 @@ class CustomerRemoteDataSource { return const Left('Unexpected error occurred'); } } + + Future> createCustomer({ + required String name, + required String phone, + required String address, + required String email, + required bool isActive, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/customers'; + + Map data = { + 'name': name, + 'is_active': isActive, + }; + + if (phone.isNotEmpty) { + data['phone'] = phone; + } + + if (address.isNotEmpty) { + data['address'] = address; + } + + if (email.isNotEmpty) { + data['email'] = email; + } + + final response = await dio.post( + url, + data: data, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return const Right(true); + } else { + return const Left('Failed to create customer '); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal membuat pelanggan'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } } diff --git a/lib/main.dart b/lib/main.dart index b25c9c9..b5f862d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ 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_form/customer_form_bloc.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'; @@ -239,6 +240,9 @@ class _MyAppState extends State { BlocProvider( create: (context) => CustomerLoaderBloc(CustomerRemoteDataSource()), ), + BlocProvider( + create: (context) => CustomerFormBloc(CustomerRemoteDataSource()), + ), ], child: MaterialApp( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_bloc.dart b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.dart new file mode 100644 index 0000000..8a5b8ef --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.dart @@ -0,0 +1,28 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'customer_form_event.dart'; +part 'customer_form_state.dart'; +part 'customer_form_bloc.freezed.dart'; + +class CustomerFormBloc extends Bloc { + final CustomerRemoteDataSource _customerRemoteDataSource; + CustomerFormBloc(this._customerRemoteDataSource) + : super(CustomerFormState.initial()) { + on<_Create>((event, emit) async { + emit(const _Loading()); + final result = await _customerRemoteDataSource.createCustomer( + name: event.name, + address: event.address, + phone: event.phoneNumber, + email: event.email, + isActive: event.isActive, + ); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Success()), + ); + }); + } +} diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_bloc.freezed.dart b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.freezed.dart new file mode 100644 index 0000000..fe82761 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.freezed.dart @@ -0,0 +1,908 @@ +// 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_form_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 _$CustomerFormEvent { + String get name => throw _privateConstructorUsedError; + String get address => throw _privateConstructorUsedError; + String get phoneNumber => throw _privateConstructorUsedError; + String get email => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String name, String address, String phoneNumber, + String email, bool isActive) + create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CustomerFormEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerFormEventCopyWith<$Res> { + factory $CustomerFormEventCopyWith( + CustomerFormEvent value, $Res Function(CustomerFormEvent) then) = + _$CustomerFormEventCopyWithImpl<$Res, CustomerFormEvent>; + @useResult + $Res call( + {String name, + String address, + String phoneNumber, + String email, + bool isActive}); +} + +/// @nodoc +class _$CustomerFormEventCopyWithImpl<$Res, $Val extends CustomerFormEvent> + implements $CustomerFormEventCopyWith<$Res> { + _$CustomerFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? address = null, + Object? phoneNumber = null, + Object? email = null, + Object? isActive = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CreateImplCopyWith<$Res> + implements $CustomerFormEventCopyWith<$Res> { + factory _$$CreateImplCopyWith( + _$CreateImpl value, $Res Function(_$CreateImpl) then) = + __$$CreateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + String address, + String phoneNumber, + String email, + bool isActive}); +} + +/// @nodoc +class __$$CreateImplCopyWithImpl<$Res> + extends _$CustomerFormEventCopyWithImpl<$Res, _$CreateImpl> + implements _$$CreateImplCopyWith<$Res> { + __$$CreateImplCopyWithImpl( + _$CreateImpl _value, $Res Function(_$CreateImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? address = null, + Object? phoneNumber = null, + Object? email = null, + Object? isActive = null, + }) { + return _then(_$CreateImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$CreateImpl implements _Create { + const _$CreateImpl( + {required this.name, + required this.address, + required this.phoneNumber, + required this.email, + required this.isActive}); + + @override + final String name; + @override + final String address; + @override + final String phoneNumber; + @override + final String email; + @override + final bool isActive; + + @override + String toString() { + return 'CustomerFormEvent.create(name: $name, address: $address, phoneNumber: $phoneNumber, email: $email, isActive: $isActive)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreateImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.address, address) || other.address == address) && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.email, email) || other.email == email) && + (identical(other.isActive, isActive) || + other.isActive == isActive)); + } + + @override + int get hashCode => + Object.hash(runtimeType, name, address, phoneNumber, email, isActive); + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + __$$CreateImplCopyWithImpl<_$CreateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String name, String address, String phoneNumber, + String email, bool isActive) + create, + }) { + return create(name, address, phoneNumber, email, isActive); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + }) { + return create?.call(name, address, phoneNumber, email, isActive); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + required TResult orElse(), + }) { + if (create != null) { + return create(name, address, phoneNumber, email, isActive); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + }) { + return create(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + }) { + return create?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + required TResult orElse(), + }) { + if (create != null) { + return create(this); + } + return orElse(); + } +} + +abstract class _Create implements CustomerFormEvent { + const factory _Create( + {required final String name, + required final String address, + required final String phoneNumber, + required final String email, + required final bool isActive}) = _$CreateImpl; + + @override + String get name; + @override + String get address; + @override + String get phoneNumber; + @override + String get email; + @override + bool get isActive; + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$CustomerFormState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + 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(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerFormStateCopyWith<$Res> { + factory $CustomerFormStateCopyWith( + CustomerFormState value, $Res Function(CustomerFormState) then) = + _$CustomerFormStateCopyWithImpl<$Res, CustomerFormState>; +} + +/// @nodoc +class _$CustomerFormStateCopyWithImpl<$Res, $Val extends CustomerFormState> + implements $CustomerFormStateCopyWith<$Res> { + _$CustomerFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerFormState + /// 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 _$CustomerFormStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CustomerFormState.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() success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + 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(_Success value) success, + 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(_Success value)? success, + 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(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements CustomerFormState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CustomerFormStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CustomerFormState.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() success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + 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(_Success value) success, + 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(_Success value)? success, + 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(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements CustomerFormState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$CustomerFormStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'CustomerFormState.success()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return success(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements CustomerFormState { + const factory _Success() = _$SuccessImpl; +} + +/// @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 _$CustomerFormStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// 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 'CustomerFormState.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 CustomerFormState + /// 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() success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + 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(_Success value) success, + 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(_Success value)? success, + 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(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements CustomerFormState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CustomerFormState + /// 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_form/customer_form_event.dart b/lib/presentation/customer/bloc/customer_form/customer_form_event.dart new file mode 100644 index 0000000..cf1b06f --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_event.dart @@ -0,0 +1,11 @@ +part of 'customer_form_bloc.dart'; + +@freezed +class CustomerFormEvent with _$CustomerFormEvent { + const factory CustomerFormEvent.create( + {required String name, + required String address, + required String phoneNumber, + required String email, + required bool isActive}) = _Create; +} diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_state.dart b/lib/presentation/customer/bloc/customer_form/customer_form_state.dart new file mode 100644 index 0000000..0fb5c71 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_state.dart @@ -0,0 +1,9 @@ +part of 'customer_form_bloc.dart'; + +@freezed +class CustomerFormState with _$CustomerFormState { + const factory CustomerFormState.initial() = _Initial; + const factory CustomerFormState.loading() = _Loading; + const factory CustomerFormState.success() = _Success; + const factory CustomerFormState.error(String message) = _Error; +} diff --git a/lib/presentation/customer/dialog/form_customer_dialog.dart b/lib/presentation/customer/dialog/form_customer_dialog.dart new file mode 100644 index 0000000..fbbab92 --- /dev/null +++ b/lib/presentation/customer/dialog/form_customer_dialog.dart @@ -0,0 +1,121 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/presentation/customer/bloc/customer_form/customer_form_bloc.dart'; +import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class FormCustomerDialog extends StatefulWidget { + const FormCustomerDialog({super.key}); + + @override + State createState() => _FormCustomerDialogState(); +} + +class _FormCustomerDialogState extends State { + TextEditingController nameController = TextEditingController(); + TextEditingController phoneController = TextEditingController(); + TextEditingController emailController = TextEditingController(); + TextEditingController addressController = TextEditingController(); + + final _formKey = GlobalKey(); + + void onSave() async { + if (_formKey.currentState!.validate()) { + context.read().add( + CustomerFormEvent.create( + name: nameController.text, + phoneNumber: phoneController.text, + email: emailController.text, + address: addressController.text, + isActive: true, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Tambah Pelanggan', + subtitle: 'Tambahkan pelanggan baru', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Form( + key: _formKey, + child: Column( + children: [ + CustomTextField( + controller: nameController, + label: 'Name *', + validator: (value) { + if (value == null || value.isEmpty) { + return 'Nama harus diisi'; + } + return null; + }, + ), + SpaceHeight(16), + CustomTextField( + controller: emailController, + label: 'Email (Opsional)', + ), + SpaceHeight(16), + CustomTextField( + controller: phoneController, + keyboardType: TextInputType.phone, + label: 'No. Handphone (Opsional)', + ), + SpaceHeight(16), + CustomTextField( + controller: addressController, + label: 'Alamat (Opsional)', + maxLines: 5, + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Pelanggan berhasil disimpan'), + backgroundColor: AppColors.green, + ), + ); + context + .read() + .add(const CustomerLoaderEvent.getCustomer()); + }, + error: (message) => + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: AppColors.red, + ), + ), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: onSave, + label: 'Simpan', + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/customer/widgets/customer_title.dart b/lib/presentation/customer/widgets/customer_title.dart index 8e6f965..5a02ed2 100644 --- a/lib/presentation/customer/widgets/customer_title.dart +++ b/lib/presentation/customer/widgets/customer_title.dart @@ -1,6 +1,7 @@ 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:enaklo_pos/presentation/customer/dialog/form_customer_dialog.dart'; import 'package:flutter/material.dart'; class CustomerTitle extends StatelessWidget { @@ -8,9 +9,7 @@ class CustomerTitle extends StatelessWidget { final Function(String) onChanged; const CustomerTitle( - {super.key, - required this.onChanged, - required this.title}); + {super.key, required this.onChanged, required this.title}); @override Widget build(BuildContext context) { @@ -85,7 +84,7 @@ class CustomerTitle extends StatelessWidget { GestureDetector( onTap: () => showDialog( context: context, - builder: (context) => Container(), + builder: (context) => FormCustomerDialog(), ), child: Container( height: 40,