Skip to content

Commit ae01934

Browse files
authored
Merge pull request #470 from bounswe/feat/mobile-job-filter-lib-only
feat: improve mobile job filtering
2 parents d02e009 + 2c63fd6 commit ae01934

File tree

14 files changed

+243
-102
lines changed

14 files changed

+243
-102
lines changed

apps/jobboard-mobile/lib/core/constants/app_constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class AppConstants {
66
static String get baseUrl => _devBaseUrl;
77

88
static String _getBaseUrl() {
9-
return 'https://jobboard-backend-test-728855696411.europe-west1.run.app/api';
9+
return 'https://jobboard-backend-728855696411.europe-west1.run.app/api';
1010
//return 'https://jobboard-backend-test-728855696411.europe-west1.run.app/api';
1111
//return 'http://localhost:8080/api';
1212
//return 'http://10.0.2.2:8080/api';

apps/jobboard-mobile/lib/core/models/job_post.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class JobPost {
8484
json['remote'] ?? (throw Exception('Missing required field: remote')),
8585
inclusiveOpportunity: json['inclusiveOpportunity'] ?? false,
8686
nonProfit: json['nonProfit'] as bool?,
87-
contactInformation: json['contact'],
87+
contactInformation: json['contact'] as String?,
8888
postedDate: parseDate(json['postedDate']),
8989
minSalary: json['minSalary'] as int?,
9090
maxSalary: json['maxSalary'] as int?,

apps/jobboard-mobile/lib/core/services/api_service.dart

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,6 @@ const List<String> _availableEthicalPolicies = [
6060
'community_volunteering',
6161
'certified_b_corporation',
6262
];
63-
const List<String> _availableJobTypes = [
64-
'Full-time',
65-
'Part-time',
66-
'Contract',
67-
'Internship',
68-
];
69-
7063
/// Service for interacting with the backend API.
7164
class ApiService {
7265
final http.Client _client;
@@ -79,7 +72,6 @@ class ApiService {
7972

8073
// --- Available Filters (Consider fetching from API) ---
8174
List<String> get availableEthicalPolicies => _availableEthicalPolicies;
82-
List<String> get availableJobTypes => _availableJobTypes;
8375

8476
// --- Helper Methods ---
8577

@@ -191,7 +183,6 @@ class ApiService {
191183
int? minSalary,
192184
int? maxSalary,
193185
bool? inclusiveOpportunity,
194-
Map<String, dynamic>? additionalFilters,
195186
}) async {
196187
final queryParams = <String, dynamic>{};
197188

@@ -214,10 +205,6 @@ class ApiService {
214205
queryParams['inclusiveOpportunity'] = inclusiveOpportunity;
215206
}
216207

217-
// Add any additional filters
218-
if (additionalFilters != null) {
219-
queryParams.addAll(additionalFilters);
220-
}
221208

222209
final uri = _buildUri('/jobs', queryParams);
223210

apps/jobboard-mobile/lib/features/auth/screens/welcome_screen.dart

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,14 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
9292
Widget build(BuildContext context) {
9393
return Scaffold(
9494
body: SafeArea(
95-
child: Padding(
96-
padding: const EdgeInsets.symmetric(horizontal: 24.0),
97-
child: Column(
98-
mainAxisAlignment: MainAxisAlignment.center,
99-
children: [
100-
const Positioned(
101-
top: 16,
102-
right: 24,
103-
child: ThemeToggleSwitch(),
104-
),
105-
if (_isLoading)
95+
child: Stack(
96+
children: [
97+
Padding(
98+
padding: const EdgeInsets.symmetric(horizontal: 24.0),
99+
child: Column(
100+
mainAxisAlignment: MainAxisAlignment.center,
101+
children: [
102+
if (_isLoading)
106103
const CircularProgressIndicator()
107104
else if (_hasError)
108105
Text(
@@ -229,8 +226,15 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
229226
),
230227
),
231228
),
232-
],
233-
),
229+
],
230+
),
231+
),
232+
const Positioned(
233+
top: 16,
234+
right: 24,
235+
child: ThemeToggleSwitch(),
236+
),
237+
],
234238
),
235239
),
236240
);

apps/jobboard-mobile/lib/features/job/screens/create_job_post_screen.dart

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
3030
bool _isRemote = false;
3131
bool _isInclusiveOpportunity = false;
3232
bool _isNonProfit = false;
33+
bool _isPoliciesExpanded = false;
3334

3435
bool _isLoading = false;
3536

@@ -118,9 +119,9 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
118119
return;
119120
}
120121

121-
// Parse salaries
122+
// Parse salaries (skip if non-profit)
122123
int? minSalary;
123-
if (_minSalaryController.text.isNotEmpty) {
124+
if (!_isNonProfit && _minSalaryController.text.isNotEmpty) {
124125
minSalary = int.tryParse(
125126
_minSalaryController.text.replaceAll(RegExp(r'[^0-9]'), ''),
126127
);
@@ -138,7 +139,7 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
138139
}
139140

140141
int? maxSalary;
141-
if (_maxSalaryController.text.isNotEmpty) {
142+
if (!_isNonProfit && _maxSalaryController.text.isNotEmpty) {
142143
maxSalary = int.tryParse(
143144
_maxSalaryController.text.replaceAll(RegExp(r'[^0-9]'), ''),
144145
);
@@ -155,7 +156,7 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
155156
}
156157
}
157158

158-
if (minSalary != null && maxSalary != null && minSalary > maxSalary) {
159+
if (!_isNonProfit && minSalary != null && maxSalary != null && minSalary > maxSalary) {
159160
ScaffoldMessenger.of(context).showSnackBar(
160161
SnackBar(
161162
content: Text(
@@ -257,27 +258,16 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
257258
prefixIcon: const Icon(Icons.business),
258259
),
259260
value: _selectedWorkplaceId,
261+
isExpanded: true,
260262
items: _userWorkplaces.map((item) {
261263
return DropdownMenuItem<int>(
262264
value: item.workplace.id,
263-
child: Column(
264-
crossAxisAlignment: CrossAxisAlignment.start,
265-
mainAxisSize: MainAxisSize.min,
266-
children: [
267-
Text(
268-
item.workplace.companyName,
269-
style: const TextStyle(
270-
fontWeight: FontWeight.bold,
271-
),
272-
),
273-
Text(
274-
'${item.workplace.location} • ${item.workplace.sector}',
275-
style: TextStyle(
276-
fontSize: 12,
277-
color: Colors.grey[600],
278-
),
279-
),
280-
],
265+
child: Text(
266+
item.workplace.companyName,
267+
style: const TextStyle(
268+
fontWeight: FontWeight.w500,
269+
),
270+
overflow: TextOverflow.ellipsis,
281271
),
282272
);
283273
}).toList(),
@@ -373,6 +363,11 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
373363
HapticFeedback.lightImpact();
374364
setState(() {
375365
_isNonProfit = value ?? false;
366+
// Clear salary fields when non-profit is enabled
367+
if (_isNonProfit) {
368+
_minSalaryController.clear();
369+
_maxSalaryController.clear();
370+
}
376371
});
377372
},
378373
controlAffinity: ListTileControlAffinity.leading,
@@ -426,6 +421,7 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
426421
// --- Min Salary (Optional) ---
427422
TextFormField(
428423
controller: _minSalaryController,
424+
enabled: !_isNonProfit,
429425
decoration: InputDecoration(
430426
labelText:
431427
AppLocalizations.of(
@@ -437,12 +433,18 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
437433
)!.createJob_minSalaryPlaceholder,
438434
border: const OutlineInputBorder(),
439435
prefixText: '\$',
436+
suffixIcon: _isNonProfit
437+
? const Icon(
438+
Icons.block,
439+
color: Colors.grey,
440+
)
441+
: null,
440442
),
441443
keyboardType: const TextInputType.numberWithOptions(
442444
decimal: false,
443445
),
444446
validator: (value) {
445-
if (value != null && value.isNotEmpty) {
447+
if (!_isNonProfit && value != null && value.isNotEmpty) {
446448
final sanitizedValue = value.replaceAll(
447449
RegExp(r'[^0-9]'),
448450
'',
@@ -461,6 +463,7 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
461463
// --- Max Salary (Optional) ---
462464
TextFormField(
463465
controller: _maxSalaryController,
466+
enabled: !_isNonProfit,
464467
decoration: InputDecoration(
465468
labelText:
466469
AppLocalizations.of(
@@ -472,12 +475,18 @@ class _CreateJobPostScreenState extends State<CreateJobPostScreen> {
472475
)!.createJob_maxSalaryPlaceholder,
473476
border: const OutlineInputBorder(),
474477
prefixText: '\$',
478+
suffixIcon: _isNonProfit
479+
? const Icon(
480+
Icons.block,
481+
color: Colors.grey,
482+
)
483+
: null,
475484
),
476485
keyboardType: const TextInputType.numberWithOptions(
477486
decimal: false,
478487
),
479488
validator: (value) {
480-
if (value != null && value.isNotEmpty) {
489+
if (!_isNonProfit && value != null && value.isNotEmpty) {
481490
final sanitizedValue = value.replaceAll(
482491
RegExp(r'[^0-9]'),
483492
'',

apps/jobboard-mobile/lib/features/job/screens/job_page.dart

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ class _JobPageState extends State<JobPage> {
3737
'maxSalary': null,
3838
'isRemote': null,
3939
'inclusiveOpportunity': null,
40-
'jobTypes': <String>[],
4140
};
4241
UserType? _userRole; // Changed type to UserType?
4342

@@ -91,32 +90,20 @@ class _JobPageState extends State<JobPage> {
9190
// Pass the search query and filter map to the API service
9291
postings = await _apiService.fetchJobPostings(
9392
query: searchQuery,
94-
title: _hasActiveFilters() ? _selectedFilters['title'] : null,
95-
company: _hasActiveFilters() ? _selectedFilters['companyName'] : null,
96-
ethicalTags:
97-
_hasActiveFilters() &&
98-
(_selectedFilters['ethicalTags'] as List<String>)
99-
.isNotEmpty
100-
? (_selectedFilters['ethicalTags'] as List<String>)
101-
: null,
102-
minSalary:
103-
_hasActiveFilters() && _selectedFilters['minSalary'] != null
104-
? (_selectedFilters['minSalary'] as num).toInt()
105-
: null,
106-
maxSalary:
107-
_hasActiveFilters() && _selectedFilters['maxSalary'] != null
108-
? (_selectedFilters['maxSalary'] as num).toInt()
109-
: null,
110-
isRemote: _hasActiveFilters() ? _selectedFilters['isRemote'] : null,
93+
title: _selectedFilters['title'],
94+
company: _selectedFilters['companyName'],
95+
ethicalTags: (_selectedFilters['ethicalTags'] as List<String>).isNotEmpty
96+
? (_selectedFilters['ethicalTags'] as List<String>)
97+
: null,
98+
minSalary: _selectedFilters['minSalary'] != null
99+
? (_selectedFilters['minSalary'] as num).toInt()
100+
: null,
101+
maxSalary: _selectedFilters['maxSalary'] != null
102+
? (_selectedFilters['maxSalary'] as num).toInt()
103+
: null,
104+
isRemote: _selectedFilters['isRemote'] == true ? true : null,
111105
inclusiveOpportunity:
112-
_hasActiveFilters()
113-
? _selectedFilters['inclusiveOpportunity']
114-
: null,
115-
additionalFilters:
116-
_hasActiveFilters() &&
117-
(_selectedFilters['jobTypes'] as List<String>).isNotEmpty
118-
? {'jobTypes': _selectedFilters['jobTypes']}
119-
: null,
106+
_selectedFilters['inclusiveOpportunity'] == true ? true : null,
120107
);
121108
}
122109
if (mounted) {
@@ -147,8 +134,8 @@ class _JobPageState extends State<JobPage> {
147134
(_selectedFilters['ethicalTags'] as List<String>).isNotEmpty ||
148135
_selectedFilters['minSalary'] != null ||
149136
_selectedFilters['maxSalary'] != null ||
150-
_selectedFilters['isRemote'] != null ||
151-
(_selectedFilters['jobTypes'] as List<String>).isNotEmpty;
137+
_selectedFilters['isRemote'] == true ||
138+
_selectedFilters['inclusiveOpportunity'] == true;
152139
}
153140

154141
// --- Event Handlers ---
@@ -200,8 +187,10 @@ class _JobPageState extends State<JobPage> {
200187
filtersChanged = true;
201188
}
202189

203-
// Compare boolean filter
204-
if (_selectedFilters['isRemote'] != result['isRemote']) {
190+
// Compare boolean filters
191+
if (_selectedFilters['isRemote'] != result['isRemote'] ||
192+
_selectedFilters['inclusiveOpportunity'] !=
193+
result['inclusiveOpportunity']) {
205194
filtersChanged = true;
206195
}
207196

@@ -217,16 +206,6 @@ class _JobPageState extends State<JobPage> {
217206
filtersChanged = true;
218207
}
219208

220-
// Compare lists (jobTypes)
221-
final currentJobTypes = Set<String>.from(
222-
_selectedFilters['jobTypes'] as List<String>,
223-
);
224-
final newJobTypes = Set<String>.from(result['jobTypes'] as List<String>);
225-
if (currentJobTypes.length != newJobTypes.length ||
226-
!currentJobTypes.containsAll(newJobTypes)) {
227-
filtersChanged = true;
228-
}
229-
230209
if (filtersChanged) {
231210
print("Filters changed, reloading data with: $result");
232211
setState(() {

0 commit comments

Comments
 (0)