From 7a6da0397b38f3e66ef6770fa0cb2902a6b413e2 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 1 Aug 2025 01:40:33 +0700 Subject: [PATCH] feat: detail product dialog --- lib/core/components/custom_modal_dialog.dart | 29 ++-- .../dialogs/detail_product_dialog.dart | 153 ++++++++++++++++++ .../setting/pages/product_page.dart | 1 - .../setting/widgets/menu_product_item.dart | 81 +--------- 4 files changed, 177 insertions(+), 87 deletions(-) create mode 100644 lib/presentation/setting/dialogs/detail_product_dialog.dart diff --git a/lib/core/components/custom_modal_dialog.dart b/lib/core/components/custom_modal_dialog.dart index 99da382..f4b9966 100644 --- a/lib/core/components/custom_modal_dialog.dart +++ b/lib/core/components/custom_modal_dialog.dart @@ -8,13 +8,22 @@ class CustomModalDialog extends StatelessWidget { final String? subtitle; final Widget child; final VoidCallback? onClose; + final double? minWidth; + final double? maxWidth; + final double? minHeight; + final double? maxHeight; - const CustomModalDialog( - {super.key, - required this.title, - this.subtitle, - required this.child, - this.onClose}); + const CustomModalDialog({ + super.key, + required this.title, + this.subtitle, + required this.child, + this.onClose, + this.minWidth, + this.maxWidth, + this.minHeight, + this.maxHeight, + }); @override Widget build(BuildContext context) { @@ -25,10 +34,10 @@ class CustomModalDialog extends StatelessWidget { ), child: ConstrainedBox( constraints: BoxConstraints( - minWidth: context.deviceWidth * 0.3, - maxWidth: context.deviceWidth * 0.8, - minHeight: context.deviceHeight * 0.3, - maxHeight: context.deviceHeight * 0.8, + minWidth: minWidth ?? context.deviceWidth * 0.3, + maxWidth: maxWidth ?? context.deviceWidth * 0.8, + minHeight: minHeight ?? context.deviceHeight * 0.3, + maxHeight: maxHeight ?? context.deviceHeight * 0.8, ), child: IntrinsicWidth( child: Column( diff --git a/lib/presentation/setting/dialogs/detail_product_dialog.dart b/lib/presentation/setting/dialogs/detail_product_dialog.dart new file mode 100644 index 0000000..f3e3e12 --- /dev/null +++ b/lib/presentation/setting/dialogs/detail_product_dialog.dart @@ -0,0 +1,153 @@ +import 'package:cached_network_image/cached_network_image.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/constants/variables.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/data/models/response/product_response_model.dart'; +import 'package:flutter/material.dart'; + +class DetailProductDialog extends StatelessWidget { + final Product product; + const DetailProductDialog({super.key, required this.product}); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: "Detail Produk", + maxWidth: context.deviceWidth * 0.5, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: product.image!.contains('http') + ? product.image! + : '${Variables.baseUrl}/${product.image}', + fit: BoxFit.cover, + errorWidget: (context, url, error) => Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColors.grey.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.image_outlined, + color: AppColors.grey, + size: 40, + ), + ), + ), + ), + const SpaceWidth(16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerRight, + child: _buildStatus(), + ), + const SpaceHeight(8), + Text( + product.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + if (product.description != null && + product.description!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + product.description!, + style: const TextStyle( + fontSize: 14, + color: AppColors.grey, + ), + ), + ), + ], + ), + ), + ], + ), + const SpaceHeight(16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildItem( + product.category?.name ?? "-", + "Kategori", + ), + _buildItem( + "${product.stock}", + "Stok", + valueColor: product.stock! < 50 + ? AppColors.red + : product.stock! < 100 + ? Colors.yellow + : AppColors.green, + ), + _buildItem( + (product.price ?? "0").currencyFormatRpV2, + "Harga", + valueColor: AppColors.primary, + ), + ], + ), + ], + ), + ), + ); + } + + Column _buildItem(String value, String label, + {Color valueColor = AppColors.black}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + const SpaceHeight(4), + Text( + value, + style: TextStyle( + fontSize: 14, + color: valueColor, + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } + + Container _buildStatus() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: product.status == 1 ? AppColors.green : AppColors.red, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + product.status == 1 ? 'Aktif' : 'Tidak Aktif', + style: const TextStyle( + color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700), + ), + ); + } +} diff --git a/lib/presentation/setting/pages/product_page.dart b/lib/presentation/setting/pages/product_page.dart index 76a110b..eb6d8c3 100644 --- a/lib/presentation/setting/pages/product_page.dart +++ b/lib/presentation/setting/pages/product_page.dart @@ -8,7 +8,6 @@ import 'package:enaklo_pos/presentation/setting/bloc/update_product/update_produ import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/add_product/add_product_bloc.dart'; import 'package:enaklo_pos/presentation/setting/dialogs/form_product_dialog.dart'; -import 'package:enaklo_pos/presentation/setting/widgets/add_data.dart'; import 'package:enaklo_pos/presentation/setting/widgets/menu_product_item.dart'; import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart'; diff --git a/lib/presentation/setting/widgets/menu_product_item.dart b/lib/presentation/setting/widgets/menu_product_item.dart index 1e1d67b..b9cc346 100644 --- a/lib/presentation/setting/widgets/menu_product_item.dart +++ b/lib/presentation/setting/widgets/menu_product_item.dart @@ -1,6 +1,7 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:cached_network_image/cached_network_image.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/setting/dialogs/detail_product_dialog.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; @@ -100,82 +101,10 @@ class MenuProductItem extends StatelessWidget { children: [ Expanded( child: GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) { - //container for product detail - return AlertDialog( - backgroundColor: AppColors.white, - contentPadding: const EdgeInsets.all(16.0), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - data.name!, - style: const TextStyle( - fontSize: 20, - ), - ), - IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: const Icon(Icons.close), - ), - ], - ), - const SpaceHeight(10.0), - ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(10.0)), - child: CachedNetworkImage( - imageUrl: '${Variables.baseUrl}${data.image}', - placeholder: (context, url) => const Center( - child: CircularProgressIndicator()), - errorWidget: (context, url, error) => - const Icon( - Icons.food_bank_outlined, - size: 80, - ), - width: 80, - ), - ), - const SpaceHeight(10.0), - Text( - data.category?.name ?? '-', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - const SpaceHeight(10.0), - Text( - data.price.toString(), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - const SpaceHeight(10.0), - Text( - data.stock.toString(), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - const SpaceHeight(10.0), - ], - ), - ); - }, - ); - }, + onTap: () => showDialog( + context: context, + builder: (context) => DetailProductDialog(product: data), + ), child: Container( padding: const EdgeInsets.symmetric(vertical: 4), decoration: BoxDecoration(