From f05347c90d9022a5c03ae85a46534e416bbdb642 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 8 Aug 2025 14:22:12 +0700 Subject: [PATCH] feat: transfer table --- lib/main.dart | 4 + .../transfer_table/transfer_table_bloc.dart | 26 + .../transfer_table_bloc.freezed.dart | 832 ++++++++++++++++++ .../transfer_table/transfer_table_event.dart | 8 + .../transfer_table/transfer_table_state.dart | 9 + .../table/dialogs/transfer_table_dialog.dart | 319 +++++++ lib/presentation/table/pages/table_page.dart | 329 ++++--- 7 files changed, 1389 insertions(+), 138 deletions(-) create mode 100644 lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart create mode 100644 lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart create mode 100644 lib/presentation/table/blocs/transfer_table/transfer_table_event.dart create mode 100644 lib/presentation/table/blocs/transfer_table/transfer_table_state.dart create mode 100644 lib/presentation/table/dialogs/transfer_table_dialog.dart diff --git a/lib/main.dart b/lib/main.dart index aaafa5e..e6acfe8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bl import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/upload_file/upload_file_bloc.dart'; +import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart'; import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; @@ -284,6 +285,9 @@ class _MyAppState extends State { BlocProvider( create: (context) => GetPrinterTicketBloc(), ), + BlocProvider( + create: (context) => TransferTableBloc(TableRemoteDataSource()), + ), ], child: MaterialApp( navigatorKey: AuthInterceptor.navigatorKey, diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart new file mode 100644 index 0000000..d9109a4 --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart @@ -0,0 +1,26 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'transfer_table_event.dart'; +part 'transfer_table_state.dart'; +part 'transfer_table_bloc.freezed.dart'; + +class TransferTableBloc extends Bloc { + final TableRemoteDataSource _dataSource; + TransferTableBloc(this._dataSource) : super(TransferTableState.initial()) { + on<_TransferTable>((event, emit) async { + emit(_Loading()); + + final result = await _dataSource.transferTable( + fromTableId: event.fromTableId, + toTableId: event.toTableId, + ); + + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Success()), + ); + }); + } +} diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart new file mode 100644 index 0000000..73a1962 --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart @@ -0,0 +1,832 @@ +// 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 'transfer_table_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 _$TransferTableEvent { + String get fromTableId => throw _privateConstructorUsedError; + String get toTableId => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String fromTableId, String toTableId) + transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String fromTableId, String toTableId)? transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String fromTableId, String toTableId)? transferTable, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_TransferTable value) transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TransferTable value)? transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TransferTable value)? transferTable, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TransferTableEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TransferTableEventCopyWith<$Res> { + factory $TransferTableEventCopyWith( + TransferTableEvent value, $Res Function(TransferTableEvent) then) = + _$TransferTableEventCopyWithImpl<$Res, TransferTableEvent>; + @useResult + $Res call({String fromTableId, String toTableId}); +} + +/// @nodoc +class _$TransferTableEventCopyWithImpl<$Res, $Val extends TransferTableEvent> + implements $TransferTableEventCopyWith<$Res> { + _$TransferTableEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fromTableId = null, + Object? toTableId = null, + }) { + return _then(_value.copyWith( + fromTableId: null == fromTableId + ? _value.fromTableId + : fromTableId // ignore: cast_nullable_to_non_nullable + as String, + toTableId: null == toTableId + ? _value.toTableId + : toTableId // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TransferTableImplCopyWith<$Res> + implements $TransferTableEventCopyWith<$Res> { + factory _$$TransferTableImplCopyWith( + _$TransferTableImpl value, $Res Function(_$TransferTableImpl) then) = + __$$TransferTableImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String fromTableId, String toTableId}); +} + +/// @nodoc +class __$$TransferTableImplCopyWithImpl<$Res> + extends _$TransferTableEventCopyWithImpl<$Res, _$TransferTableImpl> + implements _$$TransferTableImplCopyWith<$Res> { + __$$TransferTableImplCopyWithImpl( + _$TransferTableImpl _value, $Res Function(_$TransferTableImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fromTableId = null, + Object? toTableId = null, + }) { + return _then(_$TransferTableImpl( + fromTableId: null == fromTableId + ? _value.fromTableId + : fromTableId // ignore: cast_nullable_to_non_nullable + as String, + toTableId: null == toTableId + ? _value.toTableId + : toTableId // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$TransferTableImpl implements _TransferTable { + const _$TransferTableImpl( + {required this.fromTableId, required this.toTableId}); + + @override + final String fromTableId; + @override + final String toTableId; + + @override + String toString() { + return 'TransferTableEvent.transferTable(fromTableId: $fromTableId, toTableId: $toTableId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TransferTableImpl && + (identical(other.fromTableId, fromTableId) || + other.fromTableId == fromTableId) && + (identical(other.toTableId, toTableId) || + other.toTableId == toTableId)); + } + + @override + int get hashCode => Object.hash(runtimeType, fromTableId, toTableId); + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TransferTableImplCopyWith<_$TransferTableImpl> get copyWith => + __$$TransferTableImplCopyWithImpl<_$TransferTableImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String fromTableId, String toTableId) + transferTable, + }) { + return transferTable(fromTableId, toTableId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String fromTableId, String toTableId)? transferTable, + }) { + return transferTable?.call(fromTableId, toTableId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String fromTableId, String toTableId)? transferTable, + required TResult orElse(), + }) { + if (transferTable != null) { + return transferTable(fromTableId, toTableId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_TransferTable value) transferTable, + }) { + return transferTable(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TransferTable value)? transferTable, + }) { + return transferTable?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TransferTable value)? transferTable, + required TResult orElse(), + }) { + if (transferTable != null) { + return transferTable(this); + } + return orElse(); + } +} + +abstract class _TransferTable implements TransferTableEvent { + const factory _TransferTable( + {required final String fromTableId, + required final String toTableId}) = _$TransferTableImpl; + + @override + String get fromTableId; + @override + String get toTableId; + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TransferTableImplCopyWith<_$TransferTableImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$TransferTableState { + @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 $TransferTableStateCopyWith<$Res> { + factory $TransferTableStateCopyWith( + TransferTableState value, $Res Function(TransferTableState) then) = + _$TransferTableStateCopyWithImpl<$Res, TransferTableState>; +} + +/// @nodoc +class _$TransferTableStateCopyWithImpl<$Res, $Val extends TransferTableState> + implements $TransferTableStateCopyWith<$Res> { + _$TransferTableStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TransferTableState + /// 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 _$TransferTableStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'TransferTableState.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 TransferTableState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$TransferTableStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'TransferTableState.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 TransferTableState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$TransferTableStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'TransferTableState.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 TransferTableState { + 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 _$TransferTableStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// 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 'TransferTableState.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 TransferTableState + /// 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 TransferTableState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of TransferTableState + /// 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/table/blocs/transfer_table/transfer_table_event.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_event.dart new file mode 100644 index 0000000..c8e1a1c --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_event.dart @@ -0,0 +1,8 @@ +part of 'transfer_table_bloc.dart'; + +@freezed +class TransferTableEvent with _$TransferTableEvent { + const factory TransferTableEvent.transferTable( + {required String fromTableId, + required String toTableId}) = _TransferTable; +} diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_state.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_state.dart new file mode 100644 index 0000000..57411ea --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_state.dart @@ -0,0 +1,9 @@ +part of 'transfer_table_bloc.dart'; + +@freezed +class TransferTableState with _$TransferTableState { + const factory TransferTableState.initial() = _Initial; + const factory TransferTableState.loading() = _Loading; + const factory TransferTableState.success() = _Success; + const factory TransferTableState.error(String message) = _Error; +} diff --git a/lib/presentation/table/dialogs/transfer_table_dialog.dart b/lib/presentation/table/dialogs/transfer_table_dialog.dart new file mode 100644 index 0000000..7a6e228 --- /dev/null +++ b/lib/presentation/table/dialogs/transfer_table_dialog.dart @@ -0,0 +1,319 @@ +import 'dart:developer'; + +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_status_bloc.dart'; +import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class TransferTableDialog extends StatefulWidget { + final TableModel fromTable; + const TransferTableDialog({ + super.key, + required this.fromTable, + }); + + @override + State createState() => _TransferTableDialogState(); +} + +class _TransferTableDialogState extends State { + TableModel? selectTable; + + @override + void initState() { + super.initState(); + context + .read() + .add(GetTableStatusEvent.getTablesStatus('available')); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + context.pop(); + }, + ); + }, + child: CustomModalDialog( + title: 'Transfer Meja', + subtitle: 'Pilih meja yang tersedia', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + minWidth: context.deviceWidth * 0.4, + minHeight: context.deviceHeight * 0.4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pilih Meja', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(6.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + Center(child: const CircularProgressIndicator()), + loading: () => + Center(child: const CircularProgressIndicator()), + success: (tables) { + final availableTables = tables; + + if (selectTable == null && availableTables.isNotEmpty) { + selectTable = availableTables.first; + } + + if (availableTables.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.orange, + width: 1, + ), + ), + child: const Text( + 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', + style: TextStyle(color: Colors.orange), + ), + ); + } + + return DropdownSearch( + items: tables, + selectedItem: selectTable, + + // Dropdown properties + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + hintText: "Pilih meja", + hintStyle: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + prefixIcon: Icon( + Icons.category_outlined, + color: Colors.grey.shade500, + size: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.blue.shade400, + width: 2, + ), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + + // Popup properties + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Cari meja...", + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + itemBuilder: (context, category, isSelected) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade50 + : Colors.transparent, + border: Border( + bottom: BorderSide( + color: Colors.grey.shade100, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade600 + : Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + category.tableName ?? "", + style: TextStyle( + fontSize: 14, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: isSelected + ? Colors.blue.shade700 + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + Icons.check, + color: Colors.blue.shade600, + size: 18, + ), + ], + ), + ); + }, + emptyBuilder: (context, searchEntry) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + color: Colors.grey.shade400, + size: 48, + ), + const SizedBox(height: 12), + Text( + searchEntry.isEmpty + ? "Tidak ada meja tersedia" + : "Tidak ditemukan meja dengan '${searchEntry}'", + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + + // Item as string (for search functionality) + itemAsString: (TableModel table) => + table.tableName ?? "", + + // Comparison function + compareFn: (TableModel? item1, TableModel? item2) { + return item1?.id == item2?.id; + }, + + // On changed callback + onChanged: (TableModel? selectedTable) { + if (selectedTable != null) { + setState(() { + selectTable = selectedTable; + }); + log("selectTable: ${selectTable!.tableName}"); + } + }, + + // Validator (optional) + validator: (TableModel? value) { + if (value == null) { + return "Meja harus dipilih"; + } + return null; + }, + ); + }, + ); + }), + ], + ), + SpaceHeight(24), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + if (selectTable == null) { + AppFlushbar.showError( + context, 'Silahkan Pilih Meja Tujuan'); + return; + } + + context.read().add( + TransferTableEvent.transferTable( + fromTableId: widget.fromTable.id ?? "", + toTableId: selectTable!.id ?? "", + ), + ); + }, + label: "Transfer", + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/table/pages/table_page.dart b/lib/presentation/table/pages/table_page.dart index 6f1a096..b31e397 100644 --- a/lib/presentation/table/pages/table_page.dart +++ b/lib/presentation/table/pages/table_page.dart @@ -1,3 +1,4 @@ +import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; @@ -5,7 +6,9 @@ import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; import 'package:enaklo_pos/presentation/table/blocs/change_position_table/change_position_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; +import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/dialogs/form_table_new_dialog.dart'; +import 'package:enaklo_pos/presentation/table/dialogs/transfer_table_dialog.dart'; import 'package:enaklo_pos/presentation/table/widgets/table_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -41,6 +44,8 @@ class _TablePageState extends State { // Untuk drag TableModel? draggingTable; + Offset? _tapPosition; + // Ubah function toggleSelectTable menjadi selectTable void selectTable(TableModel table) { if (table.status == 'occupied') return; @@ -112,141 +117,164 @@ class _TablePageState extends State { ), ), backgroundColor: const Color(0xFFF7F8FA), - body: BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => SizedBox.shrink(), - loading: () => const Center(child: CircularProgressIndicator()), - success: (tables) => SafeArea( - child: Stack( - children: [ - // Main content area - Row( - children: [ - // Area meja (zoom & pan & drag) - Expanded( - flex: 5, - child: InteractiveViewer( - panEnabled: true, - scaleEnabled: true, - constrained: false, - boundaryMargin: const EdgeInsets.all(80), - minScale: 0.3, - maxScale: 3.0, - alignment: Alignment.topLeft, - child: Container( - width: mapWidth, - height: mapHeight, - decoration: BoxDecoration( - color: const Color(0xFFF7F8FA), - border: Border.all( - color: Colors.grey[300]!, width: 2), - ), - child: Stack( - children: [ - // Optional: Grid background - ...List.generate( - 20, - (i) => Positioned( - left: i * 100.0, - top: 0, - bottom: 0, - child: Container( - width: 1, - color: Colors.grey[200]), - )), - ...List.generate( - 15, - (i) => Positioned( - top: i * 100.0, - left: 0, - right: 0, - child: Container( - height: 1, - color: Colors.grey[200]), - )), + body: BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + AppFlushbar.showSuccess(context, 'Transfer Table Success'); + context.read().add(const GetTableEvent.getTables()); + }, + error: (message) { + AppFlushbar.showError(context, message); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => SizedBox.shrink(), + loading: () => const Center(child: CircularProgressIndicator()), + success: (tables) => SafeArea( + child: Stack( + children: [ + // Main content area + Row( + children: [ + // Area meja (zoom & pan & drag) + Expanded( + flex: 5, + child: InteractiveViewer( + panEnabled: true, + scaleEnabled: true, + constrained: false, + boundaryMargin: const EdgeInsets.all(80), + minScale: 0.3, + maxScale: 3.0, + alignment: Alignment.topLeft, + child: Container( + width: mapWidth, + height: mapHeight, + decoration: BoxDecoration( + color: const Color(0xFFF7F8FA), + border: Border.all( + color: Colors.grey[300]!, width: 2), + ), + child: Stack( + children: [ + // Optional: Grid background + ...List.generate( + 20, + (i) => Positioned( + left: i * 100.0, + top: 0, + bottom: 0, + child: Container( + width: 1, + color: Colors.grey[200]), + )), + ...List.generate( + 15, + (i) => Positioned( + top: i * 100.0, + left: 0, + right: 0, + child: Container( + height: 1, + color: Colors.grey[200]), + )), - // Tables - ...tables.map((table) { - final isSelected = selectedTable == table; - return Positioned( - left: table.positionX, - top: table.positionY, - child: Draggable( - data: table, - feedback: Material( - color: Colors.transparent, - child: TableWidget( - table: table, - isSelected: isSelected, + // Tables + ...tables.map((table) { + final isSelected = selectedTable == table; + return Positioned( + left: table.positionX, + top: table.positionY, + child: Draggable( + data: table, + feedback: Material( + color: Colors.transparent, + child: TableWidget( + table: table, + isSelected: isSelected, + ), + ), + childWhenDragging: Opacity( + opacity: 0.5, + child: TableWidget( + table: table, + isSelected: isSelected, + ), + ), + onDragStarted: () { + setState(() { + draggingTable = table; + }); + }, + onDraggableCanceled: + (velocity, offset) { + setState(() { + draggingTable = null; + }); + }, + onDragEnd: (details) { + setState(() { + draggingTable = null; + final RenderBox box = + context.findRenderObject() + as RenderBox; + final Offset local = box + .globalToLocal(details.offset); + table.positionX = local.dx + .clamp(0, mapWidth - 120); + table.positionY = local.dy + .clamp(0, mapHeight - 80); + }); + context + .read() + .add(ChangePositionTableEvent + .changePositionTable( + tableId: table.id ?? "", + position: Offset( + table.positionX ?? 0.0, + table.positionY ?? 0.0, + ), + )); + }, + child: GestureDetector( + onTap: () => selectTable(table), + onTapDown: (details) => _tapPosition = + details.globalPosition, + onLongPress: () { + if (table.status == 'occupied') { + _showPopupMenu(context, table); + } + }, + child: TableWidget( + table: table, + isSelected: isSelected, + ), ), ), - childWhenDragging: Opacity( - opacity: 0.5, - child: TableWidget( - table: table, - isSelected: isSelected, - ), - ), - onDragStarted: () { - setState(() { - draggingTable = table; - }); - }, - onDraggableCanceled: (velocity, offset) { - setState(() { - draggingTable = null; - }); - }, - onDragEnd: (details) { - setState(() { - draggingTable = null; - final RenderBox box = context - .findRenderObject() as RenderBox; - final Offset local = - box.globalToLocal(details.offset); - table.positionX = - local.dx.clamp(0, mapWidth - 120); - table.positionY = - local.dy.clamp(0, mapHeight - 80); - }); - context - .read() - .add(ChangePositionTableEvent - .changePositionTable( - tableId: table.id ?? "", - position: Offset( - table.positionX ?? 0.0, - table.positionY ?? 0.0, - ), - )); - }, - child: GestureDetector( - onTap: () => selectTable(table), - child: TableWidget( - table: table, - isSelected: isSelected, - ), - ), - ), - ); - }).toList(), - ], + ); + }).toList(), + ], + ), ), ), ), - ), - // Sidebar bar tables - ], - ), + // Sidebar bar tables + ], + ), - // Floating bottom bar - hanya muncul jika ada table yang dipilih - buildAlternativeFloatingBar(), - ], + // Floating bottom bar - hanya muncul jika ada table yang dipilih + buildAlternativeFloatingBar(), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ); } @@ -366,17 +394,7 @@ class _TablePageState extends State { context.push(DashboardPage( table: selectedTable!, )); - } else { - // Handle occupied table click - load draft order and navigate to payment - // context.read().add( - // CheckoutEvent.loadDraftOrder(data!), - // ); - // log("Data Draft Order: ${data!.toMap()}"); - // context.push(PaymentTablePage( - // table: widget.table, - // draftOrder: data!, - // )); - } + } else {} }, child: const Text( "Tempatkan Pesanan", @@ -401,4 +419,39 @@ class _TablePageState extends State { ], ); } + + void _showPopupMenu(BuildContext context, TableModel table) { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + + showMenu( + context: context, + position: RelativeRect.fromRect( + _tapPosition != null + ? Rect.fromLTWH(_tapPosition!.dx, _tapPosition!.dy, 0, 0) + : Rect.fromLTWH(100, 100, 0, 0), + Offset.zero & overlay.size, + ), + color: AppColors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + elevation: 1, + items: [ + PopupMenuItem( + onTap: () { + showDialog( + context: context, + builder: (context) => TransferTableDialog(fromTable: table), + ); + }, + child: Row( + children: [ + Icon(Icons.swap_horiz), + SizedBox(width: 8), + Text('Transfer'), + ], + ), + ), + ], + ); + } }