create table

This commit is contained in:
efrilm
2025-10-26 18:47:51 +07:00
parent 634b331716
commit d5ed3f62ff
13 changed files with 949 additions and 228 deletions
+4
View File
@@ -6,6 +6,8 @@ import '../application/category/category_loader/category_loader_bloc.dart';
import '../application/checkout/checkout_form/checkout_form_bloc.dart';
import '../application/outlet/outlet_loader/outlet_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/constant/app_constant.dart';
import '../injection.dart';
@@ -31,6 +33,8 @@ class _AppWidgetState extends State<AppWidget> {
BlocProvider(create: (context) => getIt<CategoryLoaderBloc>()),
BlocProvider(create: (context) => getIt<ProductLoaderBloc>()),
BlocProvider(create: (context) => getIt<CheckoutFormBloc>()),
BlocProvider(create: (context) => getIt<TableLoaderBloc>()),
BlocProvider(create: (context) => getIt<TableFormBloc>()),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
@@ -0,0 +1,60 @@
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,
),
],
),
);
},
);
}
}
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../../../domain/auth/auth.dart';
import '../../../domain/table/table.dart';
class AppFlushbar {
static void showSuccess(BuildContext context, String message) {
@@ -50,4 +51,18 @@ class AppFlushbar {
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,7 +8,9 @@ import '../../../../../common/extension/extension.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../../domain/table/table.dart' as t;
import '../../../../../injection.dart';
import '../../../../components/dialog/table/table_create_dialog.dart';
import '../../../../components/loader/loader_with_text.dart';
import '../../../../components/toast/flushbar.dart';
import 'widgets/floating_bottom_navbar.dart';
import 'widgets/table_card.dart';
@@ -27,7 +29,6 @@ class TablePage extends StatefulWidget implements AutoRouteWrapper {
getIt<TableLoaderBloc>()
..add(TableLoaderEvent.fetched(isRefresh: true)),
),
BlocProvider(create: (context) => getIt<TableFormBloc>()),
],
child: this,
);
@@ -43,203 +44,227 @@ class _TablePageState extends State<TablePage> {
final double mapWidth = context.deviceWidth * 2;
final double mapHeight = context.deviceHeight * 1.5;
return 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: () {
return BlocListener<TableFormBloc, TableFormState>(
listenWhen: (previous, current) =>
previous.failureOrTable != current.failureOrTable,
listener: (context, state) {
state.failureOrTable.fold(() {}, (either) {
either.fold((f) => AppFlushbar.showTableFailureToast(context, f), (
success,
) {
if (context.mounted) {
context.router.maybePop();
context.read<TableLoaderBloc>().add(
const TableLoaderEvent.fetched(isRefresh: true),
TableLoaderEvent.fetched(isRefresh: true),
);
},
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
// showDialog(
// context: context,
// builder: (context) => FormTableNewDialog(),
// );
},
),
],
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"),
],
}
});
});
},
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) {
if (state.isFetching) {
return const Center(child: LoaderWithText());
}
return SafeArea(
child: Stack(
children: [
Row(
children: [
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,
body: BlocBuilder<TableLoaderBloc, TableLoaderState>(
builder: (context, state) {
if (state.isFetching) {
return const Center(child: LoaderWithText());
}
return SafeArea(
child: Stack(
children: [
Row(
children: [
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: [
...List.generate(
20,
(i) => Positioned(
left: i * 100.0,
top: 0,
bottom: 0,
child: Container(
width: 1,
color: Colors.grey[200],
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(
15,
(i) => Positioned(
top: i * 100.0,
left: 0,
right: 0,
child: Container(
height: 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],
),
),
),
),
...state.tables.map((table) {
final isSelected = state.selectedTable == table;
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,
child: TableCard(
table: table,
isSelected: isSelected,
),
),
onDragStarted: () {
setState(() {
draggingTable = table;
});
},
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),
...state.tables.map((table) {
final isSelected =
state.selectedTable == table;
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,
),
);
context.read<TableFormBloc>().add(
TableFormEvent.updated(
id: table.id,
position: Offset(newX, newY),
),
childWhenDragging: Opacity(
opacity: 0.5,
child: TableCard(
table: table,
isSelected: isSelected,
),
);
},
child: GestureDetector(
onTap: () {
),
onDragStarted: () {
setState(() {
draggingTable = table;
});
},
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.setSelectedTable(
table,
TableLoaderEvent.updatedPostion(
id: table.id,
position: Offset(newX, newY),
),
);
context.read<TableFormBloc>().add(
TableFormEvent.updated(
id: table.id,
position: Offset(newX, newY),
),
);
},
onTapDown: (details) =>
tapPosition = details.globalPosition,
onLongPress: () {
if (table.status.isOccupied) {
_showPopupMenu(context, table);
}
},
child: TableCard(
table: table,
isSelected: isSelected,
child: GestureDetector(
onTap: () {
context.read<TableLoaderBloc>().add(
TableLoaderEvent.setSelectedTable(
table,
),
);
},
onTapDown: (details) => tapPosition =
details.globalPosition,
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),
],
),
);
},
),
),
);
}