@@ -43,8 +43,17 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
4343 }
4444 }
4545 });
46+ }
47+
48+ @override
49+ void dispose () {
50+ _addressController.dispose ();
51+ super .dispose ();
52+ }
4653
47- // Listen for draft changes and keep CartBloc in sync
54+ @override
55+ Widget build (BuildContext context) {
56+ // Ref listener moved to build method to comply with Riverpod rules.
4857 ref.listen <OrderDraft >(orderDraftProvider, (prev, next) {
4958 final cartBloc = context.read <CartBloc >();
5059 // Rebuild cart state to mirror draft exactly.
@@ -59,16 +68,6 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
5968 }
6069 }
6170 });
62- }
63-
64- @override
65- void dispose () {
66- _addressController.dispose ();
67- super .dispose ();
68- }
69-
70- @override
71- Widget build (BuildContext context) {
7271 final authState = context.watch <AuthBloc >().state;
7372 if (authState.status == AuthStatus .authenticated &&
7473 authState.user? .address != null &&
@@ -106,12 +105,15 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
106105 child: GestureDetector (
107106 onTap: () async {
108107 final selected = await Navigator .of (context).push (
109- MaterialPageRoute (builder: (_) => const CustomerSearchPage ()),
108+ MaterialPageRoute (
109+ builder: (_) => const CustomerSearchPage ()),
110110 );
111111 if (selected != null && selected is User ) {
112112 // For now just show snackbar; integration can follow
113113 ScaffoldMessenger .of (context).showSnackBar (
114- SnackBar (content: Text ('Đã chọn khách: ${selected .name }' )),
114+ SnackBar (
115+ content: Text (
116+ 'Đã chọn khách: ${selected .name }' )),
115117 );
116118 }
117119 },
@@ -148,7 +150,15 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
148150 padding: const EdgeInsets .symmetric (horizontal: 16 ),
149151 child: SectionCard (
150152 title: 'Danh sách sản phẩm' ,
151- trailing: const SizedBox .shrink (),
153+ trailing: TextButton .icon (
154+ onPressed: () {
155+ Navigator .of (context)
156+ .pushNamed ('/order/select-product' );
157+ },
158+ icon: const Icon (Icons .add_circle_outline,
159+ color: Colors .green, size: 18 ),
160+ label: const Text ('Thêm mới' ),
161+ ),
152162 child: _ProductSection (items: state.items),
153163 ),
154164 ),
@@ -183,6 +193,11 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
183193 onTap: state.items.isEmpty
184194 ? null
185195 : () {
196+ // Sync latest cart items into OrderDraft before proceeding
197+ ref
198+ .read (orderDraftProvider.notifier)
199+ .setItems (state.items);
200+
186201 Navigator .of (context).push (
187202 MaterialPageRoute (
188203 builder: (_) => const PaymentPage ()),
@@ -262,8 +277,7 @@ class _CustomerInfoContent extends StatelessWidget {
262277 decoration: const InputDecoration (
263278 hintText: 'Địa chỉ giao hàng' ,
264279 isDense: true ,
265- border: OutlineInputBorder (
266- borderSide: BorderSide .none),
280+ border: OutlineInputBorder (borderSide: BorderSide .none),
267281 ),
268282 ),
269283 ),
@@ -295,17 +309,18 @@ class _ProductSection extends StatelessWidget {
295309
296310 @override
297311 Widget build (BuildContext context) {
298- final cols = Responsive .gridColumnCount (context);
299- return GridView .builder (
312+ if (items.isEmpty) {
313+ return const Padding (
314+ padding: EdgeInsets .all (12 ),
315+ child: Text ('Chưa có sản phẩm' ),
316+ );
317+ }
318+
319+ return ListView .separated (
300320 shrinkWrap: true ,
301321 physics: const NeverScrollableScrollPhysics (),
302- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount (
303- crossAxisCount: cols,
304- mainAxisSpacing: 8 ,
305- crossAxisSpacing: 8 ,
306- childAspectRatio: cols == 1 ? 5 : 3 ,
307- ),
308322 itemCount: items.length,
323+ separatorBuilder: (_, __) => const Divider (height: 1 ),
309324 itemBuilder: (_, i) => _ProductRow (item: items[i]),
310325 );
311326 }
@@ -320,70 +335,88 @@ class _ProductRow extends StatelessWidget {
320335 Widget build (BuildContext context) {
321336 return Container (
322337 padding: const EdgeInsets .all (8 ),
323- child: SizedBox (
324- width: double .infinity,
325- child: Row (
326- children: [
327- Container (width: 60 , height: 60 , color: Colors .green.shade200),
328- const SizedBox (width: 8 ),
329- Expanded (
330- child: Column (
331- crossAxisAlignment: CrossAxisAlignment .start,
332- children: [
333- Text (item.product.title,
334- maxLines: 1 ,
335- overflow: TextOverflow .ellipsis,
336- style: const TextStyle (
337- color: Colors .orange, fontWeight: FontWeight .bold)),
338- Text (_currency (item.product.price),
339- style: const TextStyle (
340- color: Colors .red, fontWeight: FontWeight .bold)),
341- ],
342- ),
343- ),
344- InkWell (
345- onTap: () {
346- context.read <CartBloc >().add (CartItemQuantityChanged (
347- productId: item.product.id, delta: - 1 ));
348- },
349- child: const Icon (Icons .remove_circle_outline, size: 20 ),
350- ),
351- Text (item.quantity.toString ()),
352- InkWell (
353- onTap: () {
354- context.read <CartBloc >().add (CartItemQuantityChanged (
355- productId: item.product.id, delta: 1 ));
356- },
357- child: const Icon (Icons .add_circle_outline, size: 20 ),
358- ),
359- // discount row
360- Padding (
361- padding: const EdgeInsets .symmetric (horizontal: 12 , vertical: 4 ),
362- child: Row (
363- children: [
364- const Text ('Giảm giá:' ),
365- const SizedBox (width: 8 ),
366- Expanded (
367- child: TextField (
368- keyboardType: TextInputType .number,
369- decoration: const InputDecoration (
370- isDense: true ,
371- contentPadding: EdgeInsets .all (6 ),
372- hintText: '0' ),
373- onSubmitted: (value) {
374- final discount = double .tryParse (value) ?? 0 ;
375- context.read <CartBloc >().add (CartItemDiscountChanged (
376- productId: item.product.id, discount: discount));
377- },
338+ child: Column (
339+ crossAxisAlignment: CrossAxisAlignment .start,
340+ children: [
341+ Row (
342+ children: [
343+ item.product.imageUrl.isNotEmpty
344+ ? Image .network (item.product.imageUrl,
345+ width: 60 , height: 60 , fit: BoxFit .cover)
346+ : Container (
347+ width: 60 ,
348+ height: 60 ,
349+ color: Colors .grey.shade300,
350+ child: const Icon (Icons .image_not_supported),
378351 ),
379- ),
380- const SizedBox (width: 4 ),
381- const Text ('vnd' ),
382- ],
352+ const SizedBox (width: 8 ),
353+ Expanded (
354+ child: Column (
355+ crossAxisAlignment: CrossAxisAlignment .start,
356+ children: [
357+ Text (item.product.title,
358+ maxLines: 1 ,
359+ overflow: TextOverflow .ellipsis,
360+ style: const TextStyle (
361+ color: Colors .orange, fontWeight: FontWeight .bold)),
362+ Text (_currency (item.product.price),
363+ style: const TextStyle (
364+ color: Colors .red, fontWeight: FontWeight .bold)),
365+ ],
366+ ),
383367 ),
384- ),
385- ],
386- ),
368+ InkWell (
369+ onTap: () {
370+ context.read <CartBloc >().add (CartItemQuantityChanged (
371+ productId: item.product.id, delta: - 1 ));
372+ },
373+ child: const Icon (Icons .remove_circle_outline, size: 20 ),
374+ ),
375+ const SizedBox (width: 4 ),
376+ Text (item.quantity.toString ()),
377+ const SizedBox (width: 4 ),
378+ InkWell (
379+ onTap: () {
380+ context.read <CartBloc >().add (CartItemQuantityChanged (
381+ productId: item.product.id, delta: 1 ));
382+ },
383+ child: const Icon (Icons .add_circle_outline, size: 20 ),
384+ ),
385+ ],
386+ ),
387+ const SizedBox (height: 6 ),
388+ Row (
389+ children: [
390+ const Text ('Giảm giá:' ),
391+ const SizedBox (width: 8 ),
392+ Expanded (
393+ child: TextFormField (
394+ initialValue: item.discountValue > 0
395+ ? item.discountValue.toStringAsFixed (0 )
396+ : '' ,
397+ keyboardType: TextInputType .number,
398+ decoration: const InputDecoration (
399+ isDense: true ,
400+ contentPadding: EdgeInsets .all (6 ),
401+ hintText: '0' ),
402+ onChanged: (value) {
403+ final discount = double .tryParse (value) ?? 0 ;
404+ // apply immediately as user types
405+ context.read <CartBloc >().add (CartItemDiscountChanged (
406+ productId: item.product.id, discount: discount));
407+ },
408+ onFieldSubmitted: (value) {
409+ final discount = double .tryParse (value) ?? 0 ;
410+ context.read <CartBloc >().add (CartItemDiscountChanged (
411+ productId: item.product.id, discount: discount));
412+ },
413+ ),
414+ ),
415+ const SizedBox (width: 4 ),
416+ const Text ('vnd' ),
417+ ],
418+ ),
419+ ],
387420 ),
388421 );
389422 }
0 commit comments