dev #1
@ -68,18 +68,18 @@ class TableData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TableModel {
|
class TableModel {
|
||||||
final String? id;
|
String? id;
|
||||||
final String? organizationId;
|
String? organizationId;
|
||||||
final String? outletId;
|
String? outletId;
|
||||||
final String? tableName;
|
String? tableName;
|
||||||
final String? status;
|
String? status;
|
||||||
final int? paymentAmount;
|
int? paymentAmount;
|
||||||
final double? positionX;
|
double? positionX;
|
||||||
final double? positionY;
|
double? positionY;
|
||||||
final int? capacity;
|
int? capacity;
|
||||||
final bool? isActive;
|
bool? isActive;
|
||||||
final DateTime? createdAt;
|
DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
DateTime? updatedAt;
|
||||||
|
|
||||||
TableModel({
|
TableModel({
|
||||||
this.id,
|
this.id,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/table/pages/table_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:enaklo_pos/data/models/response/table_model.dart';
|
import 'package:enaklo_pos/data/models/response/table_model.dart';
|
||||||
@ -57,8 +58,7 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||||||
isTable: false,
|
isTable: false,
|
||||||
table: widget.table,
|
table: widget.table,
|
||||||
),
|
),
|
||||||
// const TablePage(),
|
const TablePage(),
|
||||||
TableManagementScreen(),
|
|
||||||
const ReportPage(),
|
const ReportPage(),
|
||||||
const PrinterConfigurationPage(),
|
const PrinterConfigurationPage(),
|
||||||
// SalesPage(),
|
// SalesPage(),
|
||||||
|
|||||||
@ -1,10 +1,32 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:enaklo_pos/core/components/components.dart';
|
|
||||||
import 'package:enaklo_pos/core/constants/colors.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/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/get_table/get_table_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/table/dialogs/form_table_dialog.dart';
|
import 'package:enaklo_pos/presentation/table/dialogs/form_table_new_dialog.dart';
|
||||||
import 'package:enaklo_pos/presentation/table/widgets/card_table_widget.dart';
|
import 'package:enaklo_pos/presentation/table/widgets/table_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
// Enum status meja
|
||||||
|
enum TableStatus { available, occupied, billed, availableSoon, unknown }
|
||||||
|
|
||||||
|
TableStatus parseStatus(String? status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'available':
|
||||||
|
return TableStatus.available;
|
||||||
|
case 'occupied':
|
||||||
|
return TableStatus.occupied;
|
||||||
|
case 'billed':
|
||||||
|
return TableStatus.billed;
|
||||||
|
case 'available_soon':
|
||||||
|
return TableStatus.availableSoon;
|
||||||
|
default:
|
||||||
|
return TableStatus.unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TablePage extends StatefulWidget {
|
class TablePage extends StatefulWidget {
|
||||||
const TablePage({super.key});
|
const TablePage({super.key});
|
||||||
@ -14,6 +36,23 @@ class TablePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TablePageState extends State<TablePage> {
|
class _TablePageState extends State<TablePage> {
|
||||||
|
TableModel? selectedTable;
|
||||||
|
|
||||||
|
// Untuk drag
|
||||||
|
TableModel? draggingTable;
|
||||||
|
|
||||||
|
// Ubah function toggleSelectTable menjadi selectTable
|
||||||
|
void selectTable(TableModel table) {
|
||||||
|
setState(() {
|
||||||
|
if (selectedTable == table) {
|
||||||
|
selectedTable = null; // Deselect jika table yang sama diklik
|
||||||
|
} else {
|
||||||
|
selectedTable =
|
||||||
|
table; // Select table baru (akan mengganti selection sebelumnya)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
context.read<GetTableBloc>().add(const GetTableEvent.getTables());
|
context.read<GetTableBloc>().add(const GetTableEvent.getTables());
|
||||||
@ -22,74 +61,340 @@ class _TablePageState extends State<TablePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
final double mapWidth = context.deviceWidth * 2;
|
||||||
padding: const EdgeInsets.all(24),
|
final double mapHeight = context.deviceHeight * 1.5;
|
||||||
child: ListView(
|
|
||||||
children: [
|
return Scaffold(
|
||||||
Row(
|
appBar: AppBar(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
title: const Text("Layout Meja"),
|
||||||
children: [
|
actions: [
|
||||||
Text(
|
BlocListener<CreateTableBloc, CreateTableState>(
|
||||||
"Table Management",
|
listener: (context, state) {
|
||||||
style: TextStyle(
|
state.maybeWhen(
|
||||||
fontSize: 24.0,
|
orElse: () {},
|
||||||
fontWeight: FontWeight.bold,
|
success: (message) {
|
||||||
color: AppColors.primary,
|
context
|
||||||
),
|
.read<GetTableBloc>()
|
||||||
),
|
.add(const GetTableEvent.getTables());
|
||||||
Button.filled(
|
});
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => FormTableDialog(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
label: 'Generate Table',
|
|
||||||
height: 48.0,
|
|
||||||
width: 200.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SpaceHeight(24.0),
|
|
||||||
BlocBuilder<GetTableBloc, GetTableState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return state.maybeWhen(
|
|
||||||
orElse: () {
|
|
||||||
return SizedBox.shrink();
|
|
||||||
},
|
|
||||||
loading: () {
|
|
||||||
return const CircularProgressIndicator();
|
|
||||||
},
|
|
||||||
success: (tables) {
|
|
||||||
if (tables.isEmpty) {
|
|
||||||
return const Center(
|
|
||||||
child: Text('No table available'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return GridView.builder(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
gridDelegate:
|
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
childAspectRatio: 1.0,
|
|
||||||
crossAxisCount: 4,
|
|
||||||
mainAxisSpacing: 16,
|
|
||||||
crossAxisSpacing: 16,
|
|
||||||
),
|
|
||||||
itemCount: tables.length,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const ScrollPhysics(),
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
return CardTableWidget(
|
|
||||||
table: tables[index],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FormTableNewDialog(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
|
elevation: 0.5,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(60),
|
||||||
|
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"),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
_buildLegendDot(Colors.green[200]!, "Billed"),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
_buildLegendDot(Colors.yellow[200]!, "Available soon"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: const Color(0xFFF7F8FA),
|
||||||
|
body: BlocBuilder<GetTableBloc, GetTableState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.maybeWhen(
|
||||||
|
orElse: () => SizedBox.shrink(),
|
||||||
|
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<TableModel>(
|
||||||
|
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<ChangePositionTableBloc>()
|
||||||
|
.add(ChangePositionTableEvent
|
||||||
|
.changePositionTable(
|
||||||
|
tableId: table.id ?? "",
|
||||||
|
position: details.offset,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => selectTable(table),
|
||||||
|
child: TableWidget(
|
||||||
|
table: table,
|
||||||
|
isSelected: isSelected,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Sidebar bar tables
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Floating bottom bar - hanya muncul jika ada table yang dipilih
|
||||||
|
buildAlternativeFloatingBar(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildAlternativeFloatingBar() {
|
||||||
|
return Positioned(
|
||||||
|
bottom: 20, // Jarak dari bawah
|
||||||
|
left: 16, // Jarak dari kiri
|
||||||
|
right: 16,
|
||||||
|
child: AnimatedSlide(
|
||||||
|
duration: const Duration(milliseconds: 400),
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
offset: selectedTable == null ? const Offset(0, 2) : Offset.zero,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
opacity: selectedTable == null ? 0.0 : 1.0,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: selectedTable == null,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
AppColors.primary,
|
||||||
|
AppColors.primary.withOpacity(0.6)
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColors.primary.withOpacity(0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.table_bar,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
"1 Meja Dipilih",
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
selectedTable?.tableName ?? "",
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedTable = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: AppColors.primary,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (selectedTable?.status == 'available') {
|
||||||
|
context.push(DashboardPage(
|
||||||
|
table: selectedTable!,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// Handle occupied table click - load draft order and navigate to payment
|
||||||
|
// context.read<CheckoutBloc>().add(
|
||||||
|
// CheckoutEvent.loadDraftOrder(data!),
|
||||||
|
// );
|
||||||
|
// log("Data Draft Order: ${data!.toMap()}");
|
||||||
|
// context.push(PaymentTablePage(
|
||||||
|
// table: widget.table,
|
||||||
|
// draftOrder: data!,
|
||||||
|
// ));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"Tempatkan Pesanan",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLegendDot(Color color, String label) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(radius: 7, backgroundColor: color),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(label, style: const TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
lib/presentation/table/pages/table_page.dart.backup
Normal file
95
lib/presentation/table/pages/table_page.dart.backup
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:enaklo_pos/core/components/components.dart';
|
||||||
|
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/table/dialogs/form_table_dialog.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/table/widgets/card_table_widget.dart';
|
||||||
|
|
||||||
|
class TablePage extends StatefulWidget {
|
||||||
|
const TablePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TablePage> createState() => _TablePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TablePageState extends State<TablePage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
context.read<GetTableBloc>().add(const GetTableEvent.getTables());
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Table Management",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24.0,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Button.filled(
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FormTableDialog(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
label: 'Generate Table',
|
||||||
|
height: 48.0,
|
||||||
|
width: 200.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SpaceHeight(24.0),
|
||||||
|
BlocBuilder<GetTableBloc, GetTableState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.maybeWhen(
|
||||||
|
orElse: () {
|
||||||
|
return SizedBox.shrink();
|
||||||
|
},
|
||||||
|
loading: () {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
},
|
||||||
|
success: (tables) {
|
||||||
|
if (tables.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text('No table available'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GridView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
childAspectRatio: 1.0,
|
||||||
|
crossAxisCount: 4,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
),
|
||||||
|
itemCount: tables.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const ScrollPhysics(),
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return CardTableWidget(
|
||||||
|
table: tables[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,395 +1,162 @@
|
|||||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:enaklo_pos/core/components/buttons.dart';
|
|
||||||
import 'package:enaklo_pos/core/components/custom_text_field.dart';
|
|
||||||
import 'package:enaklo_pos/core/components/spaces.dart';
|
|
||||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||||
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
|
||||||
import 'package:enaklo_pos/core/utils/date_formatter.dart';
|
|
||||||
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
|
|
||||||
|
|
||||||
import 'package:enaklo_pos/data/models/response/table_model.dart';
|
import 'package:enaklo_pos/data/models/response/table_model.dart';
|
||||||
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
|
import 'package:enaklo_pos/presentation/table/pages/table_page.dart';
|
||||||
import 'package:enaklo_pos/presentation/home/bloc/status_table/status_table_bloc.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.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/update_table/update_table_bloc.dart';
|
|
||||||
import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart';
|
|
||||||
|
|
||||||
import '../pages/payment_table_page.dart';
|
class TableWidget extends StatelessWidget {
|
||||||
|
|
||||||
class TableWidget extends StatefulWidget {
|
|
||||||
final TableModel table;
|
final TableModel table;
|
||||||
const TableWidget({
|
final bool isSelected;
|
||||||
super.key,
|
|
||||||
required this.table,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
const TableWidget({super.key, required this.table, this.isSelected = false});
|
||||||
State<TableWidget> createState() => _TableWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TableWidgetState extends State<TableWidget> {
|
// Fungsi untuk menentukan jumlah kursi di tiap sisi
|
||||||
TextEditingController? tableNameController;
|
Map<String, int> getChairDistribution(int capacity) {
|
||||||
DraftOrderModel? data;
|
if (capacity == 2) {
|
||||||
@override
|
return {'top': 0, 'bottom': 0, 'left': 1, 'right': 1};
|
||||||
void initState() {
|
} else if (capacity == 4) {
|
||||||
super.initState();
|
return {'top': 1, 'bottom': 1, 'left': 1, 'right': 1};
|
||||||
loadData();
|
} else if (capacity == 6) {
|
||||||
tableNameController = TextEditingController(text: widget.table.tableName);
|
return {'top': 2, 'bottom': 2, 'left': 1, 'right': 1};
|
||||||
|
} else if (capacity == 8) {
|
||||||
|
return {'top': 3, 'bottom': 3, 'left': 1, 'right': 1};
|
||||||
|
} else if (capacity == 10) {
|
||||||
|
return {'top': 4, 'bottom': 4, 'left': 1, 'right': 1};
|
||||||
|
} else {
|
||||||
|
int side = (capacity / 4).floor();
|
||||||
|
return {'top': side, 'bottom': side, 'left': 1, 'right': 1};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Color getStatusColor() {
|
||||||
void dispose() {
|
switch (parseStatus(table.status)) {
|
||||||
tableNameController!.dispose();
|
case TableStatus.available:
|
||||||
super.dispose();
|
return Colors.blue[100]!;
|
||||||
|
case TableStatus.occupied:
|
||||||
|
return Colors.orange[100]!;
|
||||||
|
case TableStatus.billed:
|
||||||
|
return Colors.green[100]!;
|
||||||
|
case TableStatus.availableSoon:
|
||||||
|
return Colors.yellow[100]!;
|
||||||
|
default:
|
||||||
|
return Colors.grey[200]!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData() async {
|
Color getBorderColor() {
|
||||||
if (widget.table.status != 'available') {
|
if (isSelected) return AppColors.primary;
|
||||||
// data = await ProductLocalDatasource.instance
|
switch (parseStatus(table.status)) {
|
||||||
// .getDraftOrderById(widget.table.orderId);
|
case TableStatus.available:
|
||||||
|
return Colors.blue;
|
||||||
|
case TableStatus.occupied:
|
||||||
|
return Colors.orange;
|
||||||
|
case TableStatus.billed:
|
||||||
|
return Colors.green;
|
||||||
|
case TableStatus.availableSoon:
|
||||||
|
return Colors.yellow[700]!;
|
||||||
|
default:
|
||||||
|
return Colors.grey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
final int capacity = table.capacity ?? 0;
|
||||||
onTap: () async {
|
final chairDist = getChairDistribution(capacity);
|
||||||
if (widget.table.status == 'available') {
|
|
||||||
context.push(DashboardPage(
|
|
||||||
table: widget.table,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
// Handle occupied table click - load draft order and navigate to payment
|
|
||||||
context.read<CheckoutBloc>().add(
|
|
||||||
CheckoutEvent.loadDraftOrder(data!),
|
|
||||||
);
|
|
||||||
log("Data Draft Order: ${data!.toMap()}");
|
|
||||||
context.push(PaymentTablePage(
|
|
||||||
table: widget.table,
|
|
||||||
draftOrder: data!,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
// dialog info table
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16)),
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.table_bar, color: AppColors.primary),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('Table ${widget.table.tableName}'),
|
|
||||||
Spacer(),
|
|
||||||
BlocListener<UpdateTableBloc, UpdateTableState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
state.maybeWhen(
|
|
||||||
orElse: () {},
|
|
||||||
success: (message) {
|
|
||||||
context
|
|
||||||
.read<GetTableBloc>()
|
|
||||||
.add(const GetTableEvent.getTables());
|
|
||||||
context.pop();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
// show dialaog adn input table name
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text('Update Table'),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxHeight: 180,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
CustomTextField(
|
|
||||||
controller: tableNameController!,
|
|
||||||
label: 'Table Name',
|
|
||||||
),
|
|
||||||
SpaceHeight(16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Button.outlined(
|
|
||||||
onPressed: () {
|
|
||||||
context.pop();
|
|
||||||
},
|
|
||||||
label: 'close',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SpaceWidth(16),
|
|
||||||
Expanded(
|
|
||||||
child: Button.filled(
|
|
||||||
onPressed: () {
|
|
||||||
// final newData =
|
|
||||||
// TableModel(
|
|
||||||
// id: widget.table.id,
|
|
||||||
// tableName:
|
|
||||||
// tableNameController!
|
|
||||||
// .text,
|
|
||||||
// status:
|
|
||||||
// widget.table.status,
|
|
||||||
// startTime: widget
|
|
||||||
// .table.startTime,
|
|
||||||
// orderId: widget
|
|
||||||
// .table.orderId,
|
|
||||||
// paymentAmount: widget
|
|
||||||
// .table
|
|
||||||
// .paymentAmount,
|
|
||||||
// position: widget
|
|
||||||
// .table.position,
|
|
||||||
// );
|
|
||||||
// context
|
|
||||||
// .read<
|
|
||||||
// UpdateTableBloc>()
|
|
||||||
// .add(
|
|
||||||
// UpdateTableEvent
|
|
||||||
// .updateTable(
|
|
||||||
// newData,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
context
|
|
||||||
.pop(); // close dialog after adding
|
|
||||||
},
|
|
||||||
label: 'Update',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: []);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(Icons.edit)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildInfoRow(
|
|
||||||
'Status:',
|
|
||||||
widget.table.status == 'available'
|
|
||||||
? 'Available'
|
|
||||||
: 'Occupied',
|
|
||||||
color: widget.table.status == 'available'
|
|
||||||
? Colors.green
|
|
||||||
: Colors.red),
|
|
||||||
// widget.table.status == 'available'
|
|
||||||
// ? SizedBox.shrink()
|
|
||||||
// : _buildInfoRow(
|
|
||||||
// 'Start Time:',
|
|
||||||
// DateFormatter.formatDateTime2(
|
|
||||||
// widget.table.startTime)),
|
|
||||||
// widget.table.status == 'available'
|
|
||||||
// ? SizedBox.shrink()
|
|
||||||
// : _buildInfoRow(
|
|
||||||
// 'Order ID:', widget.table.orderId.toString()),
|
|
||||||
widget.table.status == 'available'
|
|
||||||
? SizedBox.shrink()
|
|
||||||
: SpaceHeight(16),
|
|
||||||
widget.table.status == 'available'
|
|
||||||
? SizedBox.shrink()
|
|
||||||
: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Button.outlined(
|
|
||||||
onPressed: () {
|
|
||||||
// Show void confirmation dialog
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.warning,
|
|
||||||
color: AppColors.red),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('Void Order?'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: Text(
|
|
||||||
'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table.tableName}?\n\nPesanan akan dihapus secara permanen.'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () =>
|
|
||||||
Navigator.pop(context),
|
|
||||||
child: Text('Tidak',
|
|
||||||
style: TextStyle(
|
|
||||||
color: AppColors.primary)),
|
|
||||||
),
|
|
||||||
BlocListener<StatusTableBloc,
|
|
||||||
StatusTableState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
state.maybeWhen(
|
|
||||||
orElse: () {},
|
|
||||||
success: () {
|
|
||||||
context
|
|
||||||
.read<GetTableBloc>()
|
|
||||||
.add(const GetTableEvent
|
|
||||||
.getTables());
|
|
||||||
Navigator.pop(
|
|
||||||
context); // Close void dialog
|
|
||||||
Navigator.pop(
|
|
||||||
context); // Close table info dialog
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Pesanan berhasil dibatalkan'),
|
|
||||||
backgroundColor:
|
|
||||||
AppColors.primary,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: AppColors.red,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
// // Void the order
|
|
||||||
// final newTable = TableModel(
|
|
||||||
// id: widget.table.id,
|
|
||||||
// tableName:
|
|
||||||
// widget.table.tableName,
|
|
||||||
// status: 'available',
|
|
||||||
// orderId: 0,
|
|
||||||
// paymentAmount: 0,
|
|
||||||
// startTime: DateTime.now()
|
|
||||||
// .toIso8601String(),
|
|
||||||
// position: widget.table.position,
|
|
||||||
// );
|
|
||||||
// context
|
|
||||||
// .read<StatusTableBloc>()
|
|
||||||
// .add(
|
|
||||||
// StatusTableEvent
|
|
||||||
// .statusTabel(newTable),
|
|
||||||
// );
|
|
||||||
// // Remove draft order from local storage
|
|
||||||
// ProductLocalDatasource.instance
|
|
||||||
// .removeDraftOrderById(
|
|
||||||
// widget.table.orderId);
|
|
||||||
// log("Voided order for table: ${widget.table.tableName}");
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
"Ya, Batalkan",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
label: 'Void Order',
|
|
||||||
color: AppColors.red,
|
|
||||||
textColor: AppColors.red,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: BlocConsumer<StatusTableBloc,
|
|
||||||
StatusTableState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
state.maybeWhen(
|
|
||||||
orElse: () {},
|
|
||||||
success: () {
|
|
||||||
context.read<GetTableBloc>().add(
|
|
||||||
const GetTableEvent.getTables());
|
|
||||||
context.pop();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
builder: (context, state) {
|
|
||||||
return Button.filled(
|
|
||||||
onPressed: () {
|
|
||||||
context.pop();
|
|
||||||
context.read<CheckoutBloc>().add(
|
|
||||||
CheckoutEvent.loadDraftOrder(
|
|
||||||
data!),
|
|
||||||
);
|
|
||||||
context.push(PaymentTablePage(
|
|
||||||
table: widget.table,
|
|
||||||
draftOrder: data!,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
label: 'Selesai');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child:
|
|
||||||
Text('Close', style: TextStyle(color: AppColors.primary)),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: widget.table.status == 'available'
|
|
||||||
? AppColors.primary
|
|
||||||
: AppColors.red,
|
|
||||||
shape: BoxShape.rectangle,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Text('${widget.table.tableName}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInfoRow(String label, String value, {Color? color}) {
|
Widget chair() => Container(
|
||||||
return Padding(
|
width: 20,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
height: 10,
|
||||||
child: Row(
|
decoration: BoxDecoration(
|
||||||
children: [
|
color: Colors.grey[300],
|
||||||
Text(
|
borderRadius: BorderRadius.circular(4),
|
||||||
label,
|
|
||||||
style: TextStyle(fontWeight: FontWeight.w600),
|
|
||||||
),
|
),
|
||||||
SizedBox(width: 8),
|
);
|
||||||
Expanded(
|
|
||||||
child: Text(
|
return Container(
|
||||||
value,
|
width: 120,
|
||||||
style: TextStyle(
|
height: 80,
|
||||||
color: color ?? Colors.black87,
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// Meja utama
|
||||||
|
Container(
|
||||||
|
width: 100,
|
||||||
|
height: 60,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: getBorderColor(),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 24,
|
||||||
|
backgroundColor: getStatusColor(),
|
||||||
|
child: Text(
|
||||||
|
table.tableName ?? "",
|
||||||
|
style: TextStyle(
|
||||||
|
color: getBorderColor(),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Kursi atas
|
||||||
|
if (chairDist['top']! > 0)
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: List.generate(chairDist['top']!, (_) => chair()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Kursi bawah
|
||||||
|
if (chairDist['bottom']! > 0)
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: List.generate(chairDist['bottom']!, (_) => chair()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Kursi kiri
|
||||||
|
if (chairDist['left']! > 0)
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
top: 15,
|
||||||
|
bottom: 15,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: List.generate(chairDist['left']!, (_) => chair()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Kursi kanan
|
||||||
|
if (chairDist['right']! > 0)
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 15,
|
||||||
|
bottom: 15,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: List.generate(chairDist['right']!, (_) => chair()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Icon info kecil di pojok kanan atas jika status reserved
|
||||||
|
if (parseStatus(table.status) == TableStatus.occupied)
|
||||||
|
const Positioned(
|
||||||
|
top: 6,
|
||||||
|
right: 6,
|
||||||
|
child:
|
||||||
|
Icon(Icons.info_outline, size: 16, color: Colors.redAccent),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
399
lib/presentation/table/widgets/table_widget.dart.backup
Normal file
399
lib/presentation/table/widgets/table_widget.dart.backup
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/core/components/buttons.dart';
|
||||||
|
import 'package:enaklo_pos/core/components/custom_text_field.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/core/utils/date_formatter.dart';
|
||||||
|
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
|
||||||
|
|
||||||
|
import 'package:enaklo_pos/data/models/response/table_model.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/home/bloc/status_table/status_table_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.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/update_table/update_table_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart';
|
||||||
|
|
||||||
|
import '../pages/payment_table_page.dart';
|
||||||
|
|
||||||
|
class TableWidget extends StatefulWidget {
|
||||||
|
final TableModel table;
|
||||||
|
const TableWidget({
|
||||||
|
super.key,
|
||||||
|
required this.table,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TableWidget> createState() => _TableWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TableWidgetState extends State<TableWidget> {
|
||||||
|
TextEditingController? tableNameController;
|
||||||
|
DraftOrderModel? data;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadData();
|
||||||
|
tableNameController = TextEditingController(text: widget.table.tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
tableNameController!.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData() async {
|
||||||
|
if (widget.table.status != 'available') {
|
||||||
|
// data = await ProductLocalDatasource.instance
|
||||||
|
// .getDraftOrderById(widget.table.orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
if (widget.table.status == 'available') {
|
||||||
|
context.push(DashboardPage(
|
||||||
|
table: widget.table,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// Handle occupied table click - load draft order and navigate to payment
|
||||||
|
context.read<CheckoutBloc>().add(
|
||||||
|
CheckoutEvent.loadDraftOrder(data!),
|
||||||
|
);
|
||||||
|
log("Data Draft Order: ${data!.toMap()}");
|
||||||
|
context.push(PaymentTablePage(
|
||||||
|
table: widget.table,
|
||||||
|
draftOrder: data!,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
// dialog info table
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16)),
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.table_bar, color: AppColors.primary),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Table ${widget.table.tableName}'),
|
||||||
|
Spacer(),
|
||||||
|
BlocListener<UpdateTableBloc, UpdateTableState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
state.maybeWhen(
|
||||||
|
orElse: () {},
|
||||||
|
success: (message) {
|
||||||
|
context
|
||||||
|
.read<GetTableBloc>()
|
||||||
|
.add(const GetTableEvent.getTables());
|
||||||
|
context.pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
// show dialaog adn input table name
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('Update Table'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: 180,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CustomTextField(
|
||||||
|
controller: tableNameController!,
|
||||||
|
label: 'Table Name',
|
||||||
|
),
|
||||||
|
SpaceHeight(16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Button.outlined(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
|
label: 'close',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SpaceWidth(16),
|
||||||
|
Expanded(
|
||||||
|
child: Button.filled(
|
||||||
|
onPressed: () {
|
||||||
|
// final newData =
|
||||||
|
// TableModel(
|
||||||
|
// id: widget.table.id,
|
||||||
|
// tableName:
|
||||||
|
// tableNameController!
|
||||||
|
// .text,
|
||||||
|
// status:
|
||||||
|
// widget.table.status,
|
||||||
|
// startTime: widget
|
||||||
|
// .table.startTime,
|
||||||
|
// orderId: widget
|
||||||
|
// .table.orderId,
|
||||||
|
// paymentAmount: widget
|
||||||
|
// .table
|
||||||
|
// .paymentAmount,
|
||||||
|
// position: widget
|
||||||
|
// .table.position,
|
||||||
|
// );
|
||||||
|
// context
|
||||||
|
// .read<
|
||||||
|
// UpdateTableBloc>()
|
||||||
|
// .add(
|
||||||
|
// UpdateTableEvent
|
||||||
|
// .updateTable(
|
||||||
|
// newData,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
context
|
||||||
|
.pop(); // close dialog after adding
|
||||||
|
},
|
||||||
|
label: 'Update',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.edit)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildInfoRow(
|
||||||
|
'Status:',
|
||||||
|
widget.table.status == 'available'
|
||||||
|
? 'Available'
|
||||||
|
: 'Occupied',
|
||||||
|
color: widget.table.status == 'available'
|
||||||
|
? Colors.green
|
||||||
|
: Colors.red),
|
||||||
|
// widget.table.status == 'available'
|
||||||
|
// ? SizedBox.shrink()
|
||||||
|
// : _buildInfoRow(
|
||||||
|
// 'Start Time:',
|
||||||
|
// DateFormatter.formatDateTime2(
|
||||||
|
// widget.table.startTime)),
|
||||||
|
// widget.table.status == 'available'
|
||||||
|
// ? SizedBox.shrink()
|
||||||
|
// : _buildInfoRow(
|
||||||
|
// 'Order ID:', widget.table.orderId.toString()),
|
||||||
|
widget.table.status == 'available'
|
||||||
|
? SizedBox.shrink()
|
||||||
|
: SpaceHeight(16),
|
||||||
|
widget.table.status == 'available'
|
||||||
|
? SizedBox.shrink()
|
||||||
|
: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Button.outlined(
|
||||||
|
onPressed: () {
|
||||||
|
// Show void confirmation dialog
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.warning,
|
||||||
|
color: AppColors.red),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Void Order?'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Text(
|
||||||
|
'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table.tableName}?\n\nPesanan akan dihapus secara permanen.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.pop(context),
|
||||||
|
child: Text('Tidak',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary)),
|
||||||
|
),
|
||||||
|
BlocListener<StatusTableBloc,
|
||||||
|
StatusTableState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
state.maybeWhen(
|
||||||
|
orElse: () {},
|
||||||
|
success: () {
|
||||||
|
context
|
||||||
|
.read<GetTableBloc>()
|
||||||
|
.add(const GetTableEvent
|
||||||
|
.getTables());
|
||||||
|
Navigator.pop(
|
||||||
|
context); // Close void dialog
|
||||||
|
Navigator.pop(
|
||||||
|
context); // Close table info dialog
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Pesanan berhasil dibatalkan'),
|
||||||
|
backgroundColor:
|
||||||
|
AppColors.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.red,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// // Void the order
|
||||||
|
// final newTable = TableModel(
|
||||||
|
// id: widget.table.id,
|
||||||
|
// tableName:
|
||||||
|
// widget.table.tableName,
|
||||||
|
// status: 'available',
|
||||||
|
// orderId: 0,
|
||||||
|
// paymentAmount: 0,
|
||||||
|
// startTime: DateTime.now()
|
||||||
|
// .toIso8601String(),
|
||||||
|
// position: widget.table.position,
|
||||||
|
// );
|
||||||
|
// context
|
||||||
|
// .read<StatusTableBloc>()
|
||||||
|
// .add(
|
||||||
|
// StatusTableEvent
|
||||||
|
// .statusTabel(newTable),
|
||||||
|
// );
|
||||||
|
// // Remove draft order from local storage
|
||||||
|
// ProductLocalDatasource.instance
|
||||||
|
// .removeDraftOrderById(
|
||||||
|
// widget.table.orderId);
|
||||||
|
// log("Voided order for table: ${widget.table.tableName}");
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"Ya, Batalkan",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
label: 'Void Order',
|
||||||
|
color: AppColors.red,
|
||||||
|
textColor: AppColors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: BlocConsumer<StatusTableBloc,
|
||||||
|
StatusTableState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
state.maybeWhen(
|
||||||
|
orElse: () {},
|
||||||
|
success: () {
|
||||||
|
context.read<GetTableBloc>().add(
|
||||||
|
const GetTableEvent.getTables());
|
||||||
|
context.pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
return Button.filled(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
context.read<CheckoutBloc>().add(
|
||||||
|
CheckoutEvent.loadDraftOrder(
|
||||||
|
data!),
|
||||||
|
);
|
||||||
|
context.push(PaymentTablePage(
|
||||||
|
table: widget.table,
|
||||||
|
draftOrder: data!,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
label: 'Selesai');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child:
|
||||||
|
Text('Close', style: TextStyle(color: AppColors.primary)),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: widget.table.status == 'available'
|
||||||
|
? AppColors.primary
|
||||||
|
: AppColors.red,
|
||||||
|
shape: BoxShape.rectangle,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Text('${widget.table.tableName}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoRow(String label, String value, {Color? color}) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: color ?? Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user