import 'dart:developer'; import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.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/transfer_table/transfer_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/dialogs/form_table_new_dialog.dart'; import 'package:enaklo_pos/presentation/table/dialogs/transfer_table_dialog.dart'; import 'package:enaklo_pos/presentation/table/widgets/table_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; // 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 { final List? items; const TablePage({super.key, this.items}); @override State createState() => _TablePageState(); } class _TablePageState extends State { TableModel? selectedTable; // Untuk drag TableModel? draggingTable; Offset? _tapPosition; // Ubah function toggleSelectTable menjadi selectTable void selectTable(TableModel table) { if (table.status == 'occupied') return; setState(() { if (selectedTable == table) { selectedTable = null; // Deselect jika table yang sama diklik } else { selectedTable = table; // Select table baru (akan mengganti selection sebelumnya) } }); } @override void initState() { log("ITEM COUNT: ${widget.items?.length}"); context.read().add(const GetTableEvent.getTables()); super.initState(); } @override Widget build(BuildContext context) { final double mapWidth = context.deviceWidth * 2; final double mapHeight = context.deviceHeight * 1.5; return Scaffold( appBar: AppBar( title: const Text("Layout Meja"), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () { context.read().add(const GetTableEvent.getTables()); }, ), BlocListener( listener: (context, state) { state.maybeWhen( orElse: () {}, success: (message) { context .read() .add(const GetTableEvent.getTables()); }); }, 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: BlocListener( listener: (context, state) { state.maybeWhen( orElse: () {}, success: () { AppFlushbar.showSuccess(context, 'Transfer Table Success'); context.read().add(const GetTableEvent.getTables()); }, error: (message) { AppFlushbar.showError(context, message); }, ); }, child: BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => SizedBox.shrink(), loading: () => const Center(child: CircularProgressIndicator()), success: (tables) => SafeArea( child: Stack( children: [ // Main content area Row( children: [ // Area meja (zoom & pan & drag) Expanded( flex: 5, child: InteractiveViewer( panEnabled: true, scaleEnabled: true, constrained: false, boundaryMargin: const EdgeInsets.all(80), minScale: 0.3, maxScale: 3.0, alignment: Alignment.topLeft, child: Container( width: mapWidth, height: mapHeight, decoration: BoxDecoration( color: const Color(0xFFF7F8FA), border: Border.all( color: Colors.grey[300]!, width: 2), ), child: Stack( children: [ // Optional: Grid background ...List.generate( 20, (i) => Positioned( left: i * 100.0, top: 0, bottom: 0, child: Container( width: 1, color: Colors.grey[200]), )), ...List.generate( 15, (i) => Positioned( top: i * 100.0, left: 0, right: 0, child: Container( height: 1, color: Colors.grey[200]), )), // Tables ...tables.map((table) { final isSelected = selectedTable == table; return Positioned( left: table.positionX, top: table.positionY, child: Draggable( data: table, feedback: Material( color: Colors.transparent, child: TableWidget( table: table, isSelected: isSelected, ), ), childWhenDragging: Opacity( opacity: 0.5, child: TableWidget( table: table, isSelected: isSelected, ), ), onDragStarted: () { setState(() { draggingTable = table; }); }, onDraggableCanceled: (velocity, offset) { setState(() { draggingTable = null; }); }, onDragEnd: (details) { setState(() { draggingTable = null; final RenderBox box = context.findRenderObject() as RenderBox; final Offset local = box .globalToLocal(details.offset); table.positionX = local.dx .clamp(0, mapWidth - 120); table.positionY = local.dy .clamp(0, mapHeight - 80); }); context .read() .add(ChangePositionTableEvent .changePositionTable( tableId: table.id ?? "", position: Offset( table.positionX ?? 0.0, table.positionY ?? 0.0, ), )); }, child: GestureDetector( onTap: () => selectTable(table), onTapDown: (details) => _tapPosition = details.globalPosition, onLongPress: () { if (table.status == 'occupied') { _showPopupMenu(context, table); } }, child: TableWidget( table: table, isSelected: isSelected, ), ), ), ); }).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.pushReplacement(DashboardPage( table: selectedTable!, items: widget.items, )); } else {} }, 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)), ], ); } void _showPopupMenu(BuildContext context, TableModel table) { final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; showMenu( context: context, position: RelativeRect.fromRect( _tapPosition != null ? Rect.fromLTWH(_tapPosition!.dx, _tapPosition!.dy, 0, 0) : Rect.fromLTWH(100, 100, 0, 0), Offset.zero & overlay.size, ), color: AppColors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 1, items: [ PopupMenuItem( onTap: () { showDialog( context: context, builder: (context) => TransferTableDialog(fromTable: table), ); }, child: Row( children: [ Icon(Icons.swap_horiz), SizedBox(width: 8), Text('Transfer'), ], ), ), ], ); } }