import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:inventory_app/models/famille_produit.dart'; import 'package:inventory_app/models/product.dart'; import 'package:inventory_app/service/axelor_client.dart'; import 'package:inventory_app/controllers/home_controller.dart'; import 'package:inventory_app/service/product_image_updater.dart'; import 'package:inventory_app/widgets/glass_widgets.dart'; import 'package:quickalert/quickalert.dart'; import 'package:inventory_app/utils.dart'; class CreateProductPage extends StatefulWidget { const CreateProductPage({super.key}); @override State createState() => _CreateProductPageState(); } class _CreateProductPageState extends State with SingleTickerProviderStateMixin { // ----- Controllers ----- final _formKey = GlobalKey(); final _nameCtrl = TextEditingController(); final _serialCtrl = TextEditingController(); final _obsCtrl = TextEditingController(); // ----- State ----- final MaterialColor primary = Colors.blue; String _selectedState = 'Neuf'; bool _saving = false; // Success animation controller late final AnimationController _animCtrl; late final Animation _scale; late AxelorClient client; late final HomeController controller; Product? _createdProduct; Future?> getFamilleProduit() async { client = await AxelorClient.create(); return client.getFamilleProduit(); } @override void initState() { super.initState(); _animCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 420), ); _scale = CurvedAnimation(parent: _animCtrl, curve: Curves.easeOutBack); controller = Get.find(); WidgetsBinding.instance.addPostFrameCallback((_) { controller.fetchFamilleProduits(); }); } @override void dispose() { _nameCtrl.dispose(); _serialCtrl.dispose(); _obsCtrl.dispose(); _animCtrl.dispose(); super.dispose(); } // ----- UI ----- @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ const FancyBackground(), SafeArea( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), child: Form( key: _formKey, child: Column( children: [ BackAppBar( title: 'Créer un article', icon: Icons.inventory_2_rounded, ), SectionHeader( icon: Icons.inventory_2_rounded, title: 'Informations de base', accent: primary, ), GlassCard( child: Column( children: [ FancyTextField( controller: _nameCtrl, label: "Nom de l'article", hint: 'Ex: HP LaserJet Pro M404', icon: Icons.badge_rounded, validator: (v) => v == null || v.trim().isEmpty ? 'Product name is required' : null, ), const SizedBox(height: 12), // Famille Produit Obx(() { if (controller.isLoading.value && controller.familleProduits.isEmpty) { return const Center( child: CircularProgressIndicator(), ); } return _FancyDropdown( label: "Famille d'article", value: controller.selectedFamilleProduit.value, icon: Icons.category_rounded, items: controller.familleProduits, onChanged: controller.onFamilleSelected, validator: (v) => v == null ? 'Sélectionnez une famille' : null, ); }), const SizedBox(height: 12), Obx(() { if (controller.isLoading.value && controller.sousFamilleProduits.isEmpty) { return const Center( child: Padding( padding: EdgeInsets.all(8.0), child: CircularProgressIndicator(), ), ); } final items = controller.sousFamilleProduits; return _FancyDropdown( label: "Sous-Famille d'article", value: controller.selectedSousFamilleProduit.value, icon: Icons.subdirectory_arrow_right_rounded, items: items, onChanged: items.isEmpty ? null : (val) => controller .selectedSousFamilleProduit .value = val, validator: (v) => v == null ? 'Sélectionnez une sous-famille' : null, ); }), const SizedBox(height: 12), _FancyDropdown( label: 'Etat', value: _selectedState, icon: Icons.fact_check_rounded, items: const [ 'Ancien', 'Moyen', 'Neuf', 'Réformée', ], onChanged: (val) { if (val != null) { HapticFeedback.selectionClick(); setState(() => _selectedState = val); } }, ), ], ), ), const SizedBox(height: 16), SectionHeader( icon: Icons.confirmation_number_rounded, title: 'Identification', accent: primary, ), GlassCard( child: Column( children: [ FancyTextField( controller: _serialCtrl, label: 'N° de serie', hint: 'e.g. SN-ABC-123456', icon: Icons.numbers_rounded, ), const SizedBox(height: 12), FancyTextField( controller: _obsCtrl, label: 'Observation', hint: 'Notes supplémentaires (optionnel)', icon: Icons.notes_rounded, maxLines: 4, ), ], ), ), const SizedBox(height: 20), // Buttons Row Row( children: [ Expanded( child: _PrimaryButton( text: _saving ? 'Enregistrement...' : 'Sauvegarder', icon: Icons.save_rounded, primary: primary, onPressed: _saving ? null : _onSave, ), ), const SizedBox(width: 12), _CameraButton(primary: primary, onTap: _onTakeImage), ], ), ], ), ), ), ), if (_saving) const _SavingOverlay(), ], ), ); } // ----- Actions ----- Future _onSave() async { HapticFeedback.vibrate(); client = await AxelorClient.create(); if (!mounted) return; if (!_formKey.currentState!.validate()) { QuickAlert.show( context: context, type: QuickAlertType.error, title: 'Validation', text: 'Veuillez remplir tous les champs obligatoires.', ); return; } if (controller.selectedFamilleProduit.value?.id == null || controller.selectedSousFamilleProduit.value?.id == null) { QuickAlert.show( context: context, type: QuickAlertType.error, title: 'Missing', text: 'Sélectionnez la famille et la sous-famille.', ); return; } setState(() => _saving = true); try { final product = Product( name: _nameCtrl.text.trim(), familleProduit: controller.selectedFamilleProduit.value?.id, sousFamilleProduit: controller.selectedSousFamilleProduit.value?.id, productTypeSelect: 'storable', internalDescription: _serialCtrl.text.trim(), description: _obsCtrl.text.trim(), unit: Unit(id: 4), purchaseUnit: Unit(id: 4), procurementMethodSelect: 'buy', ); _createdProduct = await client.createProducts( product, controller.selectedDepot.value, ); if (!mounted) return; if (_createdProduct != null) { QuickAlert.show( context: context, type: QuickAlertType.success, title: 'Succès', text: "L'article ${_nameCtrl.text.trim()} a été créé avec succès !.", ); await _showSuccessAnimation(); } else { if (!mounted) return; QuickAlert.show( context: context, type: QuickAlertType.error, title: 'Erreur', text: "L'article ${_nameCtrl.text.trim()} existe déjà.", ); } } catch (e) { if (!mounted) return; QuickAlert.show( context: context, type: QuickAlertType.error, title: 'Erreur', text: "Échec de la création de l'article: $e", ); } finally { if (mounted) setState(() => _saving = false); } } Future _onTakeImage() async { HapticFeedback.lightImpact(); client = await AxelorClient.create(); if (!mounted) return; if (!_formKey.currentState!.validate() || controller.selectedFamilleProduit.value?.id == null || controller.selectedSousFamilleProduit.value?.id == null) { QuickAlert.show( context: context, type: QuickAlertType.info, title: 'Info', text: 'Please create the product first (fill required fields).', ); return; } final createdProduct = _createdProduct; if (createdProduct == null) { QuickAlert.show( context: context, type: QuickAlertType.loading, title: "Create product first", ); return; } try { final updater = ProductImageUpdater( baseUrl: Utils.url, ); await updater.updateProductPictureFlow(context, createdProduct); if (!mounted) return; QuickAlert.show( context: context, type: QuickAlertType.success, title: 'Image Updated', text: 'Product image captured and uploaded!', ); } catch (e) { if (!mounted) return; QuickAlert.show( context: context, type: QuickAlertType.error, title: 'Upload Failed', text: 'Could not upload image: $e', ); } } Future _showSuccessAnimation() async { await _animCtrl.forward(); if (!mounted) return; await showDialog( context: context, barrierDismissible: false, barrierColor: Colors.black.withValues(alpha: 0.15), builder: (_) => Center( child: ScaleTransition( scale: _scale, child: _SuccessBadge(primary: primary), ), ), ); await Future.delayed(const Duration(milliseconds: 420)); if (mounted) Navigator.of(context).pop(); _animCtrl.reset(); } } // ----------------- UI Pieces ----------------- class _FancyDropdown extends StatelessWidget { final String label; final T? value; final List items; final IconData icon; final void Function(T?)? onChanged; final String? Function(T?)? validator; const _FancyDropdown({ required this.label, required this.value, required this.items, required this.icon, this.onChanged, this.validator, }); @override Widget build(BuildContext context) { final border = OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ); final isDark = Theme.of(context).brightness == Brightness.dark; return DropdownButtonFormField( value: value, isExpanded: true, dropdownColor: isDark ? const Color(0xFF1E1E1E) : Colors.white.withValues(alpha: 0.95), items: items .map( (e) => DropdownMenuItem( value: e, child: Text( '$e', style: TextStyle( color: isDark ? Colors.white.withValues(alpha: 0.9) : Colors.black87, ), ), ), ) .toList(), onChanged: onChanged, validator: validator, decoration: InputDecoration( labelText: label, prefixIcon: Icon(icon, color: isDark ? Colors.white70 : Colors.black54), filled: true, fillColor: isDark ? Colors.white.withValues(alpha: 0.08) : Colors.white.withValues(alpha: 0.75), enabledBorder: border, focusedBorder: border, ), ); } } class _PrimaryButton extends StatelessWidget { final String text; final IconData icon; final MaterialColor primary; final VoidCallback? onPressed; const _PrimaryButton({ required this.text, required this.icon, required this.primary, this.onPressed, }); @override Widget build(BuildContext context) { return AnimatedOpacity( duration: const Duration(milliseconds: 180), opacity: onPressed == null ? 0.6 : 1, child: ElevatedButton.icon( icon: Icon(icon, color: Colors.white), label: Text(text, style: const TextStyle(color: Colors.white)), style: ElevatedButton.styleFrom( backgroundColor: primary, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), elevation: 8, shadowColor: primary.withValues(alpha: 0.35), ), onPressed: onPressed, ), ); } } class _CameraButton extends StatelessWidget { final MaterialColor primary; final VoidCallback onTap; const _CameraButton({required this.primary, required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: null, borderRadius: BorderRadius.circular(16), child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [primary.shade400, primary.shade900], ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: primary.withValues(alpha: 0.28), blurRadius: 16, offset: const Offset(0, 8), ), ], ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.camera_alt_rounded, color: Colors.white), ], ), ), ); } } class _SavingOverlay extends StatelessWidget { const _SavingOverlay(); @override Widget build(BuildContext context) { return IgnorePointer( child: Container( color: Colors.black.withValues(alpha: 0.05), child: const Center(child: CircularProgressIndicator()), ), ); } } class _SuccessBadge extends StatelessWidget { final MaterialColor primary; const _SuccessBadge({required this.primary}); @override Widget build(BuildContext context) { return Container( width: 132, height: 132, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient(colors: [primary.shade400, primary.shade600]), boxShadow: [ BoxShadow( color: primary.withValues(alpha: 0.35), blurRadius: 28, offset: const Offset(0, 10), ), ], ), child: Center( child: GestureDetector( onTap: () => Navigator.pop(context), child: const Icon(Icons.check_rounded, size: 64, color: Colors.white), ), ), ); } }