From c8f8d2ec9d62085251599c1d6c590f5ffc9f459c Mon Sep 17 00:00:00 2001 From: efrilm Date: Sun, 3 Aug 2025 12:56:13 +0700 Subject: [PATCH] feat: get list outlet id --- .../outlet_remote_data_source.dart | 43 + lib/main.dart | 5 + .../outlet_loader/outlet_loader_bloc.dart | 23 + .../outlet_loader_bloc.freezed.dart | 790 ++++++++++++++++++ .../outlet_loader/outlet_loader_event.dart | 6 + .../outlet_loader/outlet_loader_state.dart | 9 + .../home/dialog/outlet_dialog.dart | 40 + .../home/models/outlet_model.dart | 130 +++ lib/presentation/home/pages/home_page.dart | 4 + lib/presentation/home/widgets/home_title.dart | 26 +- .../home/widgets/outlet_card.dart | 53 ++ 11 files changed, 1123 insertions(+), 6 deletions(-) create mode 100644 lib/data/datasources/outlet_remote_data_source.dart create mode 100644 lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart create mode 100644 lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart create mode 100644 lib/presentation/home/bloc/outlet_loader/outlet_loader_event.dart create mode 100644 lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart create mode 100644 lib/presentation/home/dialog/outlet_dialog.dart create mode 100644 lib/presentation/home/models/outlet_model.dart create mode 100644 lib/presentation/home/widgets/outlet_card.dart diff --git a/lib/data/datasources/outlet_remote_data_source.dart b/lib/data/datasources/outlet_remote_data_source.dart new file mode 100644 index 0000000..96a3582 --- /dev/null +++ b/lib/data/datasources/outlet_remote_data_source.dart @@ -0,0 +1,43 @@ +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/presentation/home/models/outlet_model.dart'; +import '../../core/constants/variables.dart'; +import 'auth_local_datasource.dart'; + +class OutletRemoteDataSource { + final Dio dio = DioClient.instance; + + Future> getOutlets() async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/outlets/list'; + + final response = await dio.get( + url, + queryParameters: { + 'organization_id': authData.user?.organizationId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(OutletResponse.fromMap(response.data)); + } else { + return const Left('Failed to get outlets'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil outlet'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 7ff14d5..98330b8 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/outlet_remote_data_source.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'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; @@ -225,6 +227,9 @@ class _MyAppState extends State { BlocProvider( create: (context) => OrderFormBloc(OrderRemoteDatasource()), ), + BlocProvider( + create: (context) => OutletLoaderBloc(OutletRemoteDataSource()), + ), ], child: MaterialApp( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart new file mode 100644 index 0000000..1755140 --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart @@ -0,0 +1,23 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'outlet_loader_event.dart'; +part 'outlet_loader_state.dart'; +part 'outlet_loader_bloc.freezed.dart'; + +class OutletLoaderBloc extends Bloc { + final OutletRemoteDataSource _outletRemoteDataSource; + OutletLoaderBloc(this._outletRemoteDataSource) + : super(OutletLoaderState.initial()) { + on<_GetOutlet>((event, emit) async { + emit(const _Loading()); + final result = await _outletRemoteDataSource.getOutlets(); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Loaded(r.data?.outlets ?? [])), + ); + }); + } +} diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart new file mode 100644 index 0000000..b1fcd7b --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart @@ -0,0 +1,790 @@ +// 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 'outlet_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 _$OutletLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getOutlet, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetOutlet value) getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetOutlet value)? getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetOutlet value)? getOutlet, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OutletLoaderEventCopyWith<$Res> { + factory $OutletLoaderEventCopyWith( + OutletLoaderEvent value, $Res Function(OutletLoaderEvent) then) = + _$OutletLoaderEventCopyWithImpl<$Res, OutletLoaderEvent>; +} + +/// @nodoc +class _$OutletLoaderEventCopyWithImpl<$Res, $Val extends OutletLoaderEvent> + implements $OutletLoaderEventCopyWith<$Res> { + _$OutletLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OutletLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetOutletImplCopyWith<$Res> { + factory _$$GetOutletImplCopyWith( + _$GetOutletImpl value, $Res Function(_$GetOutletImpl) then) = + __$$GetOutletImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetOutletImplCopyWithImpl<$Res> + extends _$OutletLoaderEventCopyWithImpl<$Res, _$GetOutletImpl> + implements _$$GetOutletImplCopyWith<$Res> { + __$$GetOutletImplCopyWithImpl( + _$GetOutletImpl _value, $Res Function(_$GetOutletImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetOutletImpl implements _GetOutlet { + const _$GetOutletImpl(); + + @override + String toString() { + return 'OutletLoaderEvent.getOutlet()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetOutletImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() getOutlet, + }) { + return getOutlet(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getOutlet, + }) { + return getOutlet?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getOutlet, + required TResult orElse(), + }) { + if (getOutlet != null) { + return getOutlet(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetOutlet value) getOutlet, + }) { + return getOutlet(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetOutlet value)? getOutlet, + }) { + return getOutlet?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetOutlet value)? getOutlet, + required TResult orElse(), + }) { + if (getOutlet != null) { + return getOutlet(this); + } + return orElse(); + } +} + +abstract class _GetOutlet implements OutletLoaderEvent { + const factory _GetOutlet() = _$GetOutletImpl; +} + +/// @nodoc +mixin _$OutletLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List outlets) loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? 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 $OutletLoaderStateCopyWith<$Res> { + factory $OutletLoaderStateCopyWith( + OutletLoaderState value, $Res Function(OutletLoaderState) then) = + _$OutletLoaderStateCopyWithImpl<$Res, OutletLoaderState>; +} + +/// @nodoc +class _$OutletLoaderStateCopyWithImpl<$Res, $Val extends OutletLoaderState> + implements $OutletLoaderStateCopyWith<$Res> { + _$OutletLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OutletLoaderState + /// 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 _$OutletLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'OutletLoaderState.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 outlets) loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? 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 OutletLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$OutletLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'OutletLoaderState.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 outlets) loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? 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 OutletLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({List outlets}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$OutletLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlets = null, + }) { + return _then(_$LoadedImpl( + null == outlets + ? _value._outlets + : outlets // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(final List outlets) : _outlets = outlets; + + final List _outlets; + @override + List get outlets { + if (_outlets is EqualUnmodifiableListView) return _outlets; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_outlets); + } + + @override + String toString() { + return 'OutletLoaderState.loaded(outlets: $outlets)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality().equals(other._outlets, _outlets)); + } + + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_outlets)); + + /// Create a copy of OutletLoaderState + /// 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 outlets) loaded, + required TResult Function(String message) error, + }) { + return loaded(outlets); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(outlets); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(outlets); + } + 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 OutletLoaderState { + const factory _Loaded(final List outlets) = _$LoadedImpl; + + List get outlets; + + /// Create a copy of OutletLoaderState + /// 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 _$OutletLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// 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 'OutletLoaderState.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 OutletLoaderState + /// 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 outlets) loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? 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 OutletLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of OutletLoaderState + /// 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/home/bloc/outlet_loader/outlet_loader_event.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_event.dart new file mode 100644 index 0000000..c94e780 --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_event.dart @@ -0,0 +1,6 @@ +part of 'outlet_loader_bloc.dart'; + +@freezed +class OutletLoaderEvent with _$OutletLoaderEvent { + const factory OutletLoaderEvent.getOutlet() = _GetOutlet; +} diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart new file mode 100644 index 0000000..de64ef0 --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart @@ -0,0 +1,9 @@ +part of 'outlet_loader_bloc.dart'; + +@freezed +class OutletLoaderState with _$OutletLoaderState { + const factory OutletLoaderState.initial() = _Initial; + const factory OutletLoaderState.loading() = _Loading; + const factory OutletLoaderState.loaded(List outlets) = _Loaded; + const factory OutletLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/home/dialog/outlet_dialog.dart b/lib/presentation/home/dialog/outlet_dialog.dart new file mode 100644 index 0000000..d900e71 --- /dev/null +++ b/lib/presentation/home/dialog/outlet_dialog.dart @@ -0,0 +1,40 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/widgets/outlet_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class OutletDialog extends StatelessWidget { + const OutletDialog({super.key}); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Outlet', + subtitle: 'Silahkan pilih outlet', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Center( + child: Text('Error has occured'), + ), + loading: () => Center(child: CircularProgressIndicator()), + error: (message) => Center( + child: Text(message), + ), + loaded: (outlets) => Column( + children: List.generate( + outlets.length, + (index) => OutletCard( + outlet: outlets[index], + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/home/models/outlet_model.dart b/lib/presentation/home/models/outlet_model.dart new file mode 100644 index 0000000..9f1c0f5 --- /dev/null +++ b/lib/presentation/home/models/outlet_model.dart @@ -0,0 +1,130 @@ +import 'dart:convert'; + +class OutletResponse { + final bool? success; + final OutletData? data; + final dynamic errors; + + OutletResponse({ + this.success, + this.data, + this.errors, + }); + + factory OutletResponse.fromJson(String str) => + OutletResponse.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory OutletResponse.fromMap(Map json) => OutletResponse( + success: json["success"], + data: json["data"] == null ? null : OutletData.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class OutletData { + final List? outlets; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + OutletData({ + this.outlets, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory OutletData.fromMap(Map json) => OutletData( + outlets: json["outlets"] == null + ? [] + : List.from(json["outlets"].map((x) => Outlet.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "outlets": outlets == null + ? [] + : List.from(outlets!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, + }; +} + +class Outlet { + final String? id; + final String? organizationId; + final String? name; + final String? address; + final String? phoneNumber; + final String? businessType; + final String? currency; + final int? taxRate; + final bool? isActive; + final DateTime? createdAt; + final DateTime? updatedAt; + + Outlet({ + this.id, + this.organizationId, + this.name, + this.address, + this.phoneNumber, + this.businessType, + this.currency, + this.taxRate, + this.isActive, + this.createdAt, + this.updatedAt, + }); + + factory Outlet.fromMap(Map json) => Outlet( + id: json["id"], + organizationId: json["organization_id"], + name: json["name"], + address: json["address"], + phoneNumber: json["phone_number"], + businessType: json["business_type"], + currency: json["currency"], + taxRate: json["tax_rate"], + isActive: json["is_active"], + 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, + "address": address, + "phone_number": phoneNumber, + "business_type": businessType, + "currency": currency, + "tax_rate": taxRate, + "is_active": isActive, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; + + factory Outlet.fromJson(String str) => Outlet.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); +} diff --git a/lib/presentation/home/pages/home_page.dart b/lib/presentation/home/pages/home_page.dart index d3f5ec7..352dc1d 100644 --- a/lib/presentation/home/pages/home_page.dart +++ b/lib/presentation/home/pages/home_page.dart @@ -1,4 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first +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'; import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:flutter/material.dart'; @@ -58,6 +59,9 @@ class _HomePageState extends State { // Initialize checkout with tax and service charge settings context.read().add(const CheckoutEvent.started()); + + // Get Outlets + context.read().add(const OutletLoaderEvent.getOutlet()); } void onCategoryTap(int index) { diff --git a/lib/presentation/home/widgets/home_title.dart b/lib/presentation/home/widgets/home_title.dart index ba32909..28275cc 100644 --- a/lib/presentation/home/widgets/home_title.dart +++ b/lib/presentation/home/widgets/home_title.dart @@ -1,4 +1,6 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/home/dialog/outlet_dialog.dart'; import 'package:flutter/material.dart'; import '../../../core/components/search_input.dart'; @@ -25,12 +27,24 @@ class HomeTitle extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - 'DEFAULT OUTLET', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => OutletDialog(), + ), + child: Row( + children: [ + const Text( + 'DEFAULT OUTLET', + style: TextStyle( + color: AppColors.primary, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + SpaceWidth(2), + Icon(Icons.keyboard_arrow_down, color: AppColors.primary), + ], ), ), SizedBox( diff --git a/lib/presentation/home/widgets/outlet_card.dart b/lib/presentation/home/widgets/outlet_card.dart new file mode 100644 index 0000000..cec6f02 --- /dev/null +++ b/lib/presentation/home/widgets/outlet_card.dart @@ -0,0 +1,53 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:flutter/material.dart'; + +class OutletCard extends StatelessWidget { + final Outlet outlet; + const OutletCard({super.key, required this.outlet}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: AppColors.white, + border: Border.all(color: AppColors.primary), + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + ), + child: Row( + children: [ + Icon(Icons.store, color: AppColors.primary), + SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + outlet.name ?? "", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + outlet.address ?? "", + style: const TextStyle( + fontSize: 12, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ], + ), + ), + ], + ), + ); + } +}