feat: sales page

This commit is contained in:
efrilm 2025-08-02 10:50:48 +07:00
parent e825e5daed
commit 45b348684e
17 changed files with 1371 additions and 138 deletions

View 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),
),
);
}),
);
},
),
);
}
}

View File

@ -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/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_item.dart';
import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart';
import 'package:intl/intl.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import '../../presentation/home/models/product_quantity.dart'; import '../../presentation/home/models/product_quantity.dart';
@ -145,7 +144,7 @@ class ProductLocalDatasource {
Future<Database> _initDB(String filePath) async { Future<Database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath(); final dbPath = await getDatabasesPath();
final path = dbPath + filePath; final path = dbPath + filePath;
// Force delete existing database to ensure new schema // Force delete existing database to ensure new schema
try { try {
final dbExists = await databaseExists(path); final dbExists = await databaseExists(path);
@ -156,20 +155,21 @@ class ProductLocalDatasource {
} catch (e) { } catch (e) {
log("Error deleting database: $e"); log("Error deleting database: $e");
} }
return await openDatabase( return await openDatabase(
path, path,
version: 2, version: 2,
onCreate: _createDb, onCreate: _createDb,
onUpgrade: _onUpgrade, onUpgrade: _onUpgrade,
); );
} }
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) { if (oldVersion < 2) {
// Add order_type column to orders table if it doesn't exist // Add order_type column to orders table if it doesn't exist
try { 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"); log("Added order_type column to orders table");
} catch (e) { } catch (e) {
log("order_type column might already exist: $e"); log("order_type column might already exist: $e");
@ -186,11 +186,11 @@ class ProductLocalDatasource {
//save order //save order
Future<int> saveOrder(OrderModel order) async { Future<int> saveOrder(OrderModel order) async {
final db = await instance.database; final db = await instance.database;
// Since we're forcing database recreation, order_type column should exist // Since we're forcing database recreation, order_type column should exist
final orderMap = order.toMap(includeOrderType: true); final orderMap = order.toMap(includeOrderType: true);
log("Final orderMap for insertion: $orderMap"); log("Final orderMap for insertion: $orderMap");
int id = await db.insert(tableOrder, orderMap, int id = await db.insert(tableOrder, orderMap,
conflictAlgorithm: ConflictAlgorithm.replace); 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 //get order item by order id
Future<List<ProductQuantity>> getOrderItemByOrderId(int orderId) async { Future<List<ProductQuantity>> getOrderItemByOrderId(int orderId) async {
final db = await instance.database; final db = await instance.database;
@ -402,12 +427,12 @@ class ProductLocalDatasource {
Future<List<TableModel>> getTableByStatus(String status) async { Future<List<TableModel>> getTableByStatus(String status) async {
final db = await instance.database; final db = await instance.database;
List<Map<String, dynamic>> maps; List<Map<String, dynamic>> maps;
if (status == 'all') { if (status == 'all') {
// Get all tables // Get all tables
maps = await db.query(tableManagement); maps = await db.query(tableManagement);
log("Getting all tables, found: ${maps.length}"); log("Getting all tables, found: ${maps.length}");
// If no tables exist, create some default tables // If no tables exist, create some default tables
if (maps.isEmpty) { if (maps.isEmpty) {
log("No tables found, creating default tables..."); log("No tables found, creating default tables...");
@ -428,19 +453,19 @@ class ProductLocalDatasource {
final tables = List.generate(maps.length, (i) { final tables = List.generate(maps.length, (i) {
return TableModel.fromMap(maps[i]); return TableModel.fromMap(maps[i]);
}); });
log("Returning ${tables.length} tables"); log("Returning ${tables.length} tables");
tables.forEach((table) { tables.forEach((table) {
log("Table: ${table.tableName} (ID: ${table.id}, Status: ${table.status})"); log("Table: ${table.tableName} (ID: ${table.id}, Status: ${table.status})");
}); });
return tables; return tables;
} }
// Create default tables if none exist // Create default tables if none exist
Future<void> _createDefaultTables() async { Future<void> _createDefaultTables() async {
final db = await instance.database; final db = await instance.database;
// Create 5 default tables // Create 5 default tables
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++) {
await db.insert(tableManagement, { await db.insert(tableManagement, {
@ -463,7 +488,7 @@ class ProductLocalDatasource {
await db.update(tableManagement, table.toMap(), await db.update(tableManagement, table.toMap(),
where: 'id = ?', whereArgs: [table.id]); where: 'id = ?', whereArgs: [table.id]);
log("Success Update Status Table: ${table.toMap()}"); log("Success Update Status Table: ${table.toMap()}");
// Verify the update // Verify the update
final updatedTable = await db.query( final updatedTable = await db.query(
tableManagement, tableManagement,
@ -474,7 +499,7 @@ class ProductLocalDatasource {
log("Verified table update: ${updatedTable.first}"); log("Verified table update: ${updatedTable.first}");
} }
} }
// Debug method to reset all tables to available status // Debug method to reset all tables to available status
Future<void> resetAllTablesToAvailable() async { Future<void> resetAllTablesToAvailable() async {
log("Resetting all tables to available status..."); log("Resetting all tables to available status...");
@ -564,7 +589,7 @@ class ProductLocalDatasource {
//update draft order //update draft order
Future<void> updateDraftOrder(DraftOrderModel draftOrder) async { Future<void> updateDraftOrder(DraftOrderModel draftOrder) async {
final db = await instance.database; final db = await instance.database;
// Update the draft order // Update the draft order
await db.update( await db.update(
'draft_orders', 'draft_orders',
@ -572,13 +597,14 @@ class ProductLocalDatasource {
where: 'id = ?', where: 'id = ?',
whereArgs: [draftOrder.id], whereArgs: [draftOrder.id],
); );
// Remove existing items and add new ones // Remove existing items and add new ones
await db.delete('draft_order_items', await db.delete('draft_order_items',
where: 'id_draft_order = ?', whereArgs: [draftOrder.id]); where: 'id_draft_order = ?', whereArgs: [draftOrder.id]);
for (var orderItem in draftOrder.orders) { 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!));
} }
} }

View File

@ -32,10 +32,10 @@ class ConfirmPaymentPage extends StatefulWidget {
final TableModel? table; final TableModel? table;
const ConfirmPaymentPage({ const ConfirmPaymentPage({
Key? key, super.key,
required this.isTable, required this.isTable,
this.table, this.table,
}) : super(key: key); });
@override @override
State<ConfirmPaymentPage> createState() => _ConfirmPaymentPageState(); State<ConfirmPaymentPage> createState() => _ConfirmPaymentPageState();
@ -110,6 +110,7 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
child: Hero( child: Hero(
tag: 'payment_confirmation_screen', tag: 'payment_confirmation_screen',
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.white,
body: Row( body: Row(
children: [ children: [
Expanded( Expanded(
@ -401,7 +402,7 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
final subTotal = final subTotal =
price - (discount / 100 * price); price - (discount / 100 * price);
final finalTax = subTotal * (tax / 100); final finalTax = subTotal * (tax / 100);
final finalDiscount = discount / 100 * subTotal; // final finalDiscount = discount / 100 * subTotal;
// discountAmountValue = finalDiscount.toInt(); // discountAmountValue = finalDiscount.toInt();
// taxFinal = finalTax.toInt(); // taxFinal = finalTax.toInt();
return Text( return Text(
@ -653,14 +654,19 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
"🔘 Fetching available tables for Bayar Nanti"); "🔘 Fetching available tables for Bayar Nanti");
isPayNow = false; isPayNow = false;
isAddToOrder = false; isAddToOrder = false;
// Debug: Check all tables first // Debug: Check all tables first
final allTables = await ProductLocalDatasource.instance.getAllTable(); final allTables =
print("🔘 All tables in database: ${allTables.length}"); await ProductLocalDatasource
.instance
.getAllTable();
print(
"🔘 All tables in database: ${allTables.length}");
allTables.forEach((table) { 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 // Fetch available tables for Bayar Nanti
context context
.read<GetTableStatusBloc>() .read<GetTableStatusBloc>()
@ -683,14 +689,19 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
"🔘 Fetching available tables for Bayar Nanti"); "🔘 Fetching available tables for Bayar Nanti");
isPayNow = false; isPayNow = false;
isAddToOrder = false; isAddToOrder = false;
// Debug: Check all tables first // Debug: Check all tables first
final allTables = await ProductLocalDatasource.instance.getAllTable(); final allTables =
print("🔘 All tables in database: ${allTables.length}"); await ProductLocalDatasource
.instance
.getAllTable();
print(
"🔘 All tables in database: ${allTables.length}");
allTables.forEach((table) { 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 // Fetch available tables for Bayar Nanti
context context
.read<GetTableStatusBloc>() .read<GetTableStatusBloc>()
@ -1516,8 +1527,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
await ProductLocalDatasource await ProductLocalDatasource
.instance .instance
.getDraftOrderById( .getDraftOrderById(
selectTable! selectTable
.orderId ?? ?.orderId ??
0); 0);
if (existingDraftOrder != if (existingDraftOrder !=

View File

@ -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/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/dialog/type_dialog.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/home/pages/dashboard_page.dart';
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class HomeRightTitle extends StatelessWidget { class HomeRightTitle extends StatelessWidget {
@ -33,7 +34,7 @@ class HomeRightTitle extends StatelessWidget {
width: 180.0, width: 180.0,
height: 40, height: 40,
elevation: 0, elevation: 0,
onPressed: () {}, onPressed: () => context.push(SalesPage()),
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
icon: Icon( icon: Icon(

View File

@ -1,4 +1,3 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/models/order_model.dart'; import 'package:enaklo_pos/presentation/home/models/order_model.dart';
@ -12,9 +11,14 @@ class DaySalesBloc extends Bloc<DaySalesEvent, DaySalesState> {
final ProductLocalDatasource datasource; final ProductLocalDatasource datasource;
DaySalesBloc(this.datasource) : super(const _Initial()) { DaySalesBloc(this.datasource) : super(const _Initial()) {
on<_GetDaySales>((event, emit) async { 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()); emit(const _Loading());
final result = final result =
await datasource.getAllOrder(event.date); await datasource.getAllOrderByRange(event.startDate, event.endDate);
emit(_Loaded(result)); emit(_Loaded(result));
}); });
} }

View File

@ -20,18 +20,22 @@ mixin _$DaySalesEvent {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() started, required TResult Function() started,
required TResult Function(DateTime date) getDaySales, required TResult Function(DateTime date) getDaySales,
required TResult Function(DateTime startDate, DateTime endDate)
getRangeDateSales,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started, TResult? Function()? started,
TResult? Function(DateTime date)? getDaySales, TResult? Function(DateTime date)? getDaySales,
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? started, TResult Function()? started,
TResult Function(DateTime date)? getDaySales, TResult Function(DateTime date)? getDaySales,
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
required TResult orElse(), required TResult orElse(),
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@ -39,18 +43,21 @@ mixin _$DaySalesEvent {
TResult map<TResult extends Object?>({ TResult map<TResult extends Object?>({
required TResult Function(_Started value) started, required TResult Function(_Started value) started,
required TResult Function(_GetDaySales value) getDaySales, required TResult Function(_GetDaySales value) getDaySales,
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({ TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started, TResult? Function(_Started value)? started,
TResult? Function(_GetDaySales value)? getDaySales, TResult? Function(_GetDaySales value)? getDaySales,
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult maybeMap<TResult extends Object?>({ TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started, TResult Function(_Started value)? started,
TResult Function(_GetDaySales value)? getDaySales, TResult Function(_GetDaySales value)? getDaySales,
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
required TResult orElse(), required TResult orElse(),
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@ -120,6 +127,8 @@ class _$StartedImpl implements _Started {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() started, required TResult Function() started,
required TResult Function(DateTime date) getDaySales, required TResult Function(DateTime date) getDaySales,
required TResult Function(DateTime startDate, DateTime endDate)
getRangeDateSales,
}) { }) {
return started(); return started();
} }
@ -129,6 +138,7 @@ class _$StartedImpl implements _Started {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started, TResult? Function()? started,
TResult? Function(DateTime date)? getDaySales, TResult? Function(DateTime date)? getDaySales,
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
}) { }) {
return started?.call(); return started?.call();
} }
@ -138,6 +148,7 @@ class _$StartedImpl implements _Started {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? started, TResult Function()? started,
TResult Function(DateTime date)? getDaySales, TResult Function(DateTime date)? getDaySales,
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (started != null) { if (started != null) {
@ -151,6 +162,7 @@ class _$StartedImpl implements _Started {
TResult map<TResult extends Object?>({ TResult map<TResult extends Object?>({
required TResult Function(_Started value) started, required TResult Function(_Started value) started,
required TResult Function(_GetDaySales value) getDaySales, required TResult Function(_GetDaySales value) getDaySales,
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
}) { }) {
return started(this); return started(this);
} }
@ -160,6 +172,7 @@ class _$StartedImpl implements _Started {
TResult? mapOrNull<TResult extends Object?>({ TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started, TResult? Function(_Started value)? started,
TResult? Function(_GetDaySales value)? getDaySales, TResult? Function(_GetDaySales value)? getDaySales,
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
}) { }) {
return started?.call(this); return started?.call(this);
} }
@ -169,6 +182,7 @@ class _$StartedImpl implements _Started {
TResult maybeMap<TResult extends Object?>({ TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started, TResult Function(_Started value)? started,
TResult Function(_GetDaySales value)? getDaySales, TResult Function(_GetDaySales value)? getDaySales,
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (started != null) { if (started != null) {
@ -252,6 +266,8 @@ class _$GetDaySalesImpl implements _GetDaySales {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() started, required TResult Function() started,
required TResult Function(DateTime date) getDaySales, required TResult Function(DateTime date) getDaySales,
required TResult Function(DateTime startDate, DateTime endDate)
getRangeDateSales,
}) { }) {
return getDaySales(date); return getDaySales(date);
} }
@ -261,6 +277,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started, TResult? Function()? started,
TResult? Function(DateTime date)? getDaySales, TResult? Function(DateTime date)? getDaySales,
TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
}) { }) {
return getDaySales?.call(date); return getDaySales?.call(date);
} }
@ -270,6 +287,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? started, TResult Function()? started,
TResult Function(DateTime date)? getDaySales, TResult Function(DateTime date)? getDaySales,
TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (getDaySales != null) { if (getDaySales != null) {
@ -283,6 +301,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
TResult map<TResult extends Object?>({ TResult map<TResult extends Object?>({
required TResult Function(_Started value) started, required TResult Function(_Started value) started,
required TResult Function(_GetDaySales value) getDaySales, required TResult Function(_GetDaySales value) getDaySales,
required TResult Function(_GetRangeDateSales value) getRangeDateSales,
}) { }) {
return getDaySales(this); return getDaySales(this);
} }
@ -292,6 +311,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
TResult? mapOrNull<TResult extends Object?>({ TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started, TResult? Function(_Started value)? started,
TResult? Function(_GetDaySales value)? getDaySales, TResult? Function(_GetDaySales value)? getDaySales,
TResult? Function(_GetRangeDateSales value)? getRangeDateSales,
}) { }) {
return getDaySales?.call(this); return getDaySales?.call(this);
} }
@ -301,6 +321,7 @@ class _$GetDaySalesImpl implements _GetDaySales {
TResult maybeMap<TResult extends Object?>({ TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started, TResult Function(_Started value)? started,
TResult Function(_GetDaySales value)? getDaySales, TResult Function(_GetDaySales value)? getDaySales,
TResult Function(_GetRangeDateSales value)? getRangeDateSales,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (getDaySales != null) { if (getDaySales != null) {
@ -322,6 +343,166 @@ abstract class _GetDaySales implements DaySalesEvent {
throw _privateConstructorUsedError; 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 /// @nodoc
mixin _$DaySalesState { mixin _$DaySalesState {
@optionalTypeArgs @optionalTypeArgs

View File

@ -6,4 +6,8 @@ class DaySalesEvent with _$DaySalesEvent {
const factory DaySalesEvent.getDaySales( const factory DaySalesEvent.getDaySales(
DateTime date, DateTime date,
) = _GetDaySales; ) = _GetDaySales;
const factory DaySalesEvent.getRangeDateSales(
DateTime startDate,
DateTime endDate,
) = _GetRangeDateSales;
} }

View 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'),
],
),
],
),
);
}
}

View File

@ -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/material.dart';
import 'package:flutter_bloc/flutter_bloc.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 { class SalesPage extends StatefulWidget {
const SalesPage({super.key}); const SalesPage({super.key});
@ -16,110 +22,178 @@ class SalesPage extends StatefulWidget {
} }
class _SalesPageState extends State<SalesPage> { class _SalesPageState extends State<SalesPage> {
DateTime startDate = DateTime.now();
DateTime endDate = DateTime.now();
OrderModel? orderDetail;
int _total = 0;
String searchQuery = '';
@override @override
void initState() { void initState() {
context.read<DaySalesBloc>().add(DaySalesEvent.getDaySales(DateTime.now())); context
.read<DaySalesBloc>()
.add(DaySalesEvent.getRangeDateSales(startDate, endDate));
super.initState(); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return SafeArea(
padding: const EdgeInsets.all(32), child: Scaffold(
child: Column( backgroundColor: AppColors.background,
children: [ body: Row(
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, Expanded(
children: [ flex: 2,
const Text( child: Material(
'Apskel POS ', color: AppColors.white,
style: TextStyle( child: Column(
color: AppColors.primary, children: [
fontSize: 22, SalesTitle(
fontWeight: FontWeight.w600, 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,
);
}
},
);
},
), ),
), Expanded(
], flex: 4,
), child: orderDetail == null
); ? Center(
} child: Text(
"Belum ada order yang dipilih.",
List<Widget> _getTitleHeaderWidget() { style: TextStyle(
return [ fontSize: 16.0,
_getTitleItemWidget('ID', 40), fontWeight: FontWeight.bold,
_getTitleItemWidget('Customer', 120), ),
_getTitleItemWidget('Status', 120), ),
_getTitleItemWidget('Sync', 60), )
_getTitleItemWidget('Payment Status', 120), : Column(
_getTitleItemWidget('Payment Method', 120), children: [
_getTitleItemWidget('Payment Amount', 120), SalesRightTitle(
_getTitleItemWidget('Sub Total', 120), order: orderDetail,
_getTitleItemWidget('Tax', 120), actionWidget: [
_getTitleItemWidget('Discount', 60), Button.outlined(
_getTitleItemWidget('Service Charge', 120), onPressed: () {},
_getTitleItemWidget('Total', 120), label: 'Refund',
_getTitleItemWidget('Payment', 60), icon: Icon(Icons.autorenew),
_getTitleItemWidget('Item', 60), ),
_getTitleItemWidget('Cashier', 150), ],
_getTitleItemWidget('Time', 230), ),
_getTitleItemWidget('Action', 230), Padding(
]; padding: const EdgeInsets.all(16.0),
} child: Column(
children: [
Widget _getTitleItemWidget(String label, double width) { Row(
return Container( children: [
width: width, Expanded(
height: 56, child: SalesOrderInformation(
color: AppColors.primary, order: orderDetail,
alignment: Alignment.centerLeft, ),
child: Center( ),
child: Text( SpaceWidth(16),
label, Expanded(
style: const TextStyle( child: SalesDetail(
color: Colors.white, order: orderDetail,
), ),
),
],
),
SalesListOrder(
order: orderDetail,
),
SalesPayment(
order: orderDetail,
),
],
),
)
],
),
),
],
), ),
), ),
); );

View 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,
),
),
),
);
}
}

View 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,
),
),
],
),
],
),
),
);
}
}

View 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,
),
),
],
),
);
}
}

View 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,
),
),
],
),
);
}
}

View 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,
),
),
],
),
);
}
}

View 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,
),
),
],
),
],
),
);
}
}

View 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!,
],
),
);
}
}

View 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,
),
),
),
],
),
],
),
),
],
);
}
}