sr-npm 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/update-sites.yml +17 -17
- package/backend/careersMultiBoxesPageIds.js +77 -0
- package/backend/collectionConsts.js +43 -1
- package/backend/consts.js +68 -14
- package/backend/data.js +164 -29
- package/backend/fetchPositionsFromSRAPI.js +2 -1
- package/backend/index.js +2 -0
- package/backend/queries.js +9 -5
- package/backend/secretsData.js +3 -3
- package/backend/utils.js +2 -1
- package/eslint.config.mjs +26 -0
- package/package.json +8 -3
- package/pages/boardPeoplePage.js +28 -0
- package/pages/brandPage.js +12 -0
- package/pages/careersMultiBoxesPage.js +622 -0
- package/pages/careersPage.js +120 -102
- package/pages/homePage.js +79 -8
- package/pages/index.js +6 -0
- package/pages/masterPage.js +35 -0
- package/pages/pagesUtils.js +241 -0
- package/pages/positionPage.js +93 -7
- package/pages/supportTeamsPage.js +92 -0
- package/public/selectors.js +53 -0
- package/public/utils.js +2 -1
- package/tests/brandPageTest.spec.js +45 -0
- package/tests/mockJobBuilder.js +290 -0
- package/tests/multiSearchBoxCareers.spec.js +394 -0
- package/tests/positionPageTest.spec.js +140 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
const { COLLECTIONS,CUSTOM_VALUES_COLLECTION_FIELDS,JOBS_COLLECTION_FIELDS } = require('../backend/collectionConsts');
|
|
2
|
+
|
|
3
|
+
const { queryParams,onChange} = require('wix-location-frontend');
|
|
4
|
+
const { location } = require("@wix/site-location");
|
|
5
|
+
const {CAREERS_MULTI_BOXES_PAGE_CONSTS,FiltersIds,fieldTitlesInCMS,possibleUrlParams} = require('../backend/careersMultiBoxesPageIds');
|
|
6
|
+
const { groupValuesByField, debounce, getAllRecords, getFieldById, getFieldByTitle,getCorrectOption,getOptionIndexFromCheckBox,loadPrimarySearchRepeater,bindPrimarySearch,primarySearch } = require('./pagesUtils');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
let dontUpdateThisCheckBox;
|
|
10
|
+
const selectedByField = new Map(); // fieldId -> array of selected value IDs
|
|
11
|
+
const optionsByFieldId = new Map(); // fieldId -> [{label, value}] array of objects with label which is the valueLabel and value which is the valueId
|
|
12
|
+
const countsByFieldId = new Map(); // fieldId -> {valueId: count} map of counts for each valueId
|
|
13
|
+
let allfields=[] // all fields in the database
|
|
14
|
+
let alljobs=[] // all jobs in the database
|
|
15
|
+
let allvaluesobjects=[] // all values in the database
|
|
16
|
+
let valueToJobs={} // valueId -> array of jobIds
|
|
17
|
+
let currentJobs=[] // current jobs that are displayed in the jobs repeater
|
|
18
|
+
let allsecondarySearchJobs=[] // secondary search results that are displayed in the jobs repeater
|
|
19
|
+
let currentSecondarySearchJobs=[] // current secondary search results that are displayed in the jobs repeater
|
|
20
|
+
let secondarySearchIsFilled=false // whether the secondary search is filled with results
|
|
21
|
+
let keywordAllJobs; // all jobs that are displayed in the jobs repeater when the keyword is filled
|
|
22
|
+
const pagination = {
|
|
23
|
+
pageSize: 10,
|
|
24
|
+
currentPage: 1,
|
|
25
|
+
};
|
|
26
|
+
async function careersMultiBoxesPageOnReady(_$w,urlParams) {
|
|
27
|
+
//to handle back and forth , url changes
|
|
28
|
+
onChange(async ()=>{
|
|
29
|
+
await handleBackAndForth(_$w);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await loadData(_$w);
|
|
33
|
+
loadJobsRepeater(_$w);
|
|
34
|
+
loadPrimarySearchRepeater(_$w);
|
|
35
|
+
await loadFilters(_$w);
|
|
36
|
+
loadSelectedValuesRepeater(_$w);
|
|
37
|
+
bindSearchInput(_$w);
|
|
38
|
+
loadPaginationButtons(_$w);
|
|
39
|
+
|
|
40
|
+
if (await window.formFactor() === "Mobile") {
|
|
41
|
+
handleFilterInMobile(_$w);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await handleUrlParams(_$w, urlParams);
|
|
45
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.CLEAR_ALL_BUTTON_ID).onClick(async () => {
|
|
46
|
+
await clearAll(_$w);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function handleBackAndForth(_$w){
|
|
52
|
+
const newQueryParams=await location.query();
|
|
53
|
+
console.log("newQueryParams: ", newQueryParams);
|
|
54
|
+
await clearAll(_$w,true);
|
|
55
|
+
await handleUrlParams(_$w,newQueryParams);
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function clearAll(_$w,urlOnChange=false) {
|
|
60
|
+
if(selectedByField.size>0 || _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SECONDARY_SEARCH_INPUT).value || _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PRIMARY_SEARCH_INPUT).value) {
|
|
61
|
+
for(const field of allfields) {
|
|
62
|
+
_$w(`#${FiltersIds[field.title]}CheckBox`).selectedIndices = [];
|
|
63
|
+
}
|
|
64
|
+
selectedByField.clear();
|
|
65
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SECONDARY_SEARCH_INPUT).value='';
|
|
66
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PRIMARY_SEARCH_INPUT).value='';
|
|
67
|
+
secondarySearchIsFilled=false;
|
|
68
|
+
currentJobs=alljobs;
|
|
69
|
+
keywordAllJobs=undefined;
|
|
70
|
+
if(!urlOnChange) {
|
|
71
|
+
queryParams.remove(possibleUrlParams.concat(["keyword", "page"]));
|
|
72
|
+
}
|
|
73
|
+
await updateJobsAndNumbersAndFilters(_$w,true);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleFilterInMobile(_$w) {
|
|
78
|
+
const searchResultsSelectors = [
|
|
79
|
+
CAREERS_PAGE_SELECTORS.RESULT_BOX,
|
|
80
|
+
CAREERS_PAGE_SELECTORS.PAGINATION_BTN,
|
|
81
|
+
CAREERS_PAGE_SELECTORS.HEAD_BTNS,
|
|
82
|
+
CAREERS_PAGE_SELECTORS.SELECTED_VALUES_REPEATER,
|
|
83
|
+
CAREERS_PAGE_SELECTORS.BUTTOM_TXT,
|
|
84
|
+
CAREERS_PAGE_SELECTORS.SECTION_24,
|
|
85
|
+
CAREERS_PAGE_SELECTORS.SECTION_3,
|
|
86
|
+
CAREERS_PAGE_SELECTORS.LINE_3,
|
|
87
|
+
CAREERS_PAGE_SELECTORS.FILTER_ICON];
|
|
88
|
+
|
|
89
|
+
_$w(CAREERS_PAGE_SELECTORS.FILTER_ICON).onClick(()=>{
|
|
90
|
+
_$w(CAREERS_PAGE_SELECTORS.FILTER_BOX).expand();
|
|
91
|
+
searchResultsSelectors.forEach(selector => {
|
|
92
|
+
_$w(selector).collapse();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const exitFilterBox = () => {
|
|
97
|
+
_$w(CAREERS_PAGE_SELECTORS.FILTER_BOX).collapse();
|
|
98
|
+
searchResultsSelectors.forEach(selector => {
|
|
99
|
+
_$w(selector).expand();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_$w(CAREERS_PAGE_SELECTORS.EXIT_BUTTON).onClick(()=>{
|
|
104
|
+
exitFilterBox();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
_$w(CAREERS_PAGE_SELECTORS.REFINE_SEARCH_BUTTON).onClick(()=>{
|
|
108
|
+
exitFilterBox();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async function handleUrlParams(_$w,urlParams) {
|
|
114
|
+
try {
|
|
115
|
+
let applyFiltering=false;
|
|
116
|
+
let currentApplyFilterFlag=false;
|
|
117
|
+
//apply this first to determine all jobs
|
|
118
|
+
if(urlParams.keyword) {
|
|
119
|
+
applyFiltering=await primarySearch(_$w, decodeURIComponent(urlParams.keyword), alljobs);
|
|
120
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PRIMARY_SEARCH_INPUT).value=decodeURIComponent(urlParams.keyword);
|
|
121
|
+
currentJobs=_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOB_RESULTS_REPEATER).data;
|
|
122
|
+
keywordAllJobs=_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOB_RESULTS_REPEATER).data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const url of possibleUrlParams)
|
|
126
|
+
{
|
|
127
|
+
if(urlParams[url])
|
|
128
|
+
{
|
|
129
|
+
currentApplyFilterFlag=await handleParams(_$w,url,urlParams[url])
|
|
130
|
+
if(currentApplyFilterFlag) {
|
|
131
|
+
applyFiltering=true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
currentApplyFilterFlag=false;
|
|
135
|
+
}
|
|
136
|
+
if(applyFiltering || keywordAllJobs) {
|
|
137
|
+
await updateJobsAndNumbersAndFilters(_$w);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if(urlParams.page) {
|
|
141
|
+
if(Number.isNaN(Number(urlParams.page)) || Number(urlParams.page)<=1 || Number(urlParams.page)>Math.ceil(currentJobs.length/pagination.pageSize)) {
|
|
142
|
+
console.warn("page number is invalid, removing page from url");
|
|
143
|
+
queryParams.remove(["page"]);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
pagination.currentPage=Number(urlParams.page);
|
|
147
|
+
//let paginationCurrentText=urlParams.page;
|
|
148
|
+
let startSlicIndex=pagination.pageSize*(pagination.currentPage-1);
|
|
149
|
+
let endSlicIndex=(pagination.pageSize)*(pagination.currentPage);
|
|
150
|
+
if(Number(urlParams.page)==Math.ceil(currentJobs.length/pagination.pageSize)) {
|
|
151
|
+
// paginationCurrentText=(paginationCurrentText-(pagination.pageSize-(currentJobs.length%pagination.pageSize))).toString();
|
|
152
|
+
endSlicIndex=currentJobs.length;
|
|
153
|
+
}
|
|
154
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = urlParams.page
|
|
155
|
+
const jobsFirstPage=currentJobs.slice(startSlicIndex,endSlicIndex);
|
|
156
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).data = jobsFirstPage;
|
|
157
|
+
handlePaginationButtons(_$w);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Failed to handle url params:', error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function handleParams(_$w,param,values) {
|
|
165
|
+
let applyFiltering=false;
|
|
166
|
+
let valuesAsArray = values.split(',')
|
|
167
|
+
valuesAsArray=valuesAsArray.filter(value=>value.trim()!=='');
|
|
168
|
+
|
|
169
|
+
let selectedIndices=[];
|
|
170
|
+
const field=getFieldByTitle(fieldTitlesInCMS[param],allfields);
|
|
171
|
+
|
|
172
|
+
let existing = selectedByField.get(field._id) || [];
|
|
173
|
+
for(const value of valuesAsArray) {
|
|
174
|
+
|
|
175
|
+
const decodedValue = decodeURIComponent(value);
|
|
176
|
+
|
|
177
|
+
const options=optionsByFieldId.get(field._id);
|
|
178
|
+
|
|
179
|
+
const option=getCorrectOption(decodedValue,options,param);
|
|
180
|
+
|
|
181
|
+
if(option) {
|
|
182
|
+
const optionIndex=getOptionIndexFromCheckBox(_$w(`#${FiltersIds[field.title]}CheckBox`).options,option.value);
|
|
183
|
+
selectedIndices.push(optionIndex);
|
|
184
|
+
existing.push(option.value);
|
|
185
|
+
applyFiltering=true;
|
|
186
|
+
dontUpdateThisCheckBox=field._id;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.warn(`${param} value not found in dropdown options`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
selectedByField.set(field._id, existing);
|
|
194
|
+
_$w(`#${FiltersIds[field.title]}CheckBox`).selectedIndices=selectedIndices;
|
|
195
|
+
|
|
196
|
+
return applyFiltering;
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function loadPaginationButtons(_$w) {
|
|
201
|
+
try {
|
|
202
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).onClick(async () => {
|
|
203
|
+
let nextPageJobs=currentJobs.slice(pagination.pageSize*pagination.currentPage,pagination.pageSize*(pagination.currentPage+1));
|
|
204
|
+
|
|
205
|
+
pagination.currentPage++;
|
|
206
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = pagination.currentPage.toString();
|
|
207
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).data = nextPageJobs;
|
|
208
|
+
handlePaginationButtons(_$w);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).onClick(async () => {
|
|
212
|
+
let previousPageJobs=currentJobs.slice(pagination.pageSize*(pagination.currentPage-2),pagination.pageSize*(pagination.currentPage-1));
|
|
213
|
+
pagination.currentPage--;
|
|
214
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = pagination.currentPage.toString();
|
|
215
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).data = previousPageJobs;
|
|
216
|
+
handlePaginationButtons(_$w);
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Failed to load pagination buttons:', error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function loadSelectedValuesRepeater(_$w) {
|
|
224
|
+
try {
|
|
225
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SELECTED_VALUES_REPEATER).onItemReady(($item, itemData) => {
|
|
226
|
+
$item(CAREERS_MULTI_BOXES_PAGE_CONSTS.SELECTED_VALUES_REPEATER_ITEM_LABEL).text = itemData.label || '';
|
|
227
|
+
// Deselect this value from both the selected map and the multibox
|
|
228
|
+
$item(CAREERS_MULTI_BOXES_PAGE_CONSTS.DESELECT_BUTTON_ID).onClick(async () => {
|
|
229
|
+
const fieldId = itemData.fieldId;
|
|
230
|
+
const valueId = itemData.valueId;
|
|
231
|
+
dontUpdateThisCheckBox=fieldId;
|
|
232
|
+
if (!fieldId || !valueId) return;
|
|
233
|
+
const existing = selectedByField.get(fieldId) || [];
|
|
234
|
+
const updated = existing.filter(v => v !== valueId);
|
|
235
|
+
const field=getFieldById(fieldId,allfields);
|
|
236
|
+
let fieldTitle=field.title.toLowerCase().replace(' ', '');
|
|
237
|
+
fieldTitle==="brands"? fieldTitle="brand":fieldTitle;
|
|
238
|
+
if (updated.length) {
|
|
239
|
+
selectedByField.set(fieldId, updated);
|
|
240
|
+
queryParams.add({ [fieldTitle] : updated.map(val=>encodeURIComponent(val)).join(',') });
|
|
241
|
+
} else {
|
|
242
|
+
selectedByField.delete(fieldId);
|
|
243
|
+
queryParams.remove([fieldTitle ]);
|
|
244
|
+
}
|
|
245
|
+
const currentVals = _$w(`#${FiltersIds[field.title]}CheckBox`).value || [];
|
|
246
|
+
const nextVals = currentVals.filter(v => v !== valueId);
|
|
247
|
+
_$w(`#${FiltersIds[field.title]}CheckBox`).value = nextVals;
|
|
248
|
+
await updateJobsAndNumbersAndFilters(_$w);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
updateSelectedValuesRepeater(_$w);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error('Failed to load selected values repeater:', error);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function loadData() {
|
|
258
|
+
try {
|
|
259
|
+
if(alljobs.length===0) {
|
|
260
|
+
alljobs=await getAllRecords(COLLECTIONS.JOBS);
|
|
261
|
+
currentJobs=alljobs;
|
|
262
|
+
}
|
|
263
|
+
if(Object.keys(valueToJobs).length === 0){
|
|
264
|
+
allvaluesobjects=await getAllRecords(COLLECTIONS.CUSTOM_VALUES);
|
|
265
|
+
for (const value of allvaluesobjects) {
|
|
266
|
+
valueToJobs[value._id]= value.jobIds;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if(allfields.length===0) {
|
|
270
|
+
allfields=await getAllRecords(COLLECTIONS.CUSTOM_FIELDS);
|
|
271
|
+
allfields.push({_id:"Location",title:"Location"});
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error('Failed to load data:', error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async function loadJobsRepeater(_$w) {
|
|
278
|
+
try {
|
|
279
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).onItemReady(($item, itemData) => {
|
|
280
|
+
$item(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER_ITEM_TITLE).text = itemData.title;
|
|
281
|
+
$item(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER_ITEM_TITLE).onClick(() => {
|
|
282
|
+
location.to(itemData["link-jobs-title"]);
|
|
283
|
+
});
|
|
284
|
+
$item(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER_ITEM_LOCATION).text=itemData.location.fullLocation
|
|
285
|
+
$item(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER_ITEM_EMPLOYMENT_TYPE).text=itemData.employmentType
|
|
286
|
+
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const jobsFirstPage=alljobs.slice(0,pagination.pageSize);
|
|
290
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).data = jobsFirstPage;
|
|
291
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = "1"
|
|
292
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationTotalCountText).text = Math.ceil(currentJobs.length/pagination.pageSize).toString();
|
|
293
|
+
updateTotalJobsCountText(_$w);
|
|
294
|
+
handlePaginationButtons(_$w);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('Failed to load jobs repeater:', error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function updateTotalJobsCountText(_$w) {
|
|
301
|
+
secondarySearchIsFilled? _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.TotalJobsCountText).text = `${currentSecondarySearchJobs.length} Jobs`:
|
|
302
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.TotalJobsCountText).text = `${currentJobs.length} Jobs`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function loadFilters(_$w) {
|
|
306
|
+
try {
|
|
307
|
+
// 1) Load all categories (fields)
|
|
308
|
+
const cities=await getAllRecords(COLLECTIONS.CITIES);
|
|
309
|
+
for(const city of cities) {
|
|
310
|
+
valueToJobs[city._id]=city.jobIds;
|
|
311
|
+
}
|
|
312
|
+
// 2) Load all values once and group them by referenced field
|
|
313
|
+
let valuesByFieldId = groupValuesByField(allvaluesobjects, CUSTOM_VALUES_COLLECTION_FIELDS.CUSTOM_FIELD);
|
|
314
|
+
valuesByFieldId.set("Location",cities)
|
|
315
|
+
// Build CheckboxGroup options for this field
|
|
316
|
+
|
|
317
|
+
const counter={}
|
|
318
|
+
for(const city of cities) {
|
|
319
|
+
counter[city.city]=city.count
|
|
320
|
+
}
|
|
321
|
+
for(const [key, value] of valuesByFieldId) {
|
|
322
|
+
const field=getFieldById(key,allfields);
|
|
323
|
+
let originalOptions=[];
|
|
324
|
+
if(key==="Location") {
|
|
325
|
+
originalOptions=value.map(city=>({
|
|
326
|
+
label: city.city,
|
|
327
|
+
value: city._id
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
else{
|
|
331
|
+
originalOptions=value
|
|
332
|
+
}
|
|
333
|
+
optionsByFieldId.set(key, originalOptions);
|
|
334
|
+
for (const val of allvaluesobjects) {
|
|
335
|
+
counter[val.title]=val.count
|
|
336
|
+
}
|
|
337
|
+
countsByFieldId.set(key, new Map(originalOptions.map(o => [o.value, counter[o.label]])));
|
|
338
|
+
updateOptionsUI(_$w,field.title, field._id, ''); // no search query
|
|
339
|
+
_$w(`#${FiltersIds[field.title]}CheckBox`).selectedIndices = []; // start empty
|
|
340
|
+
_$w(`#${FiltersIds[field.title]}CheckBox`).onChange(async (ev) => {
|
|
341
|
+
dontUpdateThisCheckBox=field._id;
|
|
342
|
+
const selected = ev.target.value; // array of selected value IDs
|
|
343
|
+
let fieldTitle=field.title.toLowerCase().replace(' ', '');
|
|
344
|
+
fieldTitle==="brands"? fieldTitle="brand":fieldTitle;
|
|
345
|
+
|
|
346
|
+
if (selected && selected.length) {
|
|
347
|
+
selectedByField.set(field._id, selected);
|
|
348
|
+
if(fieldTitle==="brand" || fieldTitle==="storename") {
|
|
349
|
+
//in this case we need the label not valueid
|
|
350
|
+
const valueLabels=getValueFromValueId(selected,value);
|
|
351
|
+
queryParams.add({ [fieldTitle] : valueLabels.map(val=>encodeURIComponent(val)).join(',') });
|
|
352
|
+
}
|
|
353
|
+
else{
|
|
354
|
+
queryParams.add({ [fieldTitle] : selected.map(val=>encodeURIComponent(val)).join(',') });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
} else {
|
|
358
|
+
selectedByField.delete(field._id);
|
|
359
|
+
queryParams.remove([fieldTitle ]);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
await updateJobsAndNumbersAndFilters(_$w);
|
|
363
|
+
|
|
364
|
+
});
|
|
365
|
+
const runFilter = debounce(() => {
|
|
366
|
+
const query = (_$w(`#${FiltersIds[field.title]}input`).value || '').toLowerCase().trim();
|
|
367
|
+
updateOptionsUI(_$w, field.title, field._id, query);
|
|
368
|
+
}, 150);
|
|
369
|
+
_$w(`#${FiltersIds[field.title]}input`).onInput(runFilter);
|
|
370
|
+
|
|
371
|
+
}
|
|
372
|
+
await refreshFacetCounts(_$w);
|
|
373
|
+
|
|
374
|
+
} catch (err) {
|
|
375
|
+
console.error('Failed to load filters:', err);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function getValueFromValueId(valueIds,value) {
|
|
380
|
+
let valueLabels=[];
|
|
381
|
+
let currentVal
|
|
382
|
+
for(const valueId of valueIds) {
|
|
383
|
+
currentVal=value.find(val=>val.value===valueId);
|
|
384
|
+
if(currentVal) {
|
|
385
|
+
valueLabels.push(currentVal.label);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return valueLabels
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function updateJobsAndNumbersAndFilters(_$w,clearAll=false) {
|
|
392
|
+
await applyJobFilters(_$w); // re-query jobs
|
|
393
|
+
await refreshFacetCounts(_$w,clearAll); // recompute and update counts in all lists
|
|
394
|
+
await updateSelectedValuesRepeater(_$w);
|
|
395
|
+
updateTotalJobsCountText(_$w);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function updateOptionsUI(_$w,fieldTitle, fieldId, searchQuery,clearAll=false) {
|
|
399
|
+
let base = optionsByFieldId.get(fieldId) || [];
|
|
400
|
+
const countsMap = countsByFieldId.get(fieldId) || new Map();
|
|
401
|
+
if(dontUpdateThisCheckBox===fieldId && !clearAll)
|
|
402
|
+
{
|
|
403
|
+
dontUpdateThisCheckBox=null;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
let filteredbase=[]
|
|
407
|
+
for (const element of base)
|
|
408
|
+
{
|
|
409
|
+
if(countsMap.get(element.value))
|
|
410
|
+
{
|
|
411
|
+
filteredbase.push(element)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Build display options with counts
|
|
415
|
+
const withCounts = filteredbase.map(o => {
|
|
416
|
+
const count = countsMap.get(o.value)
|
|
417
|
+
return {
|
|
418
|
+
label: `${o.label} (${count})`,
|
|
419
|
+
value: o.value
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
// Apply search
|
|
423
|
+
const filtered = searchQuery
|
|
424
|
+
? withCounts.filter(o => (o.label || '').toLowerCase().includes(searchQuery))
|
|
425
|
+
: withCounts;
|
|
426
|
+
|
|
427
|
+
// Preserve currently selected values that are still visible
|
|
428
|
+
let prevSelected=[]
|
|
429
|
+
clearAll? prevSelected=[]:prevSelected= _$w(`#${FiltersIds[fieldTitle]}CheckBox`).value;
|
|
430
|
+
const visibleSet = new Set(filtered.map(o => o.value));
|
|
431
|
+
const preserved = prevSelected.filter(v => visibleSet.has(v));
|
|
432
|
+
if(filtered.length===0) {
|
|
433
|
+
_$w(`#${FiltersIds[fieldTitle]}MultiBox`).changeState(`${FiltersIds[fieldTitle]}NoResults`);
|
|
434
|
+
}
|
|
435
|
+
else{
|
|
436
|
+
_$w(`#${FiltersIds[fieldTitle]}MultiBox`).changeState(`${FiltersIds[fieldTitle]}Results`);
|
|
437
|
+
_$w(`#${FiltersIds[fieldTitle]}CheckBox`).options = filtered;
|
|
438
|
+
_$w(`#${FiltersIds[fieldTitle]}CheckBox`).value = preserved;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function applyJobFilters(_$w) {
|
|
443
|
+
let tempFilteredJobs=[];
|
|
444
|
+
let finalFilteredJobs=[];
|
|
445
|
+
secondarySearchIsFilled? finalFilteredJobs=allsecondarySearchJobs:finalFilteredJobs=alljobs;
|
|
446
|
+
if(keywordAllJobs) {
|
|
447
|
+
finalFilteredJobs=keywordAllJobs
|
|
448
|
+
}
|
|
449
|
+
let addedJobsIds=new Set();
|
|
450
|
+
// AND across categories, OR within each category
|
|
451
|
+
for (const [key, values] of selectedByField.entries()) {
|
|
452
|
+
for(const job of finalFilteredJobs) {
|
|
453
|
+
if(key==="Location"){
|
|
454
|
+
//if it is location then we check if selecred values (which is an array) have job city text
|
|
455
|
+
if(values.includes(job[JOBS_COLLECTION_FIELDS.CITY_TEXT])) {
|
|
456
|
+
if(!addedJobsIds.has(job._id)) {
|
|
457
|
+
tempFilteredJobs.push(job);
|
|
458
|
+
addedJobsIds.add(job._id);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else{
|
|
463
|
+
//if it is not location then we check if selecred values (which is an array) have one of the job values (whcih is also an array)
|
|
464
|
+
if(job[JOBS_COLLECTION_FIELDS.MULTI_REF_JOBS_CUSTOM_VALUES].some(value=>values.includes(value._id))) {
|
|
465
|
+
if(!addedJobsIds.has(job._id)) {
|
|
466
|
+
tempFilteredJobs.push(job);
|
|
467
|
+
addedJobsIds.add(job._id);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
addedJobsIds.clear();
|
|
473
|
+
finalFilteredJobs=tempFilteredJobs;
|
|
474
|
+
tempFilteredJobs=[];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
secondarySearchIsFilled? currentSecondarySearchJobs=finalFilteredJobs:currentJobs=finalFilteredJobs;
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
let jobsFirstPage=[];
|
|
481
|
+
secondarySearchIsFilled? jobsFirstPage=currentSecondarySearchJobs.slice(0,pagination.pageSize):jobsFirstPage=currentJobs.slice(0,pagination.pageSize);
|
|
482
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).data = jobsFirstPage;
|
|
483
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = "1";
|
|
484
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationTotalCountText).text = secondarySearchIsFilled? Math.ceil(currentSecondarySearchJobs.length/pagination.pageSize).toString():Math.ceil(currentJobs.length/pagination.pageSize).toString();
|
|
485
|
+
if(jobsFirstPage.length===0) {
|
|
486
|
+
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("noJobs");
|
|
487
|
+
}
|
|
488
|
+
else{
|
|
489
|
+
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("searchResult");
|
|
490
|
+
}
|
|
491
|
+
pagination.currentPage=1;
|
|
492
|
+
handlePaginationButtons(_$w);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function handlePaginationButtons(_$w)
|
|
496
|
+
{
|
|
497
|
+
handlePageUrlParam();
|
|
498
|
+
|
|
499
|
+
pagination.currentPage===1? _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).disable():_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).enable();
|
|
500
|
+
if(secondarySearchIsFilled) {
|
|
501
|
+
if(currentSecondarySearchJobs.length===0) {
|
|
502
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).disable();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
pagination.currentPage>=Math.ceil(currentSecondarySearchJobs.length/pagination.pageSize)? _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).disable():_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).enable();
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
if(currentJobs.length===0) {
|
|
509
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).disable();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
pagination.currentPage>=Math.ceil(currentJobs.length/pagination.pageSize)? _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).disable():_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).enable();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function handlePageUrlParam() {
|
|
517
|
+
if(pagination.currentPage==1)
|
|
518
|
+
{
|
|
519
|
+
queryParams.remove(["page"]);
|
|
520
|
+
}
|
|
521
|
+
else{
|
|
522
|
+
queryParams.add({ page: pagination.currentPage });
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
}
|
|
526
|
+
async function refreshFacetCounts(_$w,clearAll=false) {
|
|
527
|
+
|
|
528
|
+
secondarySearchIsFilled? countJobsPerField(currentSecondarySearchJobs):countJobsPerField(currentJobs);
|
|
529
|
+
for(const field of allfields) {
|
|
530
|
+
|
|
531
|
+
const query = (_$w(`#${FiltersIds[field.title]}input`).value || '').toLowerCase().trim();
|
|
532
|
+
clearAll? updateOptionsUI(_$w,field.title, field._id, '',true):updateOptionsUI(_$w,field.title, field._id, query);
|
|
533
|
+
// no search query
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
function countJobsPerField(jobs) {
|
|
539
|
+
const fieldIds = Array.from(optionsByFieldId.keys());
|
|
540
|
+
const currentJobsIds=jobs.map(job=>job._id);
|
|
541
|
+
|
|
542
|
+
for (const fieldId of fieldIds) {
|
|
543
|
+
let currentoptions=optionsByFieldId.get(fieldId)
|
|
544
|
+
let counter=new Map();
|
|
545
|
+
for(const option of currentoptions) {
|
|
546
|
+
for (const jobId of currentJobsIds) {
|
|
547
|
+
if (valueToJobs[option.value].includes(jobId)) {
|
|
548
|
+
counter.set(option.value, (counter.get(option.value) || 0) + 1);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
countsByFieldId.set(fieldId, counter);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
function updateSelectedValuesRepeater(_$w) {
|
|
558
|
+
const selectedItems = [];
|
|
559
|
+
for (const [fieldId, valueIds] of selectedByField.entries()) {
|
|
560
|
+
const opts = optionsByFieldId.get(fieldId) || [];
|
|
561
|
+
for (const id of valueIds) {
|
|
562
|
+
const found = opts.find((option) => option.value === id);
|
|
563
|
+
const label = found.label;
|
|
564
|
+
selectedItems.push({ _id: `${fieldId}:${id}`, label, fieldId, valueId: id });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SELECTED_VALUES_REPEATER).data = selectedItems;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
async function secondarySearch(_$w,query) {
|
|
573
|
+
if(query.length===0 || query===undefined || query==='') {
|
|
574
|
+
secondarySearchIsFilled=false;
|
|
575
|
+
await updateJobsAndNumbersAndFilters(_$w); // we do this here because of the case when searching the list and adding filters from the side, and we delete the search query, so we need to refresh the counts and the jobs
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
allsecondarySearchJobs=currentJobs.filter(job=>job.title.toLowerCase().includes(query));
|
|
580
|
+
currentSecondarySearchJobs=allsecondarySearchJobs;
|
|
581
|
+
const jobsFirstPage=allsecondarySearchJobs.slice(0,pagination.pageSize);
|
|
582
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_REPEATER).data = jobsFirstPage;
|
|
583
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = "1";
|
|
584
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationTotalCountText).text = Math.ceil(allsecondarySearchJobs.length/pagination.pageSize).toString();
|
|
585
|
+
pagination.currentPage=1;
|
|
586
|
+
|
|
587
|
+
if(jobsFirstPage.length===0) {
|
|
588
|
+
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("noJobs");
|
|
589
|
+
}
|
|
590
|
+
else{
|
|
591
|
+
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("searchResult");
|
|
592
|
+
}
|
|
593
|
+
secondarySearchIsFilled=true
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
handlePaginationButtons(_$w);
|
|
597
|
+
updateTotalJobsCountText(_$w);
|
|
598
|
+
await refreshFacetCounts(_$w);
|
|
599
|
+
return allsecondarySearchJobs;
|
|
600
|
+
}
|
|
601
|
+
function bindSearchInput(_$w) {
|
|
602
|
+
try {
|
|
603
|
+
bindPrimarySearch(_$w,allvaluesobjects,alljobs);
|
|
604
|
+
|
|
605
|
+
const secondarySearchDebounced = debounce(async () => {
|
|
606
|
+
const query = (_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SECONDARY_SEARCH_INPUT).value || '').toLowerCase().trim();
|
|
607
|
+
await secondarySearch(_$w, query);
|
|
608
|
+
}, 150);
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SECONDARY_SEARCH_INPUT).onInput(secondarySearchDebounced);
|
|
612
|
+
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error('Failed to bind search input:', error);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
module.exports = {
|
|
620
|
+
careersMultiBoxesPageOnReady,
|
|
621
|
+
secondarySearch
|
|
622
|
+
};
|