432 lines
21 KiB
Dart
432 lines
21 KiB
Dart
import 'package:fbla_ui/api_logic.dart';
|
|
import 'package:fbla_ui/main.dart';
|
|
import 'package:fbla_ui/shared.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, required this.inputBusiness});
|
|
|
|
@override
|
|
State<CreateEditJobListing> createState() => _CreateEditJobListingState();
|
|
}
|
|
|
|
class _CreateEditJobListingState extends State<CreateEditJobListing> {
|
|
late Future getBusinessNameMapping;
|
|
late TextEditingController _nameController;
|
|
late TextEditingController _descriptionController;
|
|
late TextEditingController _wageController;
|
|
late TextEditingController _linkController;
|
|
List nameMapping = [];
|
|
String? businessErrorText;
|
|
|
|
JobListing listing = JobListing(
|
|
id: null,
|
|
businessId: null,
|
|
name: 'Job Listing',
|
|
description: 'Add details about the business below.',
|
|
type: JobType.other,
|
|
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);
|
|
getBusinessNameMapping = fetchBusinessNames();
|
|
}
|
|
|
|
final formKey = GlobalKey<FormState>();
|
|
final TextEditingController jobTypeController = TextEditingController();
|
|
final TextEditingController businessController = TextEditingController();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
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 (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()));
|
|
}
|
|
} 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: 1000,
|
|
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 getIconFromJobType(
|
|
listing.type,
|
|
48,
|
|
Theme.of(context)
|
|
.colorScheme
|
|
.onSurface);
|
|
}),
|
|
),
|
|
),
|
|
// 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<JobType>(
|
|
initialSelection: listing.type,
|
|
controller: jobTypeController,
|
|
label: const Text('Job Type'),
|
|
dropdownMenuEntries: [
|
|
for (JobType type
|
|
in JobType.values)
|
|
DropdownMenuEntry(
|
|
value: type,
|
|
label:
|
|
getNameFromJobType(
|
|
type))
|
|
],
|
|
onSelected: (inputType) {
|
|
setState(() {
|
|
listing.type = inputType!;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
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<int>(
|
|
initialSelection:
|
|
widget.inputBusiness.id,
|
|
controller: businessController,
|
|
label: const Text('Business'),
|
|
dropdownMenuEntries: [
|
|
for (Map<String, dynamic> map
|
|
in nameMapping)
|
|
DropdownMenuEntry(
|
|
value: map['id']!,
|
|
label: map['name'])
|
|
],
|
|
onSelected: (inputType) {
|
|
setState(() {
|
|
listing.businessId =
|
|
inputType!;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
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 (listing.link != null &&
|
|
listing.link != '') {
|
|
listing.link =
|
|
Uri.encodeFull(inputUrl);
|
|
if (!listing.link!
|
|
.contains('http://') &&
|
|
!listing.link!
|
|
.contains('https://')) {
|
|
listing.link =
|
|
'https://${listing.link}';
|
|
}
|
|
}
|
|
},
|
|
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.'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|