@ta-interaktiv/react-municipality-search 1.5.4 → 1.7.0
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/CHANGELOG.md +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +154 -73
- package/dist/index.js.map +1 -1
- package/dist/municipalitySearch.d.ts +4 -1
- package/dist/municipalitySearch.d.ts.map +1 -1
- package/es/index.d.ts.map +1 -1
- package/es/index.js +122 -46
- package/es/index.js.map +1 -1
- package/es/municipalitySearch.d.ts +4 -1
- package/es/municipalitySearch.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/municipalitySearch.tsx +148 -57
|
@@ -9,6 +9,7 @@ import { animated, config as springConfig, Transition } from 'react-spring'
|
|
|
9
9
|
import IntlMessageFormat from 'intl-messageformat'
|
|
10
10
|
import de from './locales/de.yml'
|
|
11
11
|
import fr from './locales/fr.yml'
|
|
12
|
+
import Cookies from 'js-cookie'
|
|
12
13
|
|
|
13
14
|
type MessageListContent = string | MessageList
|
|
14
15
|
|
|
@@ -30,6 +31,7 @@ export interface Municipality {
|
|
|
30
31
|
readonly KTKZ: string
|
|
31
32
|
readonly NORMORTSNAME: string
|
|
32
33
|
readonly NORMGEMEINDE: string
|
|
34
|
+
timestamp?: number
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface Props {
|
|
@@ -76,6 +78,7 @@ interface State {
|
|
|
76
78
|
|
|
77
79
|
/** All localised strings for the component */
|
|
78
80
|
const MESSAGES = { de, fr }
|
|
81
|
+
const localStorageItemName = 'selectedMunicipalities'
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
84
|
* Create a translation Service function, using the provided MessageList and
|
|
@@ -103,7 +106,16 @@ const translationServiceFactory = (
|
|
|
103
106
|
)
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
|
-
|
|
109
|
+
function getDomain():string {
|
|
110
|
+
let url = window.location.hostname
|
|
111
|
+
url = url.replace(/(https?:\/\/)?(www.)?/i, '')
|
|
112
|
+
const urlArray = url.split('.')
|
|
113
|
+
url = urlArray.slice(urlArray.length - 2).join('.')
|
|
114
|
+
if (url.indexOf('/') !== -1) {
|
|
115
|
+
return url.split('/')[0]
|
|
116
|
+
}
|
|
117
|
+
return url;
|
|
118
|
+
}
|
|
107
119
|
class MunicipalityDownloadError extends Error {}
|
|
108
120
|
|
|
109
121
|
/** RegEx to look whether a string starts with a number */
|
|
@@ -130,7 +142,7 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
130
142
|
public static defaultProps = {
|
|
131
143
|
municipalityData: '2021v3',
|
|
132
144
|
locale: 'de',
|
|
133
|
-
numberOfLastSelectedMunicipalities: 1
|
|
145
|
+
numberOfLastSelectedMunicipalities: 1,
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
public state = {
|
|
@@ -138,14 +150,14 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
138
150
|
results: [],
|
|
139
151
|
value: '',
|
|
140
152
|
error: null,
|
|
141
|
-
municipalities: []
|
|
153
|
+
municipalities: [],
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
private resetComponent = () => {
|
|
145
157
|
this.setState({
|
|
146
158
|
isLoading: false,
|
|
147
159
|
results: [],
|
|
148
|
-
value: ''
|
|
160
|
+
value: '',
|
|
149
161
|
})
|
|
150
162
|
}
|
|
151
163
|
|
|
@@ -202,7 +214,7 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
202
214
|
window.dataLayer.push({
|
|
203
215
|
event: 'Interactions',
|
|
204
216
|
event_action: 'input',
|
|
205
|
-
event_label: `search:for_${searchTerm}:results_${results.length}
|
|
217
|
+
event_label: `search:for_${searchTerm}:results_${results.length}`,
|
|
206
218
|
})
|
|
207
219
|
}
|
|
208
220
|
|
|
@@ -214,7 +226,7 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
214
226
|
searchTerm: string
|
|
215
227
|
): Municipality[] {
|
|
216
228
|
const map = new Map()
|
|
217
|
-
arr.forEach(v => {
|
|
229
|
+
arr.forEach((v) => {
|
|
218
230
|
if (this.props.dedupe) {
|
|
219
231
|
map.set(v.ORTNAME + v.GDENR, v)
|
|
220
232
|
} else {
|
|
@@ -273,57 +285,130 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
273
285
|
.fetch(url, {
|
|
274
286
|
method: 'GET',
|
|
275
287
|
mode: 'no-cors',
|
|
276
|
-
credentials: 'include'
|
|
288
|
+
credentials: 'include',
|
|
277
289
|
})
|
|
278
|
-
.then(response => {
|
|
290
|
+
.then((response) => {
|
|
279
291
|
console.log(response)
|
|
280
292
|
})
|
|
281
|
-
.catch(error => {
|
|
293
|
+
.catch((error) => {
|
|
282
294
|
console.log(error)
|
|
283
295
|
})
|
|
284
296
|
}
|
|
285
297
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Get the history of the last selected municipalities from localStorage and cookies
|
|
300
|
+
*/
|
|
301
|
+
private getStoredMunicipalities = (): Municipality[] | [] => {
|
|
302
|
+
let munisFromLocalStorage: Municipality[] = []
|
|
303
|
+
let munisFromCookies: Municipality[] = []
|
|
290
304
|
try {
|
|
291
|
-
|
|
292
|
-
localStorage.getItem(
|
|
293
|
-
|
|
294
|
-
} catch {
|
|
295
|
-
console.
|
|
305
|
+
const munisFromLocalStorageString =
|
|
306
|
+
localStorage.getItem('selectedMunicipalities') ?? '[]'
|
|
307
|
+
munisFromLocalStorage = JSON.parse(munisFromLocalStorageString)
|
|
308
|
+
} catch (e) {
|
|
309
|
+
console.log(e)
|
|
310
|
+
console.warn('🗄 localStorage of selectedMunicipalities is not available')
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const munisFromCookiesString =
|
|
314
|
+
Cookies.get('selectedMunicipalities') ?? '[]'
|
|
315
|
+
munisFromCookies = JSON.parse(munisFromCookiesString)
|
|
316
|
+
} catch (e) {
|
|
317
|
+
console.log(e)
|
|
318
|
+
console.warn('🍪 cookies of selectedMunicipalities is not available')
|
|
296
319
|
}
|
|
320
|
+
const ordered = [...munisFromLocalStorage, ...munisFromCookies].sort(
|
|
321
|
+
(a, b) => {
|
|
322
|
+
if (b.timestamp && a.timestamp) {
|
|
323
|
+
return b.timestamp - a.timestamp
|
|
324
|
+
}
|
|
325
|
+
if (b.timestamp) {
|
|
326
|
+
return 1
|
|
327
|
+
}
|
|
328
|
+
if (a.timestamp) {
|
|
329
|
+
return -1
|
|
330
|
+
}
|
|
331
|
+
return 0
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
return this.deduplicateMunicipalityArray(ordered)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Save the history of the last selected municipalities
|
|
339
|
+
* They will be saved in localStorage and in cookies
|
|
340
|
+
* Cookies are used for exchange between subdomains eg. interaktiv.tagesanzeiger.ch and tagesanzeiger.ch
|
|
341
|
+
*/
|
|
342
|
+
private saveToSelectedMunicipalities = (newMuni: Municipality) => {
|
|
343
|
+
const prevSelectedMunicipalities = this.getStoredMunicipalities()
|
|
297
344
|
// limit the number of stored municipalities
|
|
298
|
-
const slicedArray =
|
|
345
|
+
const slicedArray = prevSelectedMunicipalities.slice(
|
|
346
|
+
0,
|
|
347
|
+
15
|
|
348
|
+
) as Municipality[]
|
|
349
|
+
newMuni.timestamp = Date.now()
|
|
299
350
|
slicedArray.unshift(newMuni)
|
|
351
|
+
console.log('domain:', getDomain())
|
|
352
|
+
|
|
300
353
|
try {
|
|
301
|
-
// deduplicate the array and stringify it for localStorage
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
JSON.stringify([
|
|
305
|
-
...new Map(
|
|
306
|
-
slicedArray.map((muni: Municipality) => [
|
|
307
|
-
muni.GDENR + muni.ORTNAME + muni.PLZ4,
|
|
308
|
-
muni
|
|
309
|
-
])
|
|
310
|
-
).values()
|
|
311
|
-
])
|
|
354
|
+
// deduplicate the array, sort it by timestamp and stringify it for localStorage and cookies
|
|
355
|
+
const newMuniArray = JSON.stringify(
|
|
356
|
+
this.deduplicateMunicipalityArray(slicedArray)
|
|
312
357
|
)
|
|
358
|
+
localStorage.setItem(localStorageItemName, newMuniArray)
|
|
359
|
+
Cookies.set(localStorageItemName, newMuniArray, {
|
|
360
|
+
expires: 365,
|
|
361
|
+
domain: getDomain(),
|
|
362
|
+
})
|
|
313
363
|
} catch {
|
|
314
364
|
console.warn('🗄 localStorage is not writeable')
|
|
315
365
|
}
|
|
316
366
|
}
|
|
317
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Deduplicate the array of municipalities
|
|
370
|
+
* @param munis Municipalities array to deduplicate
|
|
371
|
+
* @returns Municipalities array deduplicated by ORTNAME, GDENR and PLZ4
|
|
372
|
+
*/
|
|
373
|
+
|
|
374
|
+
private deduplicateMunicipalityArray = (
|
|
375
|
+
munis: Municipality[]
|
|
376
|
+
): Municipality[] => {
|
|
377
|
+
return [
|
|
378
|
+
...new Map(
|
|
379
|
+
munis
|
|
380
|
+
.slice()
|
|
381
|
+
.reverse()
|
|
382
|
+
.map((muni: Municipality) => [
|
|
383
|
+
muni.ORTNAME + muni.GDENR + (this.props.dedupe ? '' : muni.PLZ4),
|
|
384
|
+
muni,
|
|
385
|
+
])
|
|
386
|
+
).values(),
|
|
387
|
+
]
|
|
388
|
+
.reverse()
|
|
389
|
+
.sort((a, b) => {
|
|
390
|
+
if (b.timestamp && a.timestamp) {
|
|
391
|
+
return b.timestamp - a.timestamp
|
|
392
|
+
}
|
|
393
|
+
if (b.timestamp) {
|
|
394
|
+
return 1
|
|
395
|
+
}
|
|
396
|
+
if (a.timestamp) {
|
|
397
|
+
return -1
|
|
398
|
+
}
|
|
399
|
+
return 0
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
318
403
|
private getMunicipalitiesData = () => {
|
|
319
404
|
this.setState({
|
|
320
|
-
isLoading: true
|
|
405
|
+
isLoading: true,
|
|
321
406
|
})
|
|
322
407
|
|
|
323
408
|
return fetch(
|
|
324
409
|
`https://interaktiv.tagesanzeiger.ch/static/gemeindesuche/${this.props.municipalityData}.json`
|
|
325
410
|
)
|
|
326
|
-
.then(res => {
|
|
411
|
+
.then((res) => {
|
|
327
412
|
if (!res.ok) {
|
|
328
413
|
throw new MunicipalityDownloadError(
|
|
329
414
|
`Download error: ${res.status}: ${res.statusText}.`
|
|
@@ -331,14 +416,14 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
331
416
|
}
|
|
332
417
|
return res.json()
|
|
333
418
|
})
|
|
334
|
-
.then(data => {
|
|
419
|
+
.then((data) => {
|
|
335
420
|
this.setState({
|
|
336
421
|
isLoading: false,
|
|
337
|
-
municipalities: data
|
|
422
|
+
municipalities: data,
|
|
338
423
|
})
|
|
339
424
|
return Promise.resolve(data)
|
|
340
425
|
})
|
|
341
|
-
.catch(error => {
|
|
426
|
+
.catch((error) => {
|
|
342
427
|
if (error instanceof MunicipalityDownloadError) {
|
|
343
428
|
console.log(error)
|
|
344
429
|
console.info(
|
|
@@ -349,7 +434,7 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
349
434
|
)
|
|
350
435
|
this.setState({
|
|
351
436
|
isLoading: false,
|
|
352
|
-
error: 'error.municipalitiesNotDownloaded'
|
|
437
|
+
error: 'error.municipalitiesNotDownloaded',
|
|
353
438
|
})
|
|
354
439
|
} else {
|
|
355
440
|
throw error
|
|
@@ -369,7 +454,7 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
369
454
|
/** Component mounting */
|
|
370
455
|
public componentDidMount() {
|
|
371
456
|
// Download municipalities list
|
|
372
|
-
this.getMunicipalitiesData().then(data => {
|
|
457
|
+
this.getMunicipalitiesData().then((data) => {
|
|
373
458
|
// set the municipalities that have the same GDENR, ORTNAME and PLZ4 as the ones in localStorage as results
|
|
374
459
|
let locallyStoredMunicipalitiesArray
|
|
375
460
|
try {
|
|
@@ -387,30 +472,34 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
387
472
|
locallyStoredMunicipalitiesArray
|
|
388
473
|
) as Municipality[]
|
|
389
474
|
const allBfsIdsAndNames = parsed.map(
|
|
390
|
-
muni => muni.GDENR + muni.ORTNAME + muni.PLZ4
|
|
475
|
+
(muni) => muni.GDENR + muni.ORTNAME + muni.PLZ4
|
|
391
476
|
)
|
|
392
477
|
const filteredFromFetchedData: Municipality[] = data.filter(
|
|
393
478
|
(muni: Municipality) =>
|
|
394
479
|
allBfsIdsAndNames.includes(muni.GDENR + muni.ORTNAME + muni.PLZ4)
|
|
395
480
|
)
|
|
396
481
|
// sort before deduplication
|
|
397
|
-
filteredFromFetchedData.sort((a, b) =>{
|
|
398
|
-
return
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
482
|
+
filteredFromFetchedData.sort((a, b) => {
|
|
483
|
+
return (
|
|
484
|
+
allBfsIdsAndNames.indexOf(a.GDENR + a.ORTNAME + a.PLZ4) -
|
|
485
|
+
allBfsIdsAndNames.indexOf(b.GDENR + b.ORTNAME + b.PLZ4)
|
|
486
|
+
)
|
|
487
|
+
})
|
|
488
|
+
// always dedupe bc. sometimes there is duplicate data in the jsons
|
|
489
|
+
const dedupedData = this.deduplicateMunicipalityArray(
|
|
490
|
+
filteredFromFetchedData
|
|
491
|
+
)
|
|
492
|
+
|
|
407
493
|
// sort the dedupedData in the same order as in the localStorage
|
|
408
|
-
dedupedData.sort((a, b) =>{
|
|
409
|
-
return
|
|
410
|
-
|
|
411
|
-
|
|
494
|
+
dedupedData.sort((a, b) => {
|
|
495
|
+
return (
|
|
496
|
+
allBfsIdsAndNames.indexOf(a.GDENR + a.ORTNAME + a.PLZ4) -
|
|
497
|
+
allBfsIdsAndNames.indexOf(b.GDENR + b.ORTNAME + b.PLZ4)
|
|
498
|
+
)
|
|
499
|
+
})
|
|
500
|
+
const muniCount = this.props.numberOfLastSelectedMunicipalities
|
|
412
501
|
this.setState({
|
|
413
|
-
results: dedupedData.slice(0, muniCount)
|
|
502
|
+
results: dedupedData.slice(0, muniCount),
|
|
414
503
|
})
|
|
415
504
|
}
|
|
416
505
|
})
|
|
@@ -488,7 +577,7 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
488
577
|
this.state.error ? 'red' : 'basic grey'
|
|
489
578
|
} ${labelDirection} label`}
|
|
490
579
|
>
|
|
491
|
-
{labelContent()}
|
|
580
|
+
{String(labelContent())}
|
|
492
581
|
</label>
|
|
493
582
|
</div>
|
|
494
583
|
)}
|
|
@@ -513,13 +602,13 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
513
602
|
this.props.onSelectionHandler(result)
|
|
514
603
|
// this.sendToDataPipeline(result.PLZ4)
|
|
515
604
|
if (this.props.showLastSelectedMunicipalities) {
|
|
516
|
-
this.
|
|
605
|
+
this.saveToSelectedMunicipalities(result)
|
|
517
606
|
}
|
|
518
607
|
if (this.props.resetOnSelect) {
|
|
519
608
|
this.resetComponent()
|
|
520
609
|
}
|
|
521
610
|
}}
|
|
522
|
-
onKeyUp={e => {
|
|
611
|
+
onKeyUp={(e) => {
|
|
523
612
|
if (e.key === 'Enter' || e.key === 'Space') {
|
|
524
613
|
this.props.onSelectionHandler(result)
|
|
525
614
|
// this.sendToDataPipeline(result.PLZ4)
|
|
@@ -537,7 +626,9 @@ export class MunicipalitySearch extends Component<Props, State> {
|
|
|
537
626
|
{result.ORTNAME}
|
|
538
627
|
</ResultHeader>
|
|
539
628
|
<ResultMeta className='resultMeta'>
|
|
540
|
-
|
|
629
|
+
<>
|
|
630
|
+
{t('list.municipalityPrefix')} {result.GDENAMK}
|
|
631
|
+
</>
|
|
541
632
|
</ResultMeta>
|
|
542
633
|
</animated.div>
|
|
543
634
|
)}
|