dev #1
41
lib/core/components/dashed_divider.dart
Normal file
41
lib/core/components/dashed_divider.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DashedDivider extends StatelessWidget {
|
||||
final double height;
|
||||
final double dashWidth;
|
||||
final double dashSpacing;
|
||||
final Color color;
|
||||
|
||||
const DashedDivider({
|
||||
super.key,
|
||||
this.height = 1,
|
||||
this.dashWidth = 5,
|
||||
this.dashSpacing = 3,
|
||||
this.color = Colors.grey,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final boxWidth = constraints.constrainWidth();
|
||||
final dashCount = (boxWidth / (dashWidth + dashSpacing)).floor();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(dashCount, (_) {
|
||||
return SizedBox(
|
||||
width: dashWidth,
|
||||
height: height,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: color),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,6 @@ import 'package:enaklo_pos/data/models/response/table_model.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:enaklo_pos/presentation/table/models/draft_order_item.dart';
|
||||
import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import '../../presentation/home/models/product_quantity.dart';
|
||||
@ -145,7 +144,7 @@ class ProductLocalDatasource {
|
||||
Future<Database> _initDB(String filePath) async {
|
||||
final dbPath = await getDatabasesPath();
|
||||
final path = dbPath + filePath;
|
||||
|
||||
|
||||
// Force delete existing database to ensure new schema
|
||||
try {
|
||||
final dbExists = await databaseExists(path);
|
||||
@ -156,20 +155,21 @@ class ProductLocalDatasource {
|
||||
} catch (e) {
|
||||
log("Error deleting database: $e");
|
||||
}
|
||||
|
||||
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: 2,
|
||||
path,
|
||||
version: 2,
|
||||
onCreate: _createDb,
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
if (oldVersion < 2) {
|
||||
// Add order_type column to orders table if it doesn't exist
|
||||
try {
|
||||
await db.execute('ALTER TABLE $tableOrder ADD COLUMN order_type TEXT DEFAULT "DINE IN"');
|
||||
await db.execute(
|
||||
'ALTER TABLE $tableOrder ADD COLUMN order_type TEXT DEFAULT "DINE IN"');
|
||||
log("Added order_type column to orders table");
|
||||
} catch (e) {
|
||||
log("order_type column might already exist: $e");
|
||||
@ -186,11 +186,11 @@ class ProductLocalDatasource {
|
||||
//save order
|
||||
Future<int> saveOrder(OrderModel order) async {
|
||||
final db = await instance.database;
|
||||
|
||||
|
||||
// Since we're forcing database recreation, order_type column should exist
|
||||
final orderMap = order.toMap(includeOrderType: true);
|
||||
log("Final orderMap for insertion: $orderMap");
|
||||
|
||||
|
||||
int id = await db.insert(tableOrder, orderMap,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
|
||||
@ -238,6 +238,31 @@ class ProductLocalDatasource {
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<OrderModel>> getAllOrderByRange(
|
||||
DateTime start, DateTime end) async {
|
||||
final db = await instance.database;
|
||||
|
||||
// Format ke ISO 8601 untuk range, hasil: yyyy-MM-ddTHH:mm:ss
|
||||
final startIso = start.toIso8601String();
|
||||
final endIso = end.toIso8601String();
|
||||
|
||||
final startDateYYYYMMDD = startIso.substring(0, 10);
|
||||
final endDateYYYYMMDD = endIso.substring(0, 10);
|
||||
|
||||
final List<Map<String, dynamic>> maps = await db.query(
|
||||
tableOrder,
|
||||
where: 'substr(transaction_time, 1, 10) BETWEEN ? AND ?',
|
||||
whereArgs: [startDateYYYYMMDD, endDateYYYYMMDD],
|
||||
orderBy: 'transaction_time DESC',
|
||||
);
|
||||
log("Get All Order By Range: $startDateYYYYMMDD $endDateYYYYMMDD");
|
||||
|
||||
return List.generate(maps.length, (i) {
|
||||
log("Save save OrderModel: ${OrderModel.fromMap(maps[i])}");
|
||||
return OrderModel.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
//get order item by order id
|
||||
Future<List<ProductQuantity>> getOrderItemByOrderId(int orderId) async {
|
||||
final db = await instance.database;
|
||||
@ -402,12 +427,12 @@ class ProductLocalDatasource {
|
||||
Future<List<TableModel>> getTableByStatus(String status) async {
|
||||
final db = await instance.database;
|
||||
List<Map<String, dynamic>> maps;
|
||||
|
||||
|
||||
if (status == 'all') {
|
||||
// Get all tables
|
||||
maps = await db.query(tableManagement);
|
||||
log("Getting all tables, found: ${maps.length}");
|
||||
|
||||
|
||||
// If no tables exist, create some default tables
|
||||
if (maps.isEmpty) {
|
||||
log("No tables found, creating default tables...");
|
||||
@ -428,19 +453,19 @@ class ProductLocalDatasource {
|
||||
final tables = List.generate(maps.length, (i) {
|
||||
return TableModel.fromMap(maps[i]);
|
||||
});
|
||||
|
||||
|
||||
log("Returning ${tables.length} tables");
|
||||
tables.forEach((table) {
|
||||
log("Table: ${table.tableName} (ID: ${table.id}, Status: ${table.status})");
|
||||
});
|
||||
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
// Create default tables if none exist
|
||||
Future<void> _createDefaultTables() async {
|
||||
final db = await instance.database;
|
||||
|
||||
|
||||
// Create 5 default tables
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
await db.insert(tableManagement, {
|
||||
@ -463,7 +488,7 @@ class ProductLocalDatasource {
|
||||
await db.update(tableManagement, table.toMap(),
|
||||
where: 'id = ?', whereArgs: [table.id]);
|
||||
log("Success Update Status Table: ${table.toMap()}");
|
||||
|
||||
|
||||
// Verify the update
|
||||
final updatedTable = await db.query(
|
||||
tableManagement,
|
||||
@ -474,7 +499,7 @@ class ProductLocalDatasource {
|
||||
log("Verified table update: ${updatedTable.first}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Debug method to reset all tables to available status
|
||||
Future<void> resetAllTablesToAvailable() async {
|
||||
log("Resetting all tables to available status...");
|
||||
@ -564,7 +589,7 @@ class ProductLocalDatasource {
|
||||
//update draft order
|
||||
Future<void> updateDraftOrder(DraftOrderModel draftOrder) async {
|
||||
final db = await instance.database;
|
||||
|
||||
|
||||
// Update the draft order
|
||||
await db.update(
|
||||
'draft_orders',
|
||||
@ -572,13 +597,14 @@ class ProductLocalDatasource {
|
||||
where: 'id = ?',
|
||||
whereArgs: [draftOrder.id],
|
||||
);
|
||||
|
||||
|
||||
// Remove existing items and add new ones
|
||||
await db.delete('draft_order_items',
|
||||
where: 'id_draft_order = ?', whereArgs: [draftOrder.id]);
|
||||
|
||||
|
||||
for (var orderItem in draftOrder.orders) {
|
||||
await db.insert('draft_order_items', orderItem.toMapForLocal(draftOrder.id!));
|
||||
await db.insert(
|
||||
'draft_order_items', orderItem.toMapForLocal(draftOrder.id!));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -32,10 +32,10 @@ class ConfirmPaymentPage extends StatefulWidget {
|
||||
final TableModel? table;
|
||||
|
||||
const ConfirmPaymentPage({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.isTable,
|
||||
this.table,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<ConfirmPaymentPage> createState() => _ConfirmPaymentPageState();
|
||||
@ -110,6 +110,7 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
|
||||
child: Hero(
|
||||
tag: 'payment_confirmation_screen',
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.white,
|
||||
body: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@ -401,7 +402,7 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
|
||||
final subTotal =
|
||||
price - (discount / 100 * price);
|
||||
final finalTax = subTotal * (tax / 100);
|
||||
final finalDiscount = discount / 100 * subTotal;
|
||||
// final finalDiscount = discount / 100 * subTotal;
|
||||
// discountAmountValue = finalDiscount.toInt();
|
||||
// taxFinal = finalTax.toInt();
|
||||
return Text(
|
||||
@ -653,14 +654,19 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
|
||||
"🔘 Fetching available tables for Bayar Nanti");
|
||||
isPayNow = false;
|
||||
isAddToOrder = false;
|
||||
|
||||
|
||||
// Debug: Check all tables first
|
||||
final allTables = await ProductLocalDatasource.instance.getAllTable();
|
||||
print("🔘 All tables in database: ${allTables.length}");
|
||||
final allTables =
|
||||
await ProductLocalDatasource
|
||||
.instance
|
||||
.getAllTable();
|
||||
print(
|
||||
"🔘 All tables in database: ${allTables.length}");
|
||||
allTables.forEach((table) {
|
||||
print("🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}");
|
||||
print(
|
||||
"🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}");
|
||||
});
|
||||
|
||||
|
||||
// Fetch available tables for Bayar Nanti
|
||||
context
|
||||
.read<GetTableStatusBloc>()
|
||||
@ -683,14 +689,19 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
|
||||
"🔘 Fetching available tables for Bayar Nanti");
|
||||
isPayNow = false;
|
||||
isAddToOrder = false;
|
||||
|
||||
|
||||
// Debug: Check all tables first
|
||||
final allTables = await ProductLocalDatasource.instance.getAllTable();
|
||||
print("🔘 All tables in database: ${allTables.length}");
|
||||
final allTables =
|
||||
await ProductLocalDatasource
|
||||
.instance
|
||||
.getAllTable();
|
||||
print(
|
||||
"🔘 All tables in database: ${allTables.length}");
|
||||
allTables.forEach((table) {
|
||||
print("🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}");
|
||||
print(
|
||||
"🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}");
|
||||
});
|
||||
|
||||
|
||||
// Fetch available tables for Bayar Nanti
|
||||
context
|
||||
.read<GetTableStatusBloc>()
|
||||
@ -1516,8 +1527,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
|
||||
await ProductLocalDatasource
|
||||
.instance
|
||||
.getDraftOrderById(
|
||||
selectTable!
|
||||
.orderId ??
|
||||
selectTable
|
||||
?.orderId ??
|
||||
0);
|
||||
|
||||
if (existingDraftOrder !=
|
||||
|
||||
@ -4,6 +4,7 @@ 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/dialog/type_dialog.dart';
|
||||
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeRightTitle extends StatelessWidget {
|
||||
@ -33,7 +34,7 @@ class HomeRightTitle extends StatelessWidget {
|
||||
width: 180.0,
|
||||
height: 40,
|
||||
elevation: 0,
|
||||
onPressed: () {},
|
||||
onPressed: () => context.push(SalesPage()),
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
icon: Icon(
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
@ -12,9 +11,14 @@ class DaySalesBloc extends Bloc<DaySalesEvent, DaySalesState> {
|
||||
final ProductLocalDatasource datasource;
|
||||
DaySalesBloc(this.datasource) : super(const _Initial()) {
|
||||
on<_GetDaySales>((event, emit) async {
|
||||
emit(const _Loading());
|
||||
final result = await datasource.getAllOrder(event.date);
|
||||
emit(_Loaded(result));
|
||||
});
|
||||
on<_GetRangeDateSales>((event, emit) async {
|
||||
emit(const _Loading());
|
||||
final result =
|
||||
await datasource.getAllOrder(event.date);
|
||||
await datasource.getAllOrderByRange(event.startDate, event.endDate);
|
||||
emit(_Loaded(result));
|
||||
});
|
||||
}
|
||||
|
||||
@ -20,18 +20,22 @@ mixin _$DaySalesEvent {
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() started,
|
||||
required TResult Function(DateTime date) getDaySales,
|
||||
required TResult Function(DateTime startDate, DateTime endDate)
|
||||
getRangeDateSales,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? started,
|
||||
TResult? Function(DateTime date)? getDaySales,
|
||||
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? started,
|
||||
TResult Function(DateTime date)? getDaySales,
|
||||
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@ -39,18 +43,21 @@ mixin _$DaySalesEvent {
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Started value) started,
|
||||
required TResult Function(_GetDaySales value) getDaySales,
|
||||
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Started value)? started,
|
||||
TResult? Function(_GetDaySales value)? getDaySales,
|
||||
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Started value)? started,
|
||||
TResult Function(_GetDaySales value)? getDaySales,
|
||||
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@ -120,6 +127,8 @@ class _$StartedImpl implements _Started {
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() started,
|
||||
required TResult Function(DateTime date) getDaySales,
|
||||
required TResult Function(DateTime startDate, DateTime endDate)
|
||||
getRangeDateSales,
|
||||
}) {
|
||||
return started();
|
||||
}
|
||||
@ -129,6 +138,7 @@ class _$StartedImpl implements _Started {
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? started,
|
||||
TResult? Function(DateTime date)? getDaySales,
|
||||
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
}) {
|
||||
return started?.call();
|
||||
}
|
||||
@ -138,6 +148,7 @@ class _$StartedImpl implements _Started {
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? started,
|
||||
TResult Function(DateTime date)? getDaySales,
|
||||
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (started != null) {
|
||||
@ -151,6 +162,7 @@ class _$StartedImpl implements _Started {
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Started value) started,
|
||||
required TResult Function(_GetDaySales value) getDaySales,
|
||||
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
|
||||
}) {
|
||||
return started(this);
|
||||
}
|
||||
@ -160,6 +172,7 @@ class _$StartedImpl implements _Started {
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Started value)? started,
|
||||
TResult? Function(_GetDaySales value)? getDaySales,
|
||||
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
}) {
|
||||
return started?.call(this);
|
||||
}
|
||||
@ -169,6 +182,7 @@ class _$StartedImpl implements _Started {
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Started value)? started,
|
||||
TResult Function(_GetDaySales value)? getDaySales,
|
||||
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (started != null) {
|
||||
@ -252,6 +266,8 @@ class _$GetDaySalesImpl implements _GetDaySales {
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() started,
|
||||
required TResult Function(DateTime date) getDaySales,
|
||||
required TResult Function(DateTime startDate, DateTime endDate)
|
||||
getRangeDateSales,
|
||||
}) {
|
||||
return getDaySales(date);
|
||||
}
|
||||
@ -261,6 +277,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? started,
|
||||
TResult? Function(DateTime date)? getDaySales,
|
||||
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
}) {
|
||||
return getDaySales?.call(date);
|
||||
}
|
||||
@ -270,6 +287,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? started,
|
||||
TResult Function(DateTime date)? getDaySales,
|
||||
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (getDaySales != null) {
|
||||
@ -283,6 +301,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Started value) started,
|
||||
required TResult Function(_GetDaySales value) getDaySales,
|
||||
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
|
||||
}) {
|
||||
return getDaySales(this);
|
||||
}
|
||||
@ -292,6 +311,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Started value)? started,
|
||||
TResult? Function(_GetDaySales value)? getDaySales,
|
||||
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
}) {
|
||||
return getDaySales?.call(this);
|
||||
}
|
||||
@ -301,6 +321,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Started value)? started,
|
||||
TResult Function(_GetDaySales value)? getDaySales,
|
||||
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (getDaySales != null) {
|
||||
@ -322,6 +343,166 @@ abstract class _GetDaySales implements DaySalesEvent {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$GetRangeDateSalesImplCopyWith<$Res> {
|
||||
factory _$$GetRangeDateSalesImplCopyWith(_$GetRangeDateSalesImpl value,
|
||||
$Res Function(_$GetRangeDateSalesImpl) then) =
|
||||
__$$GetRangeDateSalesImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({DateTime startDate, DateTime endDate});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$GetRangeDateSalesImplCopyWithImpl<$Res>
|
||||
extends _$DaySalesEventCopyWithImpl<$Res, _$GetRangeDateSalesImpl>
|
||||
implements _$$GetRangeDateSalesImplCopyWith<$Res> {
|
||||
__$$GetRangeDateSalesImplCopyWithImpl(_$GetRangeDateSalesImpl _value,
|
||||
$Res Function(_$GetRangeDateSalesImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of DaySalesEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
}) {
|
||||
return _then(_$GetRangeDateSalesImpl(
|
||||
null == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
null == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$GetRangeDateSalesImpl implements _GetRangeDateSales {
|
||||
const _$GetRangeDateSalesImpl(this.startDate, this.endDate);
|
||||
|
||||
@override
|
||||
final DateTime startDate;
|
||||
@override
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DaySalesEvent.getRangeDateSales(startDate: $startDate, endDate: $endDate)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$GetRangeDateSalesImpl &&
|
||||
(identical(other.startDate, startDate) ||
|
||||
other.startDate == startDate) &&
|
||||
(identical(other.endDate, endDate) || other.endDate == endDate));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, startDate, endDate);
|
||||
|
||||
/// Create a copy of DaySalesEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$GetRangeDateSalesImplCopyWith<_$GetRangeDateSalesImpl> get copyWith =>
|
||||
__$$GetRangeDateSalesImplCopyWithImpl<_$GetRangeDateSalesImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() started,
|
||||
required TResult Function(DateTime date) getDaySales,
|
||||
required TResult Function(DateTime startDate, DateTime endDate)
|
||||
getRangeDateSales,
|
||||
}) {
|
||||
return getRangeDateSales(startDate, endDate);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? started,
|
||||
TResult? Function(DateTime date)? getDaySales,
|
||||
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
}) {
|
||||
return getRangeDateSales?.call(startDate, endDate);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? started,
|
||||
TResult Function(DateTime date)? getDaySales,
|
||||
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (getRangeDateSales != null) {
|
||||
return getRangeDateSales(startDate, endDate);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Started value) started,
|
||||
required TResult Function(_GetDaySales value) getDaySales,
|
||||
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
|
||||
}) {
|
||||
return getRangeDateSales(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Started value)? started,
|
||||
TResult? Function(_GetDaySales value)? getDaySales,
|
||||
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
}) {
|
||||
return getRangeDateSales?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Started value)? started,
|
||||
TResult Function(_GetDaySales value)? getDaySales,
|
||||
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (getRangeDateSales != null) {
|
||||
return getRangeDateSales(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _GetRangeDateSales implements DaySalesEvent {
|
||||
const factory _GetRangeDateSales(
|
||||
final DateTime startDate, final DateTime endDate) =
|
||||
_$GetRangeDateSalesImpl;
|
||||
|
||||
DateTime get startDate;
|
||||
DateTime get endDate;
|
||||
|
||||
/// Create a copy of DaySalesEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$GetRangeDateSalesImplCopyWith<_$GetRangeDateSalesImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$DaySalesState {
|
||||
@optionalTypeArgs
|
||||
|
||||
@ -6,4 +6,8 @@ class DaySalesEvent with _$DaySalesEvent {
|
||||
const factory DaySalesEvent.getDaySales(
|
||||
DateTime date,
|
||||
) = _GetDaySales;
|
||||
const factory DaySalesEvent.getRangeDateSales(
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
) = _GetRangeDateSales;
|
||||
}
|
||||
|
||||
123
lib/presentation/sales/dialog/filter_dialog.dart
Normal file
123
lib/presentation/sales/dialog/filter_dialog.dart
Normal file
@ -0,0 +1,123 @@
|
||||
import 'package:enaklo_pos/core/components/buttons.dart';
|
||||
import 'package:enaklo_pos/core/components/custom_modal_dialog.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/extensions/date_time_ext.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesFilterDialog extends StatefulWidget {
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final void Function(DateTime start, DateTime end) onDateRangeChanged;
|
||||
const SalesFilterDialog(
|
||||
{super.key,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.onDateRangeChanged});
|
||||
|
||||
@override
|
||||
State<SalesFilterDialog> createState() => _SalesFilterDialogState();
|
||||
}
|
||||
|
||||
class _SalesFilterDialogState extends State<SalesFilterDialog> {
|
||||
late DateTimeRange selectedDateRange;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedDateRange =
|
||||
DateTimeRange(start: widget.startDate, end: widget.endDate);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _selectDateRange(BuildContext context) async {
|
||||
final DateTimeRange? picked = await showDateRangePicker(
|
||||
context: context,
|
||||
initialDateRange: selectedDateRange,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2100),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.blue, // Header color
|
||||
onPrimary: Colors.white, // Header text color
|
||||
onSurface: Colors.black, // Body text color
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
selectedDateRange = picked;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomModalDialog(
|
||||
title: 'Filter',
|
||||
contentPadding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Periode',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async => await _selectDateRange(context),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
margin: EdgeInsets.only(top: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: AppColors.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${selectedDateRange.start.toFormattedDate2()} - ${selectedDateRange.end.toFormattedDate2()}',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.calendar_month_outlined,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SpaceHeight(24),
|
||||
Button.filled(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
widget.onDateRangeChanged(
|
||||
selectedDateRange.start, selectedDateRange.end);
|
||||
},
|
||||
label: 'Terapkan'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,18 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:enaklo_pos/core/components/buttons.dart';
|
||||
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_order_information.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_payment.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/widgets/sales_right_title.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart';
|
||||
|
||||
import '../widgets/sales_widget.dart';
|
||||
import '../../../core/constants/colors.dart';
|
||||
import '../widgets/sales_card.dart';
|
||||
import '../widgets/sales_title.dart';
|
||||
|
||||
class SalesPage extends StatefulWidget {
|
||||
const SalesPage({super.key});
|
||||
@ -16,110 +22,178 @@ class SalesPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SalesPageState extends State<SalesPage> {
|
||||
DateTime startDate = DateTime.now();
|
||||
DateTime endDate = DateTime.now();
|
||||
OrderModel? orderDetail;
|
||||
|
||||
int _total = 0;
|
||||
String searchQuery = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
context.read<DaySalesBloc>().add(DaySalesEvent.getDaySales(DateTime.now()));
|
||||
context
|
||||
.read<DaySalesBloc>()
|
||||
.add(DaySalesEvent.getRangeDateSales(startDate, endDate));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
List<OrderModel> _filterOrders(List<OrderModel> orders) {
|
||||
if (searchQuery.isEmpty) {
|
||||
return orders;
|
||||
}
|
||||
|
||||
return orders.where((order) {
|
||||
final customerName = order.customerName.toLowerCase();
|
||||
final queryLower = searchQuery.toLowerCase();
|
||||
return customerName.contains(queryLower);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Apskel POS ',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
body: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Material(
|
||||
color: AppColors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
SalesTitle(
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
total: _total,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
searchQuery = value;
|
||||
});
|
||||
},
|
||||
onDateRangeChanged: (start, end) {
|
||||
setState(() {
|
||||
startDate = start;
|
||||
endDate = end;
|
||||
});
|
||||
|
||||
context.read<DaySalesBloc>().add(
|
||||
DaySalesEvent.getRangeDateSales(
|
||||
startDate, endDate));
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<DaySalesBloc, DaySalesState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loaded: (orders) {
|
||||
final filtered = _filterOrders(orders);
|
||||
if (filtered.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
"Belum ada transaksi saat ini. ",
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_total = filtered.length;
|
||||
});
|
||||
});
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: List.generate(
|
||||
filtered.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
orderDetail = filtered[index];
|
||||
});
|
||||
},
|
||||
child: SalesCard(
|
||||
order: orders[index],
|
||||
isActive:
|
||||
orders[index] == orderDetail,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${DateTime.now().toFormattedDate()}",
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtitle,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<DaySalesBloc, DaySalesState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loaded: (orders) {
|
||||
log("message: ${orders.length}");
|
||||
if (orders.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
"Belum ada transaksi saat ini. ",
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SalesWidget(
|
||||
headerWidgets: _getTitleHeaderWidget(),
|
||||
orders: orders,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _getTitleHeaderWidget() {
|
||||
return [
|
||||
_getTitleItemWidget('ID', 40),
|
||||
_getTitleItemWidget('Customer', 120),
|
||||
_getTitleItemWidget('Status', 120),
|
||||
_getTitleItemWidget('Sync', 60),
|
||||
_getTitleItemWidget('Payment Status', 120),
|
||||
_getTitleItemWidget('Payment Method', 120),
|
||||
_getTitleItemWidget('Payment Amount', 120),
|
||||
_getTitleItemWidget('Sub Total', 120),
|
||||
_getTitleItemWidget('Tax', 120),
|
||||
_getTitleItemWidget('Discount', 60),
|
||||
_getTitleItemWidget('Service Charge', 120),
|
||||
_getTitleItemWidget('Total', 120),
|
||||
_getTitleItemWidget('Payment', 60),
|
||||
_getTitleItemWidget('Item', 60),
|
||||
_getTitleItemWidget('Cashier', 150),
|
||||
_getTitleItemWidget('Time', 230),
|
||||
_getTitleItemWidget('Action', 230),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _getTitleItemWidget(String label, double width) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: 56,
|
||||
color: AppColors.primary,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: orderDetail == null
|
||||
? Center(
|
||||
child: Text(
|
||||
"Belum ada order yang dipilih.",
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
SalesRightTitle(
|
||||
order: orderDetail,
|
||||
actionWidget: [
|
||||
Button.outlined(
|
||||
onPressed: () {},
|
||||
label: 'Refund',
|
||||
icon: Icon(Icons.autorenew),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SalesOrderInformation(
|
||||
order: orderDetail,
|
||||
),
|
||||
),
|
||||
SpaceWidth(16),
|
||||
Expanded(
|
||||
child: SalesDetail(
|
||||
order: orderDetail,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SalesListOrder(
|
||||
order: orderDetail,
|
||||
),
|
||||
SalesPayment(
|
||||
order: orderDetail,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
127
lib/presentation/sales/pages/sales_page.dart.backup
Normal file
127
lib/presentation/sales/pages/sales_page.dart.backup
Normal file
@ -0,0 +1,127 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart';
|
||||
|
||||
import '../widgets/sales_widget.dart';
|
||||
|
||||
class SalesPage extends StatefulWidget {
|
||||
const SalesPage({super.key});
|
||||
|
||||
@override
|
||||
State<SalesPage> createState() => _SalesPageState();
|
||||
}
|
||||
|
||||
class _SalesPageState extends State<SalesPage> {
|
||||
@override
|
||||
void initState() {
|
||||
context.read<DaySalesBloc>().add(DaySalesEvent.getDaySales(DateTime.now()));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Apskel POS ',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${DateTime.now().toFormattedDate()}",
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtitle,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<DaySalesBloc, DaySalesState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
orElse: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loaded: (orders) {
|
||||
log("message: ${orders.length}");
|
||||
if (orders.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
"Belum ada transaksi saat ini. ",
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SalesWidget(
|
||||
headerWidgets: _getTitleHeaderWidget(),
|
||||
orders: orders,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _getTitleHeaderWidget() {
|
||||
return [
|
||||
_getTitleItemWidget('ID', 40),
|
||||
_getTitleItemWidget('Customer', 120),
|
||||
_getTitleItemWidget('Status', 120),
|
||||
_getTitleItemWidget('Sync', 60),
|
||||
_getTitleItemWidget('Payment Status', 120),
|
||||
_getTitleItemWidget('Payment Method', 120),
|
||||
_getTitleItemWidget('Payment Amount', 120),
|
||||
_getTitleItemWidget('Sub Total', 120),
|
||||
_getTitleItemWidget('Tax', 120),
|
||||
_getTitleItemWidget('Discount', 60),
|
||||
_getTitleItemWidget('Service Charge', 120),
|
||||
_getTitleItemWidget('Total', 120),
|
||||
_getTitleItemWidget('Payment', 60),
|
||||
_getTitleItemWidget('Item', 60),
|
||||
_getTitleItemWidget('Cashier', 150),
|
||||
_getTitleItemWidget('Time', 230),
|
||||
_getTitleItemWidget('Action', 230),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _getTitleItemWidget(String label, double width) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: 56,
|
||||
color: AppColors.primary,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
113
lib/presentation/sales/widgets/sales_card.dart
Normal file
113
lib/presentation/sales/widgets/sales_card.dart
Normal file
@ -0,0 +1,113 @@
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||
import 'package:enaklo_pos/core/extensions/int_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesCard extends StatelessWidget {
|
||||
final OrderModel order;
|
||||
final bool isActive;
|
||||
|
||||
const SalesCard({
|
||||
super.key,
|
||||
required this.order,
|
||||
required this.isActive,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? AppColors.primary.withOpacity(0.1) : AppColors.white,
|
||||
border:
|
||||
Border.all(color: isActive ? AppColors.primary : AppColors.stroke),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 22,
|
||||
backgroundColor: AppColors.primary,
|
||||
child: Icon(Icons.person, color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
order.customerName == ""
|
||||
? "Anonim"
|
||||
: order.customerName,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.table_bar, size: 16, color: Colors.grey),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Meja ${order.tableNumber}',
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
order.status.toUpperCase(),
|
||||
style: TextStyle(
|
||||
color: Colors.green,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
order.total.currencyFormatRpV2,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateTime.parse(order.transactionTime).toFormattedDate2(),
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
76
lib/presentation/sales/widgets/sales_detail.dart
Normal file
76
lib/presentation/sales/widgets/sales_detail.dart
Normal file
@ -0,0 +1,76 @@
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesDetail extends StatelessWidget {
|
||||
final OrderModel? order;
|
||||
const SalesDetail({super.key, this.order});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Detail',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
_item(
|
||||
title: 'Pelanggan',
|
||||
value: order?.customerName ?? "-",
|
||||
),
|
||||
_item(
|
||||
title: 'Waktu',
|
||||
value:
|
||||
DateTime.parse(order?.transactionTime ?? "").toFormattedDate3(),
|
||||
),
|
||||
_item(
|
||||
title: 'Status',
|
||||
value: order?.paymentStatus ?? "-",
|
||||
),
|
||||
_item(
|
||||
title: 'Jenis Order',
|
||||
value: order?.paymentMethod ?? "-",
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Padding _item({
|
||||
required String title,
|
||||
required String value,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
88
lib/presentation/sales/widgets/sales_list_order.dart
Normal file
88
lib/presentation/sales/widgets/sales_list_order.dart
Normal file
@ -0,0 +1,88 @@
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesListOrder extends StatelessWidget {
|
||||
final OrderModel? order;
|
||||
const SalesListOrder({super.key, this.order});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: AppColors.background),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Daftar Pembelian',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: List.generate(
|
||||
order?.orderItems.length ?? 0,
|
||||
(index) => _item(order!.orderItems[index]),
|
||||
).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Padding _item(ProductQuantity product) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16)
|
||||
.copyWith(bottom: 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
product.product.name ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product.product.price ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'X${product.quantity}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product.product.price ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
76
lib/presentation/sales/widgets/sales_order_information.dart
Normal file
76
lib/presentation/sales/widgets/sales_order_information.dart
Normal file
@ -0,0 +1,76 @@
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesOrderInformation extends StatelessWidget {
|
||||
final OrderModel? order;
|
||||
const SalesOrderInformation({super.key, this.order});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Informasi Pesanan',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
_item(
|
||||
title: 'No. Order',
|
||||
value: "${order?.id}",
|
||||
),
|
||||
_item(
|
||||
title: 'Tanggal',
|
||||
value:
|
||||
DateTime.parse(order?.transactionTime ?? "").toFormattedDate3(),
|
||||
),
|
||||
_item(
|
||||
title: 'Kasir',
|
||||
value: order?.namaKasir ?? "-",
|
||||
),
|
||||
_item(
|
||||
title: 'Jenis Order',
|
||||
value: order?.orderType.value ?? "-",
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Padding _item({
|
||||
required String title,
|
||||
required String value,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
99
lib/presentation/sales/widgets/sales_payment.dart
Normal file
99
lib/presentation/sales/widgets/sales_payment.dart
Normal file
@ -0,0 +1,99 @@
|
||||
import 'package:enaklo_pos/core/components/dashed_divider.dart';
|
||||
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/int_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesPayment extends StatelessWidget {
|
||||
final OrderModel? order;
|
||||
const SalesPayment({super.key, this.order});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Informasi Pesanan',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
SpaceHeight(12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Subtotal ${order?.totalItem} Produk',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(order?.subTotal)?.currencyFormatRp ?? "0",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SpaceHeight(12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Pajak (11%)',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(order?.tax)?.currencyFormatRp ?? "0",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SpaceHeight(12),
|
||||
DashedDivider(
|
||||
color: AppColors.stroke,
|
||||
),
|
||||
SpaceHeight(12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Total',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(order?.total)?.currencyFormatRp ?? "0",
|
||||
style: const TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/presentation/sales/widgets/sales_right_title.dart
Normal file
43
lib/presentation/sales/widgets/sales_right_title.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesRightTitle extends StatelessWidget {
|
||||
final OrderModel? order;
|
||||
final List<Widget>? actionWidget;
|
||||
const SalesRightTitle({super.key, this.order, this.actionWidget});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: context.deviceHeight * 0.1,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.background,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Detail Pesanan #${order?.id}",
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (actionWidget != null) ...actionWidget!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
146
lib/presentation/sales/widgets/sales_title.dart
Normal file
146
lib/presentation/sales/widgets/sales_title.dart
Normal file
@ -0,0 +1,146 @@
|
||||
import 'package:enaklo_pos/core/components/components.dart';
|
||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
||||
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/sales/dialog/filter_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SalesTitle extends StatelessWidget {
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final int total;
|
||||
final Function(String) onChanged;
|
||||
final void Function(DateTime start, DateTime end) onDateRangeChanged;
|
||||
|
||||
const SalesTitle(
|
||||
{super.key,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.onChanged,
|
||||
required this.total,
|
||||
required this.onDateRangeChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
height: context.deviceHeight * 0.1,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.background,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => context.pop(),
|
||||
child: Icon(
|
||||
Icons.arrow_back,
|
||||
color: AppColors.primary,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
SpaceWidth(16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Daftar Pesanan",
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.background,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
startDate.toFormattedDate2() == endDate.toFormattedDate2()
|
||||
? startDate.toFormattedDate2()
|
||||
: '${startDate.toFormattedDate2()} - ${endDate.toFormattedDate2()}',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$total Pesanan',
|
||||
style: TextStyle(
|
||||
color: AppColors.black,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SpaceHeight(16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(
|
||||
Icons.search,
|
||||
),
|
||||
hintText: 'Cari Pesanan',
|
||||
),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => SalesFilterDialog(
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
onDateRangeChanged: onDateRangeChanged,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.filter_list_outlined,
|
||||
color: AppColors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user