more fixes and features
This commit is contained in:
parent
03abc1191d
commit
b860ae52f6
@ -176,48 +176,58 @@ void main() async {
|
|||||||
request.url.queryParameters['offerFilters']?.split(',') ??
|
request.url.queryParameters['offerFilters']?.split(',') ??
|
||||||
OfferType.values.asNameMap().keys;
|
OfferType.values.asNameMap().keys;
|
||||||
|
|
||||||
Map<String, dynamic> output = {};
|
var postgresResult = (await postgres.query('''
|
||||||
|
WITH business_listings AS (
|
||||||
for (int i = 0; i < typeFilters.length; i++) {
|
SELECT b.id AS business_id,
|
||||||
var postgresResult = (await postgres.query('''
|
b.name AS business_name,
|
||||||
SELECT json_agg(
|
b."contactName" AS business_contactName,
|
||||||
json_build_object(
|
b."contactEmail" AS business_contactEmail,
|
||||||
'id', b.id,
|
b."contactPhone" AS business_contactPhone,
|
||||||
'name', b.name,
|
b."locationName" AS business_locationName,
|
||||||
'contactName', b."contactName",
|
b."locationAddress" AS business_locationAddress,
|
||||||
'contactEmail', b."contactEmail",
|
l.type AS listing_type,
|
||||||
'contactPhone', b."contactPhone",
|
json_agg(
|
||||||
'locationName', b."locationName",
|
json_build_object(
|
||||||
'locationAddress', b."locationAddress",
|
'id', l.id,
|
||||||
'listings', (
|
'businessId', l."businessId",
|
||||||
SELECT json_agg(
|
'name', l.name,
|
||||||
json_build_object(
|
'description', l.description,
|
||||||
'id', l.id,
|
'type', l.type,
|
||||||
'name', l.name,
|
'offerType', l."offerType",
|
||||||
'description', l.description,
|
'wage', l.wage,
|
||||||
'type', l.type,
|
'link', l.link
|
||||||
'offerType', l."offerType",
|
|
||||||
'wage', l.wage,
|
|
||||||
'link', l.link
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
FROM listings l
|
) AS listings
|
||||||
WHERE l."businessId" = b.id AND l.type = '${typeFilters.elementAt(i)}' AND l."offerType" IN (${offerFilters.map((element) => "'$element'").join(',')})
|
FROM businesses b
|
||||||
)
|
JOIN listings l ON b.id = l."businessId"
|
||||||
|
WHERE l.type IN (${typeFilters.map((element) => "'$element'").join(',')})
|
||||||
|
AND l."offerType" IN (${offerFilters.map((element) => "'$element'").join(',')})
|
||||||
|
GROUP BY b.id, b.name, b."contactName", b."contactEmail", b."contactPhone",
|
||||||
|
b."locationName", b."locationAddress", l.type
|
||||||
|
),
|
||||||
|
aggregated_businesses AS (
|
||||||
|
SELECT listing_type,
|
||||||
|
json_agg(
|
||||||
|
json_build_object(
|
||||||
|
'id', business_id,
|
||||||
|
'name', business_name,
|
||||||
|
'contactName', business_contactName,
|
||||||
|
'contactEmail', business_contactEmail,
|
||||||
|
'contactPhone', business_contactPhone,
|
||||||
|
'locationName', business_locationName,
|
||||||
|
'locationAddress', business_locationAddress,
|
||||||
|
'listings', listings
|
||||||
|
)
|
||||||
|
) AS businesses
|
||||||
|
FROM business_listings
|
||||||
|
GROUP BY listing_type
|
||||||
)
|
)
|
||||||
)
|
SELECT jsonb_object_agg(listing_type, businesses) AS result
|
||||||
FROM businesses b
|
FROM aggregated_businesses;
|
||||||
WHERE b.id IN (SELECT "businessId" FROM public.listings WHERE type='${typeFilters.elementAt(i)}' AND "offerType" IN (${offerFilters.map((element) => "'$element'").join(',')}))
|
|
||||||
GROUP BY b.id;
|
|
||||||
'''));
|
'''));
|
||||||
|
|
||||||
if (postgresResult.isNotEmpty) {
|
|
||||||
output.addAll({typeFilters.elementAt(i): postgresResult[0][0]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok(
|
return Response.ok(
|
||||||
json.encode(output),
|
json.encode(postgresResult[0][0]),
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
@ -311,9 +321,9 @@ void main() async {
|
|||||||
'name', l.name,
|
'name', l.name,
|
||||||
'description', l.description,
|
'description', l.description,
|
||||||
'type', l.type,
|
'type', l.type,
|
||||||
|
'offerType', l."offerType",
|
||||||
'wage', l.wage,
|
'wage', l.wage,
|
||||||
'link', l.link,
|
'link', l.link
|
||||||
'offerType', l."offerType"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
END
|
END
|
||||||
|
|||||||
@ -225,25 +225,22 @@ class _BusinessesOverviewState extends State<BusinessesOverview> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Padding> chips = [];
|
List<Widget> chips = [];
|
||||||
for (var type in BusinessType.values) {
|
for (var type in BusinessType.values) {
|
||||||
chips.add(Padding(
|
chips.add(FilterChip(
|
||||||
padding: const EdgeInsets.all(4),
|
showCheckmark: false,
|
||||||
child: FilterChip(
|
shape: RoundedRectangleBorder(
|
||||||
showCheckmark: false,
|
borderRadius: BorderRadius.circular(20)),
|
||||||
shape: RoundedRectangleBorder(
|
label: Text(getNameFromBusinessType(type)),
|
||||||
borderRadius: BorderRadius.circular(20)),
|
selected: selectedChips.contains(type),
|
||||||
label: Text(getNameFromBusinessType(type)),
|
onSelected: (bool selected) {
|
||||||
selected: selectedChips.contains(type),
|
if (selected) {
|
||||||
onSelected: (bool selected) {
|
selectedChips.add(type);
|
||||||
if (selected) {
|
} else {
|
||||||
selectedChips.add(type);
|
selectedChips.remove(type);
|
||||||
} else {
|
}
|
||||||
selectedChips.remove(type);
|
setDialogState(filters);
|
||||||
}
|
}));
|
||||||
setDialogState(filters);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
@ -251,6 +248,8 @@ class _BusinessesOverviewState extends State<BusinessesOverview> {
|
|||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 400,
|
width: 400,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
children: chips,
|
children: chips,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -141,8 +141,18 @@ class _CreateEditJobListingState extends State<CreateEditJobListing> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
subtitle: Text(
|
subtitle: Column(
|
||||||
listing.description,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
getNameFromJobType(listing.type!),
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
listing.description,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.only(
|
contentPadding: const EdgeInsets.only(
|
||||||
bottom: 8, left: 16),
|
bottom: 8, left: 16),
|
||||||
|
|||||||
@ -45,27 +45,53 @@ class _CreateBusinessDetailState extends State<JobListingDetail> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
|
minVerticalPadding: 0,
|
||||||
titleAlignment: ListTileTitleAlignment.titleHeight,
|
titleAlignment: ListTileTitleAlignment.titleHeight,
|
||||||
title: Text(listing.name,
|
title: Padding(
|
||||||
style: const TextStyle(
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
fontSize: 24, fontWeight: FontWeight.bold)),
|
child: Text(
|
||||||
subtitle: Text(
|
'${listing.name} (${getNameFromOfferType(listing.offerType!)})',
|
||||||
listing.description,
|
style: const TextStyle(
|
||||||
|
fontSize: 24, fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
getNameFromJobType(listing.type!),
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
listing.description,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.only(bottom: 8, left: 16),
|
const EdgeInsets.only(bottom: 8, left: 16),
|
||||||
leading: ClipRRect(
|
leading: Badge(
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
label: Text(
|
||||||
child: Image.network(
|
getLetterFromOfferType(listing.offerType!),
|
||||||
'$apiAddress/logos/${listing.businessId}',
|
style: const TextStyle(fontSize: 16),
|
||||||
width: 48,
|
),
|
||||||
height: 48, errorBuilder: (BuildContext context,
|
largeSize: 26,
|
||||||
Object exception, StackTrace? stackTrace) {
|
offset: const Offset(15, -5),
|
||||||
return Icon(
|
textColor: Colors.white,
|
||||||
getIconFromJobType(
|
backgroundColor:
|
||||||
listing.type ?? JobType.other),
|
getColorFromOfferType(listing.offerType!),
|
||||||
size: 48);
|
child: ClipRRect(
|
||||||
}),
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
|
child: Image.network(
|
||||||
|
'$apiAddress/logos/${listing.businessId}',
|
||||||
|
width: 48,
|
||||||
|
height: 48, errorBuilder:
|
||||||
|
(BuildContext context, Object exception,
|
||||||
|
StackTrace? stackTrace) {
|
||||||
|
return Icon(
|
||||||
|
getIconFromJobType(
|
||||||
|
listing.type ?? JobType.other),
|
||||||
|
size: 48);
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (listing.link != null && listing.link != '')
|
if (listing.link != null && listing.link != '')
|
||||||
@ -87,16 +113,14 @@ class _CreateBusinessDetailState extends State<JobListingDetail> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Wage
|
// Wage
|
||||||
Visibility(
|
if (listing.wage != null && listing.wage != '')
|
||||||
visible: listing.wage != null && listing.wage != '',
|
Card(
|
||||||
child: Card(
|
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.attach_money),
|
leading: const Icon(Icons.attach_money),
|
||||||
subtitle: Text(listing.wage!),
|
subtitle: Text(listing.wage!),
|
||||||
title: const Text('Wage Information'),
|
title: const Text('Wage Information'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Card(
|
Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@ -242,46 +242,40 @@ class _JobsOverviewState extends State<JobsOverview> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Padding> jobTypeChips = [];
|
List<Widget> jobTypeChips = [];
|
||||||
for (JobType type in JobType.values) {
|
for (JobType type in JobType.values) {
|
||||||
jobTypeChips.add(Padding(
|
jobTypeChips.add(FilterChip(
|
||||||
padding: const EdgeInsets.all(4),
|
showCheckmark: false,
|
||||||
child: FilterChip(
|
shape: RoundedRectangleBorder(
|
||||||
showCheckmark: false,
|
borderRadius: BorderRadius.circular(20)),
|
||||||
shape: RoundedRectangleBorder(
|
label: Text(getNameFromJobType(type)),
|
||||||
borderRadius: BorderRadius.circular(20)),
|
selected: selectedJobTypeChips.contains(type),
|
||||||
label: Text(getNameFromJobType(type)),
|
onSelected: (bool selected) {
|
||||||
selected: selectedJobTypeChips.contains(type),
|
if (selected) {
|
||||||
onSelected: (bool selected) {
|
selectedJobTypeChips.add(type);
|
||||||
if (selected) {
|
} else {
|
||||||
selectedJobTypeChips.add(type);
|
selectedJobTypeChips.remove(type);
|
||||||
} else {
|
}
|
||||||
selectedJobTypeChips.remove(type);
|
setDialogState(selectedJobTypeChips, null);
|
||||||
}
|
}));
|
||||||
setDialogState(selectedJobTypeChips, null);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Padding> offerTypeChips = [];
|
List<Widget> offerTypeChips = [];
|
||||||
for (OfferType type in OfferType.values) {
|
for (OfferType type in OfferType.values) {
|
||||||
offerTypeChips.add(Padding(
|
offerTypeChips.add(FilterChip(
|
||||||
padding: const EdgeInsets.all(4),
|
showCheckmark: false,
|
||||||
child: FilterChip(
|
shape: RoundedRectangleBorder(
|
||||||
showCheckmark: false,
|
borderRadius: BorderRadius.circular(20)),
|
||||||
shape: RoundedRectangleBorder(
|
label: Text(getNameFromOfferType(type)),
|
||||||
borderRadius: BorderRadius.circular(20)),
|
selected: selectedOfferTypeChips.contains(type),
|
||||||
label: Text(getNameFromOfferType(type)),
|
onSelected: (bool selected) {
|
||||||
selected: selectedOfferTypeChips.contains(type),
|
if (selected) {
|
||||||
onSelected: (bool selected) {
|
selectedOfferTypeChips.add(type);
|
||||||
if (selected) {
|
} else {
|
||||||
selectedOfferTypeChips.add(type);
|
selectedOfferTypeChips.remove(type);
|
||||||
} else {
|
}
|
||||||
selectedOfferTypeChips.remove(type);
|
setDialogState(null, selectedOfferTypeChips);
|
||||||
}
|
}));
|
||||||
setDialogState(null, selectedOfferTypeChips);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
@ -295,6 +289,8 @@ class _JobsOverviewState extends State<JobsOverview> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
children: jobTypeChips,
|
children: jobTypeChips,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -302,6 +298,8 @@ class _JobsOverviewState extends State<JobsOverview> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
children: offerTypeChips,
|
children: offerTypeChips,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -515,8 +513,9 @@ class _JobHeaderState extends State<_JobHeader> {
|
|||||||
),
|
),
|
||||||
largeSize: 26,
|
largeSize: 26,
|
||||||
offset: const Offset(15, -5),
|
offset: const Offset(15, -5),
|
||||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
textColor: Colors.white,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: getColorFromOfferType(
|
||||||
|
business.listings![0].offerType!),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
|
|||||||
@ -87,31 +87,28 @@ class _FilterBusinessDataTypeChipsState
|
|||||||
extends State<_FilterBusinessDataTypeChips> {
|
extends State<_FilterBusinessDataTypeChips> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<Padding> chips = [];
|
List<Widget> chips = [];
|
||||||
for (var type in DataTypeBusiness.values) {
|
for (var type in DataTypeBusiness.values) {
|
||||||
chips.add(Padding(
|
chips.add(FilterChip(
|
||||||
padding:
|
shape: RoundedRectangleBorder(
|
||||||
const EdgeInsets.only(left: 3.0, right: 3.0, bottom: 3.0, top: 3.0),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: FilterChip(
|
side: BorderSide(color: Theme.of(context).colorScheme.secondary)),
|
||||||
shape: RoundedRectangleBorder(
|
label: Text(dataTypeFriendlyBusiness[type]!),
|
||||||
borderRadius: BorderRadius.circular(20),
|
showCheckmark: false,
|
||||||
side:
|
selected: widget.selectedDataTypesBusiness.contains(type),
|
||||||
BorderSide(color: Theme.of(context).colorScheme.secondary)),
|
onSelected: (bool selected) {
|
||||||
label: Text(dataTypeFriendlyBusiness[type]!),
|
setState(() {
|
||||||
showCheckmark: false,
|
if (selected) {
|
||||||
selected: widget.selectedDataTypesBusiness.contains(type),
|
widget.selectedDataTypesBusiness.add(type);
|
||||||
onSelected: (bool selected) {
|
} else {
|
||||||
setState(() {
|
widget.selectedDataTypesBusiness.remove(type);
|
||||||
if (selected) {
|
}
|
||||||
widget.selectedDataTypesBusiness.add(type);
|
});
|
||||||
} else {
|
}));
|
||||||
widget.selectedDataTypesBusiness.remove(type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return Wrap(
|
return Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
children: chips,
|
children: chips,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -155,6 +152,8 @@ class _FilterJobDataTypeChipsState extends State<_FilterJobDataTypeChips> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
return Wrap(
|
return Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
children: chips,
|
children: chips,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,12 @@ enum DataTypeBusiness {
|
|||||||
logo,
|
logo,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
type,
|
||||||
website,
|
website,
|
||||||
contactName,
|
contactName,
|
||||||
contactEmail,
|
contactEmail,
|
||||||
contactPhone,
|
contactPhone,
|
||||||
notes,
|
notes,
|
||||||
type,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DataTypeJob {
|
enum DataTypeJob {
|
||||||
@ -42,19 +42,20 @@ class JobListing {
|
|||||||
String name;
|
String name;
|
||||||
String description;
|
String description;
|
||||||
JobType? type;
|
JobType? type;
|
||||||
|
OfferType? offerType;
|
||||||
String? wage;
|
String? wage;
|
||||||
String? link;
|
String? link;
|
||||||
OfferType? offerType;
|
|
||||||
|
|
||||||
JobListing(
|
JobListing({
|
||||||
{this.id,
|
this.id,
|
||||||
this.businessId,
|
this.businessId,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
required this.description,
|
||||||
this.type,
|
this.type,
|
||||||
this.wage,
|
this.offerType,
|
||||||
this.link,
|
this.wage,
|
||||||
this.offerType});
|
this.link,
|
||||||
|
});
|
||||||
|
|
||||||
factory JobListing.copy(JobListing input) {
|
factory JobListing.copy(JobListing input) {
|
||||||
return JobListing(
|
return JobListing(
|
||||||
@ -63,9 +64,9 @@ class JobListing {
|
|||||||
name: input.name,
|
name: input.name,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
type: input.type,
|
type: input.type,
|
||||||
|
offerType: input.offerType,
|
||||||
wage: input.wage,
|
wage: input.wage,
|
||||||
link: input.link,
|
link: input.link,
|
||||||
offerType: input.offerType,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,6 +262,17 @@ String getLetterFromOfferType(OfferType type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color getColorFromOfferType(OfferType type) {
|
||||||
|
switch (type) {
|
||||||
|
case OfferType.job:
|
||||||
|
return Colors.blue;
|
||||||
|
case OfferType.internship:
|
||||||
|
return Colors.green.shade800;
|
||||||
|
case OfferType.apprenticeship:
|
||||||
|
return Colors.red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IconData getIconFromThemeMode(ThemeMode theme) {
|
IconData getIconFromThemeMode(ThemeMode theme) {
|
||||||
switch (theme) {
|
switch (theme) {
|
||||||
case ThemeMode.dark:
|
case ThemeMode.dark:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user