Compare commits
No commits in common. "5c683eda8d49800bf79b5ccd622434ec7c564548" and "634b33171693e1c47fc393e0e3dc369539d5b961" have entirely different histories.
5c683eda8d
...
634b331716
@ -23,74 +23,23 @@ class TableFormBloc extends Bloc<TableFormEvent, TableFormState> {
|
|||||||
Emitter<TableFormState> emit,
|
Emitter<TableFormState> emit,
|
||||||
) {
|
) {
|
||||||
return event.map(
|
return event.map(
|
||||||
nameChanged: (e) async {
|
updated: (e) async {
|
||||||
emit(state.copyWith(name: e.name));
|
|
||||||
},
|
|
||||||
capacityChanged: (e) async {
|
|
||||||
emit(state.copyWith(capacity: int.tryParse(e.capacity) ?? 0));
|
|
||||||
},
|
|
||||||
created: (e) async {
|
|
||||||
Either<TableFailure, Table> failureOrTable;
|
Either<TableFailure, Table> failureOrTable;
|
||||||
|
|
||||||
emit(state.copyWith(isCreating: true, failureOrTable: none()));
|
|
||||||
|
|
||||||
final isNameValid = state.name.isNotEmpty;
|
|
||||||
final isCapacityValid = state.capacity > 0;
|
|
||||||
|
|
||||||
if (isNameValid && isCapacityValid) {
|
|
||||||
failureOrTable = await _repository.createTable(
|
|
||||||
name: state.name,
|
|
||||||
capacity: state.capacity,
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
failureOrTable: optionOf(failureOrTable),
|
|
||||||
isCreating: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(state.copyWith(failureOrTable: none(), isCreating: false));
|
|
||||||
},
|
|
||||||
updated: (e) async {
|
|
||||||
emit(state.copyWith(isUpdating: true, failureOrTable: none()));
|
emit(state.copyWith(isUpdating: true, failureOrTable: none()));
|
||||||
|
|
||||||
await _repository.updatePosition(id: e.id, position: e.position);
|
failureOrTable = await _repository.updatePosition(
|
||||||
|
id: e.id,
|
||||||
|
position: e.position,
|
||||||
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
// failureOrTable: optionOf(failureOrTable),
|
failureOrTable: optionOf(failureOrTable),
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
transfer: (e) async {
|
|
||||||
Either<TableFailure, Unit> failureOrTableTransfer;
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(isTransfering: true, failureOrTableTransfer: none()),
|
|
||||||
);
|
|
||||||
|
|
||||||
final isFromTableIdValid = e.fromTableId.isNotEmpty;
|
|
||||||
final isToTableIdValid = e.toTableId.isNotEmpty;
|
|
||||||
|
|
||||||
if (isFromTableIdValid && isToTableIdValid) {
|
|
||||||
failureOrTableTransfer = await _repository.transferTable(
|
|
||||||
fromTableId: e.fromTableId,
|
|
||||||
toTableId: e.toTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
failureOrTableTransfer: optionOf(failureOrTableTransfer),
|
|
||||||
isTransfering: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(state.copyWith(failureOrTable: none(), isCreating: false));
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,16 +2,8 @@ part of 'table_form_bloc.dart';
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class TableFormEvent with _$TableFormEvent {
|
class TableFormEvent with _$TableFormEvent {
|
||||||
const factory TableFormEvent.nameChanged(String name) = _NameChanged;
|
|
||||||
const factory TableFormEvent.capacityChanged(String capacity) =
|
|
||||||
_CapacityChanged;
|
|
||||||
const factory TableFormEvent.created() = _Created;
|
|
||||||
const factory TableFormEvent.updated({
|
const factory TableFormEvent.updated({
|
||||||
required String id,
|
required String id,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
}) = _Updated;
|
}) = _Updated;
|
||||||
const factory TableFormEvent.transfer({
|
|
||||||
required String fromTableId,
|
|
||||||
required String toTableId,
|
|
||||||
}) = _Transfer;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,19 +3,9 @@ part of 'table_form_bloc.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class TableFormState with _$TableFormState {
|
class TableFormState with _$TableFormState {
|
||||||
factory TableFormState({
|
factory TableFormState({
|
||||||
required String name,
|
|
||||||
required int capacity,
|
|
||||||
required Option<Either<TableFailure, Table>> failureOrTable,
|
required Option<Either<TableFailure, Table>> failureOrTable,
|
||||||
required Option<Either<TableFailure, Unit>> failureOrTableTransfer,
|
|
||||||
@Default(false) bool isUpdating,
|
@Default(false) bool isUpdating,
|
||||||
@Default(false) bool isCreating,
|
|
||||||
@Default(false) bool isTransfering,
|
|
||||||
}) = _TableFormState;
|
}) = _TableFormState;
|
||||||
|
|
||||||
factory TableFormState.initial() => TableFormState(
|
factory TableFormState.initial() => TableFormState(failureOrTable: none());
|
||||||
failureOrTable: none(),
|
|
||||||
name: '',
|
|
||||||
capacity: 0,
|
|
||||||
failureOrTableTransfer: none(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,11 +40,7 @@ class TableLoaderBloc extends Bloc<TableLoaderEvent, TableLoaderState> {
|
|||||||
emit(newState);
|
emit(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
newState = await _mapFetchedToState(
|
newState = await _mapFetchedToState(newState, isRefresh: e.isRefresh);
|
||||||
newState,
|
|
||||||
isRefresh: e.isRefresh,
|
|
||||||
status: e.status,
|
|
||||||
);
|
|
||||||
emit(newState);
|
emit(newState);
|
||||||
},
|
},
|
||||||
updatedPostion: (e) async {
|
updatedPostion: (e) async {
|
||||||
@ -66,7 +62,6 @@ class TableLoaderBloc extends Bloc<TableLoaderEvent, TableLoaderState> {
|
|||||||
Future<TableLoaderState> _mapFetchedToState(
|
Future<TableLoaderState> _mapFetchedToState(
|
||||||
TableLoaderState state, {
|
TableLoaderState state, {
|
||||||
bool isRefresh = false,
|
bool isRefresh = false,
|
||||||
String? status,
|
|
||||||
}) async {
|
}) async {
|
||||||
state = state.copyWith(isFetching: false);
|
state = state.copyWith(isFetching: false);
|
||||||
|
|
||||||
@ -83,10 +78,7 @@ class TableLoaderBloc extends Bloc<TableLoaderEvent, TableLoaderState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final failureOrTable = await _repository.fetchTables(
|
final failureOrTable = await _repository.fetchTables(page: state.page);
|
||||||
page: state.page,
|
|
||||||
status: status,
|
|
||||||
);
|
|
||||||
|
|
||||||
state = failureOrTable.fold(
|
state = failureOrTable.fold(
|
||||||
(f) {
|
(f) {
|
||||||
|
|||||||
@ -19,19 +19,19 @@ final _privateConstructorUsedError = UnsupportedError(
|
|||||||
mixin _$TableLoaderEvent {
|
mixin _$TableLoaderEvent {
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(bool isRefresh, String? status) fetched,
|
required TResult Function(bool isRefresh) fetched,
|
||||||
required TResult Function(String id, Offset position) updatedPostion,
|
required TResult Function(String id, Offset position) updatedPostion,
|
||||||
required TResult Function(Table? table) setSelectedTable,
|
required TResult Function(Table? table) setSelectedTable,
|
||||||
}) => throw _privateConstructorUsedError;
|
}) => throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function(bool isRefresh, String? status)? fetched,
|
TResult? Function(bool isRefresh)? fetched,
|
||||||
TResult? Function(String id, Offset position)? updatedPostion,
|
TResult? Function(String id, Offset position)? updatedPostion,
|
||||||
TResult? Function(Table? table)? setSelectedTable,
|
TResult? Function(Table? table)? setSelectedTable,
|
||||||
}) => throw _privateConstructorUsedError;
|
}) => throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(bool isRefresh, String? status)? fetched,
|
TResult Function(bool isRefresh)? fetched,
|
||||||
TResult Function(String id, Offset position)? updatedPostion,
|
TResult Function(String id, Offset position)? updatedPostion,
|
||||||
TResult Function(Table? table)? setSelectedTable,
|
TResult Function(Table? table)? setSelectedTable,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
@ -86,7 +86,7 @@ abstract class _$$FetchedImplCopyWith<$Res> {
|
|||||||
$Res Function(_$FetchedImpl) then,
|
$Res Function(_$FetchedImpl) then,
|
||||||
) = __$$FetchedImplCopyWithImpl<$Res>;
|
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isRefresh, String? status});
|
$Res call({bool isRefresh});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -102,17 +102,13 @@ class __$$FetchedImplCopyWithImpl<$Res>
|
|||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({Object? isRefresh = null, Object? status = freezed}) {
|
$Res call({Object? isRefresh = null}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$FetchedImpl(
|
_$FetchedImpl(
|
||||||
isRefresh: null == isRefresh
|
isRefresh: null == isRefresh
|
||||||
? _value.isRefresh
|
? _value.isRefresh
|
||||||
: isRefresh // ignore: cast_nullable_to_non_nullable
|
: isRefresh // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
status: freezed == status
|
|
||||||
? _value.status
|
|
||||||
: status // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -121,17 +117,15 @@ class __$$FetchedImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
||||||
class _$FetchedImpl implements _Fetched {
|
class _$FetchedImpl implements _Fetched {
|
||||||
const _$FetchedImpl({this.isRefresh = false, this.status});
|
const _$FetchedImpl({this.isRefresh = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool isRefresh;
|
final bool isRefresh;
|
||||||
@override
|
|
||||||
final String? status;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'TableLoaderEvent.fetched(isRefresh: $isRefresh, status: $status)';
|
return 'TableLoaderEvent.fetched(isRefresh: $isRefresh)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -140,12 +134,11 @@ class _$FetchedImpl implements _Fetched {
|
|||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$FetchedImpl &&
|
other is _$FetchedImpl &&
|
||||||
(identical(other.isRefresh, isRefresh) ||
|
(identical(other.isRefresh, isRefresh) ||
|
||||||
other.isRefresh == isRefresh) &&
|
other.isRefresh == isRefresh));
|
||||||
(identical(other.status, status) || other.status == status));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, isRefresh, status);
|
int get hashCode => Object.hash(runtimeType, isRefresh);
|
||||||
|
|
||||||
/// Create a copy of TableLoaderEvent
|
/// Create a copy of TableLoaderEvent
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -158,33 +151,33 @@ class _$FetchedImpl implements _Fetched {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(bool isRefresh, String? status) fetched,
|
required TResult Function(bool isRefresh) fetched,
|
||||||
required TResult Function(String id, Offset position) updatedPostion,
|
required TResult Function(String id, Offset position) updatedPostion,
|
||||||
required TResult Function(Table? table) setSelectedTable,
|
required TResult Function(Table? table) setSelectedTable,
|
||||||
}) {
|
}) {
|
||||||
return fetched(isRefresh, status);
|
return fetched(isRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function(bool isRefresh, String? status)? fetched,
|
TResult? Function(bool isRefresh)? fetched,
|
||||||
TResult? Function(String id, Offset position)? updatedPostion,
|
TResult? Function(String id, Offset position)? updatedPostion,
|
||||||
TResult? Function(Table? table)? setSelectedTable,
|
TResult? Function(Table? table)? setSelectedTable,
|
||||||
}) {
|
}) {
|
||||||
return fetched?.call(isRefresh, status);
|
return fetched?.call(isRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(bool isRefresh, String? status)? fetched,
|
TResult Function(bool isRefresh)? fetched,
|
||||||
TResult Function(String id, Offset position)? updatedPostion,
|
TResult Function(String id, Offset position)? updatedPostion,
|
||||||
TResult Function(Table? table)? setSelectedTable,
|
TResult Function(Table? table)? setSelectedTable,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (fetched != null) {
|
if (fetched != null) {
|
||||||
return fetched(isRefresh, status);
|
return fetched(isRefresh);
|
||||||
}
|
}
|
||||||
return orElse();
|
return orElse();
|
||||||
}
|
}
|
||||||
@ -225,11 +218,9 @@ class _$FetchedImpl implements _Fetched {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Fetched implements TableLoaderEvent {
|
abstract class _Fetched implements TableLoaderEvent {
|
||||||
const factory _Fetched({final bool isRefresh, final String? status}) =
|
const factory _Fetched({final bool isRefresh}) = _$FetchedImpl;
|
||||||
_$FetchedImpl;
|
|
||||||
|
|
||||||
bool get isRefresh;
|
bool get isRefresh;
|
||||||
String? get status;
|
|
||||||
|
|
||||||
/// Create a copy of TableLoaderEvent
|
/// Create a copy of TableLoaderEvent
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -319,7 +310,7 @@ class _$UpdatedPositionImpl implements _UpdatedPosition {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(bool isRefresh, String? status) fetched,
|
required TResult Function(bool isRefresh) fetched,
|
||||||
required TResult Function(String id, Offset position) updatedPostion,
|
required TResult Function(String id, Offset position) updatedPostion,
|
||||||
required TResult Function(Table? table) setSelectedTable,
|
required TResult Function(Table? table) setSelectedTable,
|
||||||
}) {
|
}) {
|
||||||
@ -329,7 +320,7 @@ class _$UpdatedPositionImpl implements _UpdatedPosition {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function(bool isRefresh, String? status)? fetched,
|
TResult? Function(bool isRefresh)? fetched,
|
||||||
TResult? Function(String id, Offset position)? updatedPostion,
|
TResult? Function(String id, Offset position)? updatedPostion,
|
||||||
TResult? Function(Table? table)? setSelectedTable,
|
TResult? Function(Table? table)? setSelectedTable,
|
||||||
}) {
|
}) {
|
||||||
@ -339,7 +330,7 @@ class _$UpdatedPositionImpl implements _UpdatedPosition {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(bool isRefresh, String? status)? fetched,
|
TResult Function(bool isRefresh)? fetched,
|
||||||
TResult Function(String id, Offset position)? updatedPostion,
|
TResult Function(String id, Offset position)? updatedPostion,
|
||||||
TResult Function(Table? table)? setSelectedTable,
|
TResult Function(Table? table)? setSelectedTable,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
@ -490,7 +481,7 @@ class _$SetSelectedTableImpl implements _SetSelectedTable {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function(bool isRefresh, String? status) fetched,
|
required TResult Function(bool isRefresh) fetched,
|
||||||
required TResult Function(String id, Offset position) updatedPostion,
|
required TResult Function(String id, Offset position) updatedPostion,
|
||||||
required TResult Function(Table? table) setSelectedTable,
|
required TResult Function(Table? table) setSelectedTable,
|
||||||
}) {
|
}) {
|
||||||
@ -500,7 +491,7 @@ class _$SetSelectedTableImpl implements _SetSelectedTable {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function(bool isRefresh, String? status)? fetched,
|
TResult? Function(bool isRefresh)? fetched,
|
||||||
TResult? Function(String id, Offset position)? updatedPostion,
|
TResult? Function(String id, Offset position)? updatedPostion,
|
||||||
TResult? Function(Table? table)? setSelectedTable,
|
TResult? Function(Table? table)? setSelectedTable,
|
||||||
}) {
|
}) {
|
||||||
@ -510,7 +501,7 @@ class _$SetSelectedTableImpl implements _SetSelectedTable {
|
|||||||
@override
|
@override
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function(bool isRefresh, String? status)? fetched,
|
TResult Function(bool isRefresh)? fetched,
|
||||||
TResult Function(String id, Offset position)? updatedPostion,
|
TResult Function(String id, Offset position)? updatedPostion,
|
||||||
TResult Function(Table? table)? setSelectedTable,
|
TResult Function(Table? table)? setSelectedTable,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
|
|||||||
@ -2,10 +2,8 @@ part of 'table_loader_bloc.dart';
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class TableLoaderEvent with _$TableLoaderEvent {
|
class TableLoaderEvent with _$TableLoaderEvent {
|
||||||
const factory TableLoaderEvent.fetched({
|
const factory TableLoaderEvent.fetched({@Default(false) bool isRefresh}) =
|
||||||
@Default(false) bool isRefresh,
|
_Fetched;
|
||||||
String? status,
|
|
||||||
}) = _Fetched;
|
|
||||||
const factory TableLoaderEvent.updatedPostion({
|
const factory TableLoaderEvent.updatedPostion({
|
||||||
required String id,
|
required String id,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
|
|||||||
@ -4,21 +4,10 @@ abstract class ITableRepository {
|
|||||||
Future<Either<TableFailure, ListTable>> fetchTables({
|
Future<Either<TableFailure, ListTable>> fetchTables({
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 50,
|
int limit = 50,
|
||||||
String? status,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Either<TableFailure, Table>> createTable({
|
|
||||||
required String name,
|
|
||||||
required int capacity,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<TableFailure, Table>> updatePosition({
|
Future<Either<TableFailure, Table>> updatePosition({
|
||||||
required String id,
|
required String id,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<TableFailure, Unit>> transferTable({
|
|
||||||
required String fromTableId,
|
|
||||||
required String toTableId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,6 @@ class OutletRepository implements IOutletRepository {
|
|||||||
return left(result.error!);
|
return left(result.error!);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _localDataProvider.saveCurrentOutlet(result.data!);
|
|
||||||
final outlet = result.data!.toDomain();
|
final outlet = result.data!.toDomain();
|
||||||
return right(outlet);
|
return right(outlet);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:data_channel/data_channel.dart';
|
import 'package:data_channel/data_channel.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
@ -22,19 +21,12 @@ class TableRemoteDataProvider {
|
|||||||
Future<DC<TableFailure, ListTableDto>> fetchTables({
|
Future<DC<TableFailure, ListTableDto>> fetchTables({
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 50,
|
int limit = 50,
|
||||||
String? status,
|
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic> queryParameters = {'page': page, 'limit': limit};
|
|
||||||
|
|
||||||
if (status != null) {
|
|
||||||
queryParameters['status'] = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await _apiClient.get(
|
final response = await _apiClient.get(
|
||||||
ApiPath.tables,
|
ApiPath.tables,
|
||||||
headers: getAuthorizationHeader(),
|
headers: getAuthorizationHeader(),
|
||||||
params: queryParameters,
|
params: {'page': page, 'limit': limit},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['data'] == null) {
|
if (response.data['data'] == null) {
|
||||||
@ -52,38 +44,6 @@ class TableRemoteDataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DC<TableFailure, TableDto>> storeTable({
|
|
||||||
required String outletId,
|
|
||||||
required String name,
|
|
||||||
required int capacity,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final response = await _apiClient.post(
|
|
||||||
ApiPath.tables,
|
|
||||||
data: {
|
|
||||||
'outlet_id': outletId,
|
|
||||||
'table_name': name,
|
|
||||||
'capacity': capacity,
|
|
||||||
'position_x': 200,
|
|
||||||
'position_y': 200,
|
|
||||||
},
|
|
||||||
headers: getAuthorizationHeader(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
|
||||||
return DC.error(TableFailure.unexpectedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
final table = TableDto.fromJson(
|
|
||||||
response.data['data'] as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
return DC.data(table);
|
|
||||||
} on ApiFailure catch (e, s) {
|
|
||||||
log('storeTableError', name: _logName, error: e, stackTrace: s);
|
|
||||||
return DC.error(TableFailure.serverError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DC<TableFailure, TableDto>> updatePosition({
|
Future<DC<TableFailure, TableDto>> updatePosition({
|
||||||
required String id,
|
required String id,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
@ -111,26 +71,4 @@ class TableRemoteDataProvider {
|
|||||||
return DC.error(TableFailure.serverError(e));
|
return DC.error(TableFailure.serverError(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DC<TableFailure, Unit>> transferTable({
|
|
||||||
required String fromTableId,
|
|
||||||
required String toTableId,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final response = await _apiClient.put(
|
|
||||||
'${ApiPath.tables}/transfer',
|
|
||||||
data: {'from_table': fromTableId, 'to_table': toTableId},
|
|
||||||
headers: getAuthorizationHeader(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
|
||||||
return DC.error(TableFailure.unexpectedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return DC.data(unit);
|
|
||||||
} on ApiFailure catch (e, s) {
|
|
||||||
log('transferTableError', name: _logName, error: e, stackTrace: s);
|
|
||||||
return DC.error(TableFailure.serverError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,28 +5,24 @@ import 'package:dartz/dartz.dart';
|
|||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
import '../../../domain/table/table.dart';
|
import '../../../domain/table/table.dart';
|
||||||
import '../../outlet/datasources/local_data_provider.dart';
|
|
||||||
import '../datasources/remote_data_provider.dart';
|
import '../datasources/remote_data_provider.dart';
|
||||||
|
|
||||||
@Injectable(as: ITableRepository)
|
@Injectable(as: ITableRepository)
|
||||||
class TableRepository implements ITableRepository {
|
class TableRepository implements ITableRepository {
|
||||||
final TableRemoteDataProvider _remoteDataProvider;
|
final TableRemoteDataProvider _remoteDataProvider;
|
||||||
final OutletLocalDatasource _outletLocalDatasource;
|
|
||||||
final _logName = 'TableRepository';
|
final _logName = 'TableRepository';
|
||||||
|
|
||||||
TableRepository(this._remoteDataProvider, this._outletLocalDatasource);
|
TableRepository(this._remoteDataProvider);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<TableFailure, ListTable>> fetchTables({
|
Future<Either<TableFailure, ListTable>> fetchTables({
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 50,
|
int limit = 50,
|
||||||
String? status,
|
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _remoteDataProvider.fetchTables(
|
final result = await _remoteDataProvider.fetchTables(
|
||||||
page: page,
|
page: page,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
status: status,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
@ -42,33 +38,6 @@ class TableRepository implements ITableRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<TableFailure, Table>> createTable({
|
|
||||||
required String name,
|
|
||||||
required int capacity,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final outlet = await _outletLocalDatasource.currentOutlet();
|
|
||||||
|
|
||||||
final result = await _remoteDataProvider.storeTable(
|
|
||||||
outletId: outlet.id,
|
|
||||||
name: name,
|
|
||||||
capacity: capacity,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.hasError) {
|
|
||||||
return left(result.error!);
|
|
||||||
}
|
|
||||||
|
|
||||||
final table = result.data!.toDomain();
|
|
||||||
|
|
||||||
return right(table);
|
|
||||||
} catch (e) {
|
|
||||||
log('fetchCreateTable', name: _logName, error: e);
|
|
||||||
return left(const TableFailure.unexpectedError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<TableFailure, Table>> updatePosition({
|
Future<Either<TableFailure, Table>> updatePosition({
|
||||||
required String id,
|
required String id,
|
||||||
@ -92,26 +61,4 @@ class TableRepository implements ITableRepository {
|
|||||||
return left(const TableFailure.unexpectedError());
|
return left(const TableFailure.unexpectedError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<TableFailure, Unit>> transferTable({
|
|
||||||
required String fromTableId,
|
|
||||||
required String toTableId,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final result = await _remoteDataProvider.transferTable(
|
|
||||||
fromTableId: fromTableId,
|
|
||||||
toTableId: toTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.hasError) {
|
|
||||||
return left(result.error!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return right(unit);
|
|
||||||
} catch (e) {
|
|
||||||
log('transferTableError', name: _logName, error: e);
|
|
||||||
return left(const TableFailure.unexpectedError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,18 +149,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh<_i708.CategoryLocalDataProvider>(),
|
gh<_i708.CategoryLocalDataProvider>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.factory<_i983.ITableRepository>(
|
|
||||||
() => _i824.TableRepository(
|
|
||||||
gh<_i95.TableRemoteDataProvider>(),
|
|
||||||
gh<_i693.OutletLocalDatasource>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.factory<_i248.TableFormBloc>(
|
|
||||||
() => _i248.TableFormBloc(gh<_i983.ITableRepository>()),
|
|
||||||
);
|
|
||||||
gh.factory<_i424.TableLoaderBloc>(
|
|
||||||
() => _i424.TableLoaderBloc(gh<_i983.ITableRepository>()),
|
|
||||||
);
|
|
||||||
gh.factory<_i44.IProductRepository>(
|
gh.factory<_i44.IProductRepository>(
|
||||||
() => _i763.ProductRepository(
|
() => _i763.ProductRepository(
|
||||||
gh<_i707.ProductRemoteDataProvider>(),
|
gh<_i707.ProductRemoteDataProvider>(),
|
||||||
@ -179,6 +167,9 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i1018.CategoryLoaderBloc>(
|
gh.factory<_i1018.CategoryLoaderBloc>(
|
||||||
() => _i1018.CategoryLoaderBloc(gh<_i502.ICategoryRepository>()),
|
() => _i1018.CategoryLoaderBloc(gh<_i502.ICategoryRepository>()),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i983.ITableRepository>(
|
||||||
|
() => _i824.TableRepository(gh<_i95.TableRemoteDataProvider>()),
|
||||||
|
);
|
||||||
gh.factory<_i343.AuthBloc>(
|
gh.factory<_i343.AuthBloc>(
|
||||||
() => _i343.AuthBloc(
|
() => _i343.AuthBloc(
|
||||||
gh<_i776.IAuthRepository>(),
|
gh<_i776.IAuthRepository>(),
|
||||||
@ -197,6 +188,12 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh<_i502.ICategoryRepository>(),
|
gh<_i502.ICategoryRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i424.TableLoaderBloc>(
|
||||||
|
() => _i424.TableLoaderBloc(gh<_i983.ITableRepository>()),
|
||||||
|
);
|
||||||
|
gh.factory<_i248.TableFormBloc>(
|
||||||
|
() => _i248.TableFormBloc(gh<_i983.ITableRepository>()),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,6 @@ import '../application/category/category_loader/category_loader_bloc.dart';
|
|||||||
import '../application/checkout/checkout_form/checkout_form_bloc.dart';
|
import '../application/checkout/checkout_form/checkout_form_bloc.dart';
|
||||||
import '../application/outlet/outlet_loader/outlet_loader_bloc.dart';
|
import '../application/outlet/outlet_loader/outlet_loader_bloc.dart';
|
||||||
import '../application/product/product_loader/product_loader_bloc.dart';
|
import '../application/product/product_loader/product_loader_bloc.dart';
|
||||||
import '../application/table/table_form/table_form_bloc.dart';
|
|
||||||
import '../application/table/table_loader/table_loader_bloc.dart';
|
|
||||||
import '../common/theme/theme.dart';
|
import '../common/theme/theme.dart';
|
||||||
import '../common/constant/app_constant.dart';
|
import '../common/constant/app_constant.dart';
|
||||||
import '../injection.dart';
|
import '../injection.dart';
|
||||||
@ -33,8 +31,6 @@ class _AppWidgetState extends State<AppWidget> {
|
|||||||
BlocProvider(create: (context) => getIt<CategoryLoaderBloc>()),
|
BlocProvider(create: (context) => getIt<CategoryLoaderBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<ProductLoaderBloc>()),
|
BlocProvider(create: (context) => getIt<ProductLoaderBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<CheckoutFormBloc>()),
|
BlocProvider(create: (context) => getIt<CheckoutFormBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<TableLoaderBloc>()),
|
|
||||||
BlocProvider(create: (context) => getIt<TableFormBloc>()),
|
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import '../../../../application/table/table_form/table_form_bloc.dart';
|
|
||||||
import '../../button/button.dart';
|
|
||||||
import '../../field/field.dart';
|
|
||||||
import '../../spaces/space.dart';
|
|
||||||
import '../dialog.dart';
|
|
||||||
|
|
||||||
class TableCreateDialog extends StatelessWidget {
|
|
||||||
const TableCreateDialog({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<TableFormBloc, TableFormState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return CustomModalDialog(
|
|
||||||
title: 'Tambah Meja',
|
|
||||||
subtitle: 'Silahkan isi data meja',
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
vertical: 24.0,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
AppTextFormField(
|
|
||||||
label: 'Nama Meja',
|
|
||||||
onChanged: (value) {
|
|
||||||
context.read<TableFormBloc>().add(
|
|
||||||
TableFormEvent.nameChanged(value),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SpaceHeight(16),
|
|
||||||
AppTextFormField(
|
|
||||||
label: 'Kapasitas',
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
onChanged: (value) {
|
|
||||||
context.read<TableFormBloc>().add(
|
|
||||||
TableFormEvent.capacityChanged(value),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SpaceHeight(24),
|
|
||||||
AppElevatedButton.filled(
|
|
||||||
onPressed: () {
|
|
||||||
context.read<TableFormBloc>().add(
|
|
||||||
const TableFormEvent.created(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
label: "Simpan",
|
|
||||||
isLoading: state.isCreating,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,284 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:dropdown_search/dropdown_search.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import '../../../../application/table/table_form/table_form_bloc.dart';
|
|
||||||
import '../../../../application/table/table_loader/table_loader_bloc.dart';
|
|
||||||
import '../../../../common/extension/extension.dart';
|
|
||||||
import '../../../../common/theme/theme.dart';
|
|
||||||
import '../../../../domain/table/table.dart' as t;
|
|
||||||
import '../../button/button.dart';
|
|
||||||
import '../../loader/loader_with_text.dart';
|
|
||||||
import '../../spaces/space.dart';
|
|
||||||
import '../../toast/flushbar.dart';
|
|
||||||
import '../dialog.dart';
|
|
||||||
|
|
||||||
class TableTransferDialog extends StatefulWidget {
|
|
||||||
final t.Table fromTable;
|
|
||||||
const TableTransferDialog({super.key, required this.fromTable});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<TableTransferDialog> createState() => _TableTransferDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TableTransferDialogState extends State<TableTransferDialog> {
|
|
||||||
t.Table? selectTable;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
context.read<TableLoaderBloc>().add(
|
|
||||||
TableLoaderEvent.fetched(isRefresh: true, status: 'available'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return 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: [
|
|
||||||
Text(
|
|
||||||
'Pilih Meja',
|
|
||||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
const SpaceHeight(6.0),
|
|
||||||
BlocBuilder<TableLoaderBloc, TableLoaderState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final availableTables = state.tables;
|
|
||||||
|
|
||||||
if (state.isFetching) {
|
|
||||||
return Center(child: const LoaderWithText());
|
|
||||||
}
|
|
||||||
|
|
||||||
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<t.Table>(
|
|
||||||
items: state.tables,
|
|
||||||
selectedItem: selectTable,
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
itemAsString: (t.Table table) => table.tableName,
|
|
||||||
compareFn: (t.Table? item1, t.Table? item2) {
|
|
||||||
return item1?.id == item2?.id;
|
|
||||||
},
|
|
||||||
onChanged: (t.Table? selectedTable) {
|
|
||||||
if (selectedTable != null) {
|
|
||||||
setState(() {
|
|
||||||
selectTable = selectedTable;
|
|
||||||
});
|
|
||||||
log("selectTable: ${selectTable!.tableName}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validator: (t.Table? value) {
|
|
||||||
if (value == null) {
|
|
||||||
return "Meja harus dipilih";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SpaceHeight(24),
|
|
||||||
BlocBuilder<TableFormBloc, TableFormState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return AppElevatedButton.filled(
|
|
||||||
onPressed: () {
|
|
||||||
if (selectTable == null) {
|
|
||||||
AppFlushbar.showError(
|
|
||||||
context,
|
|
||||||
'Silahkan Pilih Meja Tujuan',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.read<TableFormBloc>().add(
|
|
||||||
TableFormEvent.transfer(
|
|
||||||
fromTableId: widget.fromTable.id,
|
|
||||||
toTableId: selectTable!.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
label: "Transfer",
|
|
||||||
isLoading: state.isTransfering,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../../../common/theme/theme.dart';
|
import '../../../common/theme/theme.dart';
|
||||||
import '../../../domain/auth/auth.dart';
|
import '../../../domain/auth/auth.dart';
|
||||||
import '../../../domain/table/table.dart';
|
|
||||||
|
|
||||||
class AppFlushbar {
|
class AppFlushbar {
|
||||||
static void showSuccess(BuildContext context, String message) {
|
static void showSuccess(BuildContext context, String message) {
|
||||||
@ -51,18 +50,4 @@ class AppFlushbar {
|
|||||||
unexpectedError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
|
unexpectedError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
static void showTableFailureToast(
|
|
||||||
BuildContext context,
|
|
||||||
TableFailure failure,
|
|
||||||
) => showError(
|
|
||||||
context,
|
|
||||||
failure.map(
|
|
||||||
serverError: (value) => value.failure.toStringFormatted(context),
|
|
||||||
dynamicErrorMessage: (value) => value.erroMessage,
|
|
||||||
unexpectedError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
|
|
||||||
empty: (value) => 'Tidak ada data',
|
|
||||||
localStorageError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,7 @@ import '../../../../../common/extension/extension.dart';
|
|||||||
import '../../../../../common/theme/theme.dart';
|
import '../../../../../common/theme/theme.dart';
|
||||||
import '../../../../../domain/table/table.dart' as t;
|
import '../../../../../domain/table/table.dart' as t;
|
||||||
import '../../../../../injection.dart';
|
import '../../../../../injection.dart';
|
||||||
import '../../../../components/dialog/table/table_create_dialog.dart';
|
|
||||||
import '../../../../components/dialog/table/table_transfer_dialog.dart';
|
|
||||||
import '../../../../components/loader/loader_with_text.dart';
|
import '../../../../components/loader/loader_with_text.dart';
|
||||||
import '../../../../components/toast/flushbar.dart';
|
|
||||||
import 'widgets/floating_bottom_navbar.dart';
|
import 'widgets/floating_bottom_navbar.dart';
|
||||||
import 'widgets/table_card.dart';
|
import 'widgets/table_card.dart';
|
||||||
|
|
||||||
@ -30,6 +27,7 @@ class TablePage extends StatefulWidget implements AutoRouteWrapper {
|
|||||||
getIt<TableLoaderBloc>()
|
getIt<TableLoaderBloc>()
|
||||||
..add(TableLoaderEvent.fetched(isRefresh: true)),
|
..add(TableLoaderEvent.fetched(isRefresh: true)),
|
||||||
),
|
),
|
||||||
|
BlocProvider(create: (context) => getIt<TableFormBloc>()),
|
||||||
],
|
],
|
||||||
child: this,
|
child: this,
|
||||||
);
|
);
|
||||||
@ -45,251 +43,203 @@ class _TablePageState extends State<TablePage> {
|
|||||||
final double mapWidth = context.deviceWidth * 2;
|
final double mapWidth = context.deviceWidth * 2;
|
||||||
final double mapHeight = context.deviceHeight * 1.5;
|
final double mapHeight = context.deviceHeight * 1.5;
|
||||||
|
|
||||||
return MultiBlocListener(
|
return Scaffold(
|
||||||
listeners: [
|
backgroundColor: AppColor.background,
|
||||||
BlocListener<TableFormBloc, TableFormState>(
|
appBar: AppBar(
|
||||||
listenWhen: (previous, current) =>
|
title: const Text("Layout Meja"),
|
||||||
previous.failureOrTable != current.failureOrTable,
|
backgroundColor: Colors.white,
|
||||||
listener: (context, state) {
|
foregroundColor: Colors.black,
|
||||||
state.failureOrTable.fold(() {}, (either) {
|
elevation: 0.5,
|
||||||
either.fold(
|
actions: [
|
||||||
(f) => AppFlushbar.showTableFailureToast(context, f),
|
IconButton(
|
||||||
(success) {
|
icon: const Icon(Icons.refresh),
|
||||||
if (context.mounted) {
|
onPressed: () {
|
||||||
context.router.maybePop();
|
context.read<TableLoaderBloc>().add(
|
||||||
context.read<TableLoaderBloc>().add(
|
const TableLoaderEvent.fetched(isRefresh: true),
|
||||||
TableLoaderEvent.fetched(isRefresh: true),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
},
|
),
|
||||||
),
|
IconButton(
|
||||||
BlocListener<TableFormBloc, TableFormState>(
|
icon: const Icon(Icons.add),
|
||||||
listenWhen: (previous, current) =>
|
onPressed: () {
|
||||||
previous.failureOrTableTransfer != current.failureOrTableTransfer,
|
// showDialog(
|
||||||
listener: (context, state) {
|
// context: context,
|
||||||
state.failureOrTableTransfer.fold(() {}, (either) {
|
// builder: (context) => FormTableNewDialog(),
|
||||||
either.fold(
|
// );
|
||||||
(f) => AppFlushbar.showTableFailureToast(context, f),
|
},
|
||||||
(success) {
|
),
|
||||||
if (context.mounted) {
|
],
|
||||||
context.router.maybePop();
|
bottom: PreferredSize(
|
||||||
context.read<TableLoaderBloc>().add(
|
preferredSize: const Size.fromHeight(20),
|
||||||
TableLoaderEvent.fetched(isRefresh: true),
|
child: Padding(
|
||||||
);
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
}
|
child: Row(
|
||||||
},
|
children: [
|
||||||
);
|
_buildLegendDot(Colors.blue[200]!, "Available"),
|
||||||
});
|
const SizedBox(width: 16),
|
||||||
},
|
_buildLegendDot(Colors.orange[200]!, "Occupied"),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: AppColor.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text("Layout Meja"),
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
elevation: 0.5,
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.refresh),
|
|
||||||
onPressed: () {
|
|
||||||
context.read<TableLoaderBloc>().add(
|
|
||||||
const TableLoaderEvent.fetched(isRefresh: true),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => TableCreateDialog(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(20),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
_buildLegendDot(Colors.blue[200]!, "Available"),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
_buildLegendDot(Colors.orange[200]!, "Occupied"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: BlocBuilder<TableLoaderBloc, TableLoaderState>(
|
),
|
||||||
builder: (context, state) {
|
body: BlocBuilder<TableLoaderBloc, TableLoaderState>(
|
||||||
if (state.isFetching) {
|
builder: (context, state) {
|
||||||
return const Center(child: LoaderWithText());
|
if (state.isFetching) {
|
||||||
}
|
return const Center(child: LoaderWithText());
|
||||||
return SafeArea(
|
}
|
||||||
child: Stack(
|
return SafeArea(
|
||||||
children: [
|
child: Stack(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Expanded(
|
children: [
|
||||||
flex: 5,
|
Expanded(
|
||||||
child: InteractiveViewer(
|
flex: 5,
|
||||||
panEnabled: true,
|
child: InteractiveViewer(
|
||||||
scaleEnabled: true,
|
panEnabled: true,
|
||||||
constrained: false,
|
scaleEnabled: true,
|
||||||
boundaryMargin: const EdgeInsets.all(80),
|
constrained: false,
|
||||||
minScale: 0.3,
|
boundaryMargin: const EdgeInsets.all(80),
|
||||||
maxScale: 3.0,
|
minScale: 0.3,
|
||||||
alignment: Alignment.topLeft,
|
maxScale: 3.0,
|
||||||
child: Container(
|
alignment: Alignment.topLeft,
|
||||||
width: mapWidth,
|
child: Container(
|
||||||
height: mapHeight,
|
width: mapWidth,
|
||||||
decoration: BoxDecoration(
|
height: mapHeight,
|
||||||
color: const Color(0xFFF7F8FA),
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
color: const Color(0xFFF7F8FA),
|
||||||
color: Colors.grey[300]!,
|
border: Border.all(
|
||||||
width: 2,
|
color: Colors.grey[300]!,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
...List.generate(
|
||||||
|
20,
|
||||||
|
(i) => Positioned(
|
||||||
|
left: i * 100.0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 1,
|
||||||
|
color: Colors.grey[200],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
...List.generate(
|
||||||
child: Stack(
|
15,
|
||||||
children: [
|
(i) => Positioned(
|
||||||
...List.generate(
|
top: i * 100.0,
|
||||||
20,
|
left: 0,
|
||||||
(i) => Positioned(
|
right: 0,
|
||||||
left: i * 100.0,
|
child: Container(
|
||||||
top: 0,
|
height: 1,
|
||||||
bottom: 0,
|
color: Colors.grey[200],
|
||||||
child: Container(
|
|
||||||
width: 1,
|
|
||||||
color: Colors.grey[200],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...List.generate(
|
),
|
||||||
15,
|
...state.tables.map((table) {
|
||||||
(i) => Positioned(
|
final isSelected = state.selectedTable == table;
|
||||||
top: i * 100.0,
|
return Positioned(
|
||||||
left: 0,
|
left: table.positionX,
|
||||||
right: 0,
|
top: table.positionY,
|
||||||
child: Container(
|
child: Draggable<t.Table>(
|
||||||
height: 1,
|
data: table,
|
||||||
color: Colors.grey[200],
|
feedback: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TableCard(
|
||||||
|
table: table,
|
||||||
|
isSelected: isSelected,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
childWhenDragging: Opacity(
|
||||||
),
|
opacity: 0.5,
|
||||||
...state.tables.map((table) {
|
child: TableCard(
|
||||||
final isSelected =
|
table: table,
|
||||||
state.selectedTable == table;
|
isSelected: isSelected,
|
||||||
return Positioned(
|
|
||||||
left: table.positionX,
|
|
||||||
top: table.positionY,
|
|
||||||
child: Draggable<t.Table>(
|
|
||||||
data: table,
|
|
||||||
feedback: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: TableCard(
|
|
||||||
table: table,
|
|
||||||
isSelected: isSelected,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
childWhenDragging: Opacity(
|
),
|
||||||
opacity: 0.5,
|
onDragStarted: () {
|
||||||
child: TableCard(
|
setState(() {
|
||||||
table: table,
|
draggingTable = table;
|
||||||
isSelected: isSelected,
|
});
|
||||||
|
},
|
||||||
|
onDraggableCanceled: (velocity, offset) {
|
||||||
|
setState(() {
|
||||||
|
draggingTable = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDragEnd: (details) {
|
||||||
|
final RenderBox box =
|
||||||
|
context.findRenderObject()
|
||||||
|
as RenderBox;
|
||||||
|
final Offset local = box.globalToLocal(
|
||||||
|
details.offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
final newX = local.dx.clamp(
|
||||||
|
0.0,
|
||||||
|
mapWidth - 120,
|
||||||
|
);
|
||||||
|
final newY = local.dy.clamp(
|
||||||
|
0.0,
|
||||||
|
mapHeight - 80,
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
draggingTable = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update position locally in bloc state
|
||||||
|
context.read<TableLoaderBloc>().add(
|
||||||
|
TableLoaderEvent.updatedPostion(
|
||||||
|
id: table.id,
|
||||||
|
position: Offset(newX, newY),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
onDragStarted: () {
|
context.read<TableFormBloc>().add(
|
||||||
setState(() {
|
TableFormEvent.updated(
|
||||||
draggingTable = table;
|
id: table.id,
|
||||||
});
|
position: Offset(newX, newY),
|
||||||
},
|
),
|
||||||
onDraggableCanceled: (velocity, offset) {
|
);
|
||||||
setState(() {
|
},
|
||||||
draggingTable = null;
|
child: GestureDetector(
|
||||||
});
|
onTap: () {
|
||||||
},
|
|
||||||
onDragEnd: (details) {
|
|
||||||
final RenderBox box =
|
|
||||||
context.findRenderObject()
|
|
||||||
as RenderBox;
|
|
||||||
final Offset local = box.globalToLocal(
|
|
||||||
details.offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
final newX = local.dx.clamp(
|
|
||||||
0.0,
|
|
||||||
mapWidth - 120,
|
|
||||||
);
|
|
||||||
final newY = local.dy.clamp(
|
|
||||||
0.0,
|
|
||||||
mapHeight - 80,
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
draggingTable = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update position locally in bloc state
|
|
||||||
context.read<TableLoaderBloc>().add(
|
context.read<TableLoaderBloc>().add(
|
||||||
TableLoaderEvent.updatedPostion(
|
TableLoaderEvent.setSelectedTable(
|
||||||
id: table.id,
|
table,
|
||||||
position: Offset(newX, newY),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
context.read<TableFormBloc>().add(
|
|
||||||
TableFormEvent.updated(
|
|
||||||
id: table.id,
|
|
||||||
position: Offset(newX, newY),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: GestureDetector(
|
onTapDown: (details) =>
|
||||||
onTap: () {
|
tapPosition = details.globalPosition,
|
||||||
context.read<TableLoaderBloc>().add(
|
onLongPress: () {
|
||||||
TableLoaderEvent.setSelectedTable(
|
if (table.status.isOccupied) {
|
||||||
table,
|
_showPopupMenu(context, table);
|
||||||
),
|
}
|
||||||
);
|
},
|
||||||
},
|
child: TableCard(
|
||||||
onTapDown: (details) => tapPosition =
|
table: table,
|
||||||
details.globalPosition,
|
isSelected: isSelected,
|
||||||
onLongPress: () {
|
|
||||||
if (table.status.isOccupied) {
|
|
||||||
context.read<TableLoaderBloc>().add(
|
|
||||||
TableLoaderEvent.setSelectedTable(
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_showPopupMenu(context, table);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: TableCard(
|
|
||||||
table: table,
|
|
||||||
isSelected: isSelected,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
);
|
||||||
],
|
}),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
TableFloatingBottomNavbar(selectedTable: state.selectedTable),
|
),
|
||||||
],
|
TableFloatingBottomNavbar(selectedTable: state.selectedTable),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -322,10 +272,10 @@ class _TablePageState extends State<TablePage> {
|
|||||||
items: [
|
items: [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
// showDialog(
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (context) => TableTransferDialog(fromTable: table),
|
// builder: (context) => TransferTableDialog(fromTable: table),
|
||||||
);
|
// );
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -345,14 +345,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
dropdown_search:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: dropdown_search
|
|
||||||
sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.6"
|
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -36,7 +36,6 @@ dependencies:
|
|||||||
sqflite: ^2.4.2
|
sqflite: ^2.4.2
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
dropdown_search: ^5.0.6
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user