import 'package:fbla_ui/main.dart'; import 'package:fbla_ui/shared/api_logic.dart'; import 'package:fbla_ui/shared/utils.dart'; import 'package:flutter/material.dart'; import 'package:rive/rive.dart'; class CreateEditJobListing extends StatefulWidget { final JobListing? inputJobListing; final Business? inputBusiness; const CreateEditJobListing( {super.key, this.inputJobListing, this.inputBusiness}); @override State createState() => _CreateEditJobListingState(); } class _CreateEditJobListingState extends State { late Future getBusinessNameMapping; late TextEditingController _nameController; late TextEditingController _descriptionController; late TextEditingController _wageController; late TextEditingController _linkController; List nameMapping = []; String? typeDropdownErrorText; String? businessDropdownErrorText; JobListing listing = JobListing( id: null, businessId: null, name: 'Job Listing', description: 'Add details about the business below.', type: null, wage: null, link: null); bool _isLoading = false; @override void initState() { super.initState(); if (widget.inputJobListing != null) { listing = JobListing.copy(widget.inputJobListing!); _nameController = TextEditingController(text: listing.name); _descriptionController = TextEditingController(text: listing.description); } else { _nameController = TextEditingController(); _descriptionController = TextEditingController(); } _wageController = TextEditingController(text: listing.wage); _linkController = TextEditingController( text: listing.link ?.replaceAll('https://', '') .replaceAll('http://', '') .replaceAll('www.', '')); getBusinessNameMapping = fetchBusinessNames(); } final formKey = GlobalKey(); @override Widget build(BuildContext context) { if (widget.inputBusiness != null) { listing.businessId = widget.inputBusiness!.id; } return PopScope( canPop: !_isLoading, onPopInvoked: _handlePop, child: Form( key: formKey, child: Scaffold( appBar: AppBar( title: (widget.inputJobListing != null) ? Text('Edit ${widget.inputJobListing?.name}', maxLines: 1) : const Text('Add New Job Listing'), ), floatingActionButton: FloatingActionButton( child: _isLoading ? const Padding( padding: EdgeInsets.all(16.0), child: CircularProgressIndicator( color: Colors.white, strokeWidth: 3.0, ), ) : const Icon(Icons.save), onPressed: () async { if (listing.type == null || listing.businessId == null) { if (listing.type == null) { setState(() { typeDropdownErrorText = 'Job type is required'; }); formKey.currentState!.validate(); } if (listing.businessId == null) { setState(() { businessDropdownErrorText = 'Business is required'; }); formKey.currentState!.validate(); } } else { setState(() { typeDropdownErrorText = null; businessDropdownErrorText = null; }); if (formKey.currentState!.validate()) { formKey.currentState?.save(); setState(() { _isLoading = true; }); String? result; if (widget.inputJobListing != null) { result = await editListing(listing); } else { result = await createListing(listing); } setState(() { _isLoading = false; }); if (result != null) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( width: 400, behavior: SnackBarBehavior.floating, content: Text(result))); } else { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const MainApp( initialPage: 1, ))); } } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Check field inputs!'), width: 200, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), ); } } }), body: FutureBuilder( future: getBusinessNameMapping, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { if (snapshot.data.runtimeType == String) { return Padding( padding: const EdgeInsets.all(16.0), child: Column(children: [ Center( child: Text(snapshot.data, textAlign: TextAlign.center)), Padding( padding: const EdgeInsets.all(8.0), child: FilledButton( child: const Text('Retry'), onPressed: () async { var refreshedData = fetchBusinessNames(); await refreshedData; setState(() { getBusinessNameMapping = refreshedData; }); }, ), ), ]), ); } nameMapping = snapshot.data; return ListView( children: [ Center( child: SizedBox( width: 800, child: Column( children: [ ListTile( title: Text(listing.name, textAlign: TextAlign.left, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold)), subtitle: Text( listing.description, textAlign: TextAlign.left, ), leading: ClipRRect( borderRadius: BorderRadius.circular(6.0), child: Image.network( width: 48, height: 48, listing.businessId != null ? '$apiAddress/logos/${listing.businessId}' : '', errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { return Icon( getIconFromJobType( listing.type ?? JobType.other, ), size: 48); }), ), ), // Business Type Dropdown Card( child: Column( children: [ Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 8.0, top: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Type of Job', style: TextStyle(fontSize: 16)), DropdownMenu( initialSelection: listing.type, label: const Text('Job Type'), errorText: typeDropdownErrorText, dropdownMenuEntries: [ for (JobType type in JobType.values) DropdownMenuEntry( value: type, label: getNameFromJobType( type)) ], onSelected: (inputType) { setState(() { listing.type = inputType!; typeDropdownErrorText = null; }); }, ), ], ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 8.0, top: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Business that has the job', style: TextStyle(fontSize: 16)), DropdownMenu( errorText: businessDropdownErrorText, initialSelection: widget.inputBusiness?.id, label: const Text('Business'), dropdownMenuEntries: [ for (Map map in nameMapping) DropdownMenuEntry( value: map['id']!, label: map['name']) ], onSelected: (inputType) { setState(() { listing.businessId = inputType!; businessDropdownErrorText = null; }); }, ), ], ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0), child: TextFormField( controller: _nameController, autovalidateMode: AutovalidateMode .onUserInteraction, maxLength: 30, onChanged: (inputName) { setState(() { listing.name = inputName; }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Job Listing Name (required)', ), validator: (value) { if (value != null && value.isEmpty) { return 'Name is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0), child: TextFormField( controller: _descriptionController, autovalidateMode: AutovalidateMode .onUserInteraction, maxLength: 500, maxLines: null, onChanged: (inputDesc) { setState(() { listing.description = inputDesc; }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Job Listing Description (required)', ), validator: (value) { if (value != null && value.isEmpty) { return 'Description is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 8.0), child: TextFormField( controller: _wageController, onChanged: (input) { setState(() { listing.wage = input; }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Wage Information', ), ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 8.0), child: TextFormField( controller: _linkController, autovalidateMode: AutovalidateMode .onUserInteraction, keyboardType: TextInputType.url, onChanged: (inputUrl) { if (inputUrl != '') { listing.link = Uri.encodeFull(inputUrl); if (!listing.link! .contains('http://') && !listing.link! .contains('https://')) { listing.link = 'https://${listing.link}'; } } listing.link = null; }, validator: (value) { if (value != null && value.isNotEmpty && !RegExp(r'(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(?:\/[^\/\s]*)*') .hasMatch(value)) { return 'Enter a valid Website'; } return null; }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Additional Information Link', ), ), ), ], ), ), const SizedBox( height: 75, ) ], ), ), ), ], ); } else if (snapshot.hasError) { return Padding( padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: Text( 'Error when loading data! Error: ${snapshot.error}'), ); } } else if (snapshot.connectionState == ConnectionState.waiting) { return Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.center, child: const SizedBox( width: 75, height: 75, child: RiveAnimation.asset( 'assets/mdev_triangle_loading.riv'), ), ); } return const Padding( padding: EdgeInsets.only(left: 16.0, right: 16.0), child: Text('Error when loading data!'), ); }), )), ); } void _handlePop(bool didPop) { if (!didPop) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( width: 400, behavior: SnackBarBehavior.floating, content: Text('Please wait for it to save.'), ), ); } } }