@geops/rvf-mobility-web-component 0.1.61 → 0.1.62

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +1 -0
  3. package/docutils.js +103 -4
  4. package/fonts/source-sans-3/source-sans-3-v15-latin-500.ttf +0 -0
  5. package/fonts/source-sans-3/source-sans-3-v15-latin-500.woff2 +0 -0
  6. package/fonts/source-sans-3/source-sans-3-v15-latin-600.ttf +0 -0
  7. package/fonts/source-sans-3/source-sans-3-v15-latin-600.woff2 +0 -0
  8. package/fonts/source-sans-3/source-sans-3-v15-latin-700.ttf +0 -0
  9. package/fonts/source-sans-3/source-sans-3-v15-latin-700.woff2 +0 -0
  10. package/fonts/source-sans-3/source-sans-3-v15-latin-regular.ttf +0 -0
  11. package/fonts/source-sans-3/source-sans-3-v15-latin-regular.woff2 +0 -0
  12. package/index.html +27 -97
  13. package/index.js +235 -224
  14. package/notifications.html +144 -0
  15. package/package.json +2 -2
  16. package/search.html +24 -65
  17. package/src/FeatureDetails/FeatureDetails.tsx +20 -6
  18. package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +2 -0
  19. package/src/LayerTree/TreeItem/TreeItem.tsx +2 -2
  20. package/src/LayerTreeMenu/LayerTreeMenu.tsx +19 -3
  21. package/src/LayoutState/LayoutState.tsx +17 -0
  22. package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +34 -21
  23. package/src/LinesNetworkPlanLayer/LinesNetworkPlanLayer.tsx +3 -6
  24. package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +88 -0
  25. package/src/LinesNetworkPlanLayerHighlight/index.tsx +1 -0
  26. package/src/MapDispatchEvents/MapDispatchEvents.tsx +6 -4
  27. package/src/MapLayout/MapLayout.tsx +2 -2
  28. package/src/MapsetLayer/MapsetLayer.tsx +116 -0
  29. package/src/MapsetLayer/index.tsx +1 -0
  30. package/src/MobilityMap/MobilityMap.tsx +27 -5
  31. package/src/MobilityMap/MobilityMapAttributes.test.ts +38 -0
  32. package/src/MobilityMap/MobilityMapAttributes.ts +99 -22
  33. package/src/MobilityMap/MobilityMapEvents.ts +53 -0
  34. package/src/MobilityNotifications/MobilityNotifications.tsx +93 -0
  35. package/src/MobilityNotifications/MobilityNotificationsAttributes.test.ts +21 -0
  36. package/src/MobilityNotifications/MobilityNotificationsAttributes.ts +46 -0
  37. package/src/MobilityNotifications/index.ts +2 -0
  38. package/src/MobilitySearch/MobilitySearchEvents.ts +21 -0
  39. package/src/NotificationDetails/NotificationDetails.tsx +74 -251
  40. package/src/OverlayContent/OverlayContent.tsx +1 -1
  41. package/src/OverlayDetails/OverlayDetails.tsx +4 -2
  42. package/src/OverlayFooter/OverlayFooter.tsx +3 -2
  43. package/src/RealtimeLayer/RealtimeLayer.tsx +36 -7
  44. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +1 -2
  45. package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +2 -2
  46. package/src/RvfFeatureDetails/RvfLineNetworkDetails/RvfLineNetworkDetails.tsx +2 -0
  47. package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +12 -453
  48. package/src/RvfFeatureDetails/RvfSellingPointDetails/RvfSellingPointDetails.tsx +20 -17
  49. package/src/RvfFeatureDetails/RvfSharedMobilityDetail/RvfSharedMobilityDetails.tsx +93 -36
  50. package/src/RvfLink/RvfLink.tsx +5 -2
  51. package/src/RvfMobilityMap/RvfMobilityMap.tsx +27 -9
  52. package/src/RvfSelectedFeatureHighlightLayer/RvfSelectedFeatureHighlightLayer.tsx +9 -5
  53. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +3 -1
  54. package/src/SituationDetails/SituationDetails.tsx +324 -0
  55. package/src/SituationDetails/index.ts +1 -0
  56. package/src/index.tsx +16 -0
  57. package/src/indexDoc.ts +25 -3
  58. package/src/ui/Checkbox/Checkbox.tsx +2 -2
  59. package/src/ui/Link/Link.tsx +49 -0
  60. package/src/ui/Link/index.tsx +1 -0
  61. package/src/ui/Select/Select.tsx +2 -2
  62. package/src/utils/constants.ts +37 -0
  63. package/src/utils/exportPdf.ts +3 -1
  64. package/src/utils/highlightLinesNetworkPlan.ts +25 -0
  65. package/src/utils/hooks/useI18n.tsx +6 -4
  66. package/src/utils/hooks/useMapContext.tsx +9 -0
  67. package/src/utils/sharingGraphqlUtils.ts +1 -1
  68. package/src/utils/translations.ts +12 -1
  69. package/tailwind.config.mjs +3 -1
  70. package/src/ShareMenu/PermalinkButton/PermalinkButton.tsx +0 -62
  71. package/src/ShareMenu/PermalinkButton/index.tsx +0 -1
  72. package/src/icons/Geolocation/airport-14-svgrepo-com.svg +0 -41
@@ -1,319 +1,32 @@
1
- import {
2
- type AffectedTimeIntervalType,
3
- type PublicationType,
4
- type SituationType,
5
- type TextualContentType,
6
- } from "mobility-toolbox-js/types";
7
- import { useEffect, useState } from "preact/hooks";
1
+ import { type SituationType } from "mobility-toolbox-js/types";
8
2
  import { twMerge } from "tailwind-merge";
9
3
 
10
- import Warning from "../../icons/Warning";
11
4
  import ShadowOverflow from "../../ShadowOverflow";
12
- import getBgColor from "../../utils/getBgColor";
13
- import useI18n from "../../utils/hooks/useI18n";
14
- import useMapContext from "../../utils/hooks/useMapContext";
5
+ import SituationDetails from "../../SituationDetails";
15
6
 
16
- import type { RealtimeLine, RealtimeMot } from "mobility-toolbox-js/types";
17
7
  import type { Feature } from "ol";
18
8
 
19
- const toShortDate = (date: Date, showTime, showYear?: boolean) => {
20
- const time = date.toLocaleTimeString(["de"], {
21
- hour: "2-digit",
22
- minute: "2-digit",
23
- });
24
- const dateString = date.toLocaleDateString(["de"], {
25
- day: "2-digit",
26
- month: "short",
27
- weekday: "short",
28
- year: showYear ? "numeric" : undefined,
29
- });
30
-
31
- return `${dateString}${showTime && showTime !== time ? ` ${time}` : ""}`
32
- .replace(",", "")
33
- .replace(/\./, "")
34
- .replace(/\.$/, "");
35
- };
36
-
37
- const getLine = (name: string, lines: NotificationLine[]): NotificationLine => {
38
- if (lines?.length) {
39
- const line = lines.find((linee) => {
40
- return linee.name === name;
41
- });
42
- if (line) {
43
- return line;
44
- }
45
- }
46
- return { mot: "bus", name } as NotificationLine;
47
- };
48
-
49
- export type NotificationLine = {
50
- mot?: RealtimeMot;
51
- operator_name?: string;
52
- short_name?: string;
53
- tags?: string[];
54
- } & RealtimeLine;
55
-
56
9
  function NotificationDetails({
57
10
  className,
58
11
  feature,
59
12
  ...props
60
13
  }: {
61
14
  className?: string;
15
+ containerClassName?: string;
62
16
  feature: Feature;
17
+ headerClassName?: string;
18
+ iconClassName?: string;
19
+ reasonClassName?: string;
20
+ timeIntervalClassName?: string;
21
+ toggleable?: boolean;
63
22
  }) {
64
- const { t } = useI18n();
65
- const { notificationtenant } = useMapContext();
66
- const [lines, setLines] = useState<NotificationLine[]>([]);
67
- const {
68
- affected_products: affectedProducts,
69
- affected_time_intervals: timeIntervals,
70
- consequence_de: consequence,
71
- description_de: descriptionDe,
72
- // title,
73
- disruption_type: disruptionType,
74
- // disruption_type: disruptionType,
75
- // duration_text_de: durationText,
76
- // links,
77
- // long_description: description,
78
- reason_de: reason,
79
- recommendation_de: recommendation,
80
- situation,
81
- summary_de: summary,
82
- } = feature.getProperties();
83
-
84
- useEffect(() => {
85
- const abortController = new AbortController();
86
- if (!notificationtenant) {
87
- setLines([]);
88
- }
89
- fetch(
90
- `https://tralis-tracker-api.geops.io/api/lines/${notificationtenant}/`,
91
- )
92
- .then((res) => {
93
- return res.json();
94
- })
95
- .then((data) => {
96
- setLines(data);
97
- })
98
- .catch((err) => {
99
- // eslint-disable-next-line no-console
100
- console.error("Failed to fetch lines", err);
101
- });
102
- return () => {
103
- abortController?.abort();
104
- };
105
- }, [notificationtenant]);
106
-
107
- // "title_de": "",
108
- // "title_fr": "",
109
- // "title_it": "",
110
- // "title_en": "",
111
- // "summary_de": "",
112
- // "summary_fr": "",
113
- // "summary_it": "",
114
- // "summary_en": "",
115
- // "reason_de": "Baustelle",
116
- // "reason_fr": "",
117
- // "reason_it": "",
118
- // "reason_en": "",
119
- // "description_de": "",
120
- // "description_fr": "",
121
- // "description_it": "",
122
- // "description_en": "",
123
- // "consequence_de": "Umleitung",
124
- // "consequence_fr": "",
125
- // "consequence_it": "",
126
- // "consequence_en": "",
127
- // "duration_text_de": "",
128
- // "duration_text_fr": "",
129
- // "duration_text_it": "",
130
- // "duration_text_en": "",
131
- // "recommendation_de": "",
132
- // "recommendation_fr": "",
133
- // "recommendation_it": "",
134
- // "recommendation_en": "",
135
- // "reasons": [
136
- // "Ausfall des Aufzuges"
137
- // ]
138
-
139
- let end = "",
140
- start = "";
141
- let products = [];
142
- // let externalLinks = [];
143
-
144
- try {
145
- const timeInterval = JSON.parse(timeIntervals)?.[0] || {};
146
- start = timeInterval.start;
147
- end = timeInterval.end;
148
- // const dateStart = new Date(timeInterval.start);
149
- // start =
150
- // dateStart.toLocaleDateString() + " " + dateStart.toLocaleTimeString();
151
- // const dateEnd = new Date(timeInterval.end);
152
- // console.log("dateEnd", dateEnd);
153
- // end = dateEnd.toLocaleDateString() + " " + dateEnd.toLocaleTimeString();
154
-
155
- products = JSON.parse(affectedProducts);
156
- products?.sort((a, b) => {
157
- return a.name.localeCompare(b.name);
158
- });
159
- // externalLinks = JSON.parse(links);
160
- } catch (e) {
161
- console.error(e);
162
- }
163
-
164
- // moco export v1
165
- if (disruptionType) {
166
- return (
167
- // <div className={"flex gap-2 text-sm"}>
168
- // <div className="min-w-8 shrink-0 grow-0">
169
- // <img
170
- // alt={disruptionType}
171
- // className={"w-8"}
172
- // src={icons[disruptionType]}
173
- // ></img>
174
- // </div>
175
- <div className={"flex flex-col gap-2 text-sm"}>
176
- {/* <div className="text-base font-bold">{title}</div> */}
177
- {!!products?.length && (
178
- <div className="flex flex-wrap gap-2">
179
- {products?.map(({ name }) => {
180
- const line = getLine(name, lines);
181
- return (
182
- <>
183
- <div
184
- className={
185
- "w-fit rounded-md bg-black px-[12px] py-[9px] leading-none font-bold text-white"
186
- }
187
- key={name}
188
- style={{
189
- backgroundColor: getBgColor(line.mot, line),
190
- }}
191
- >
192
- {name}
193
- </div>
194
- </>
195
- );
196
- })}
197
- </div>
198
- )}
199
- <div className="text-base font-bold">
200
- {!!start && !end && `ab${toShortDate(new Date(start), true)}`}
201
- {!start && !!end && `bis${toShortDate(new Date(end), true)}`}
202
- {!!start &&
203
- !!end &&
204
- `${toShortDate(new Date(start), true)} - ${toShortDate(new Date(end), true)}`}
205
- </div>
206
- <div className={"flex flex-col gap-2"}>
207
- <p className={"text-base"}>{summary}</p>
208
- <p className={"text-base"}>{descriptionDe}</p>
209
-
210
- {[
211
- {
212
- content: recommendation,
213
- label: "recommendation",
214
- },
215
- {
216
- content: reason,
217
- label: "reason",
218
- },
219
- {
220
- content: consequence,
221
- label: "consequence",
222
- },
223
- ].map((item) => {
224
- if (!item.content) {
225
- return null;
226
- }
227
- return (
228
- <div key={item.content}>
229
- {!!item.label && <p className="font-bold">{t(item.label)}:</p>}
230
- <p>{item.content}</p>
231
- </div>
232
- );
233
- })}
234
- </div>
235
- </div>
236
- // </div>
237
- // <div className={"flex flex-col gap-4 text-sm"}>
238
- // <div>
239
- // <div className="text-base font-bold">{title}</div>
240
- // <div className="text-xs">
241
- // {start} - {end}
242
- // </div>
243
- // </div>
244
- // <div
245
- // className={"flex flex-col gap-2"}
246
- // dangerouslySetInnerHTML={{
247
- // __html: converter.makeHtml(description).replace("<hr />", "<br />"),
248
- // }}
249
- // ></div>
250
- // {externalLinks?.map(({ label_de: label, uri }) => {
251
- // return (
252
- // <RvfLink href={uri} key={uri}>
253
- // {label}
254
- // </RvfLink>
255
- // );
256
- // })}
257
- // {!!products?.length && (
258
- // <>
259
- // <div className={"font-bold"}>Betroffene Lines:</div>
260
- // <div className={"flex flex-wrap gap-1"}>
261
- // {products?.map(({ name }) => {
262
- // return (
263
- // <div
264
- // className={
265
- // "rounded-md bg-red px-[12px] py-[9px] font-bold leading-none text-white"
266
- // }
267
- // key={name}
268
- // >
269
- // {name}
270
- // </div>
271
- // );
272
- // })}
273
- // </div>
274
- // </>
275
- // )}
276
- // </div>
277
- );
278
- }
23
+ const { situation } = feature.getProperties();
279
24
 
280
25
  // moco export v2
281
- let textualContent: Partial<TextualContentType> = {};
282
- let timeIntervalsToDisplay = [];
283
- let publicationsToDisplay: PublicationType[] = [];
284
- let reasonsToDisplay: string[] = [];
26
+ let situationParsed: Partial<SituationType> = {};
285
27
 
286
28
  try {
287
- const situationParsed: SituationType = JSON.parse(situation) || {};
288
- const publicationsArr: PublicationType[] =
289
- situationParsed?.publications || [];
290
-
291
- // Find the current publication(s) at the current date
292
- publicationsToDisplay =
293
- publicationsArr?.filter(({ publicationWindows }) => {
294
- return publicationWindows.find(({ endTime, startTime }) => {
295
- const now = new Date();
296
- const startT = new Date(startTime);
297
- const endT = new Date(endTime);
298
- return startT <= now && now <= endT;
299
- });
300
- }) || [];
301
-
302
- // Display the current and next affected time intervals not the one in the past
303
- timeIntervalsToDisplay =
304
- (situationParsed?.affectedTimeIntervals || []).filter(
305
- ({ endTime, startTime }) => {
306
- const now = new Date();
307
- const startT = new Date(startTime);
308
- const endT = new Date(endTime);
309
- return (startT <= now && now <= endT) || now < startT;
310
- },
311
- ) || [];
312
-
313
- // Display the reasons
314
- reasonsToDisplay = (situationParsed?.reasons || []).map(({ name }) => {
315
- return name;
316
- });
29
+ situationParsed = JSON.parse(situation) || {};
317
30
  } catch (e) {
318
31
  // eslint-disable-next-line no-console
319
32
  console.error("Failed to parse publications", e);
@@ -321,161 +34,7 @@ function NotificationDetails({
321
34
 
322
35
  return (
323
36
  <ShadowOverflow {...props} className={twMerge("px-4 text-base", className)}>
324
- <div>
325
- {publicationsToDisplay?.map(
326
- ({
327
- id,
328
- publicationLines,
329
- publicationStops,
330
- textualContentLarge,
331
- textualContentMedium,
332
- textualContentSmall,
333
- }) => {
334
- // Get the textual content in German
335
- textualContent = (
336
- textualContentLarge ||
337
- textualContentMedium ||
338
- textualContentSmall
339
- )?.de;
340
-
341
- const pubLines =
342
- publicationLines?.flatMap((publication) => {
343
- return (
344
- publication.lines?.map(({ name }) => {
345
- return name;
346
- }) || []
347
- );
348
- }) || [];
349
-
350
- const stations = publicationStops.map((publication) => {
351
- return publication?.name || "";
352
- });
353
-
354
- return (
355
- <div className={"text-base"} key={id}>
356
- <div className="text-xs uppercase">{reasonsToDisplay}</div>
357
- <h3 className="space-x-2 text-lg font-bold text-balance">
358
- <span
359
- className={"line-height-[1.3] inline-block align-middle"}
360
- >
361
- <Warning />
362
- </span>
363
- <span
364
- className={"*:inline"}
365
- dangerouslySetInnerHTML={{
366
- __html: textualContent?.summary,
367
- }}
368
- ></span>
369
- </h3>
370
- <hr className="my-1" />
371
-
372
- {timeIntervalsToDisplay?.map(
373
- ({
374
- dailyEndTime,
375
- dailyStartTime,
376
- endTime,
377
- startTime,
378
- }: AffectedTimeIntervalType) => {
379
- const hasDailyTime = dailyEndTime && dailyStartTime;
380
- const isStartCurrentYear =
381
- new Date().getFullYear() ===
382
- new Date(startTime).getFullYear();
383
- const isEndCurrentYear =
384
- new Date().getFullYear() ===
385
- new Date(endTime).getFullYear();
386
- const isEndInfinite = endTime.includes("2500");
387
-
388
- return (
389
- <div
390
- className="text-sm font-bold text-balance"
391
- key={startTime}
392
- >
393
- <span>
394
- {t("from_to", {
395
- from: toShortDate(
396
- new Date(startTime),
397
- !hasDailyTime,
398
- !isStartCurrentYear,
399
- ),
400
- to: !isEndInfinite
401
- ? toShortDate(
402
- new Date(endTime),
403
- !hasDailyTime,
404
- !isEndCurrentYear,
405
- )
406
- : undefined,
407
- })}
408
- </span>
409
- {hasDailyTime && (
410
- <span>{` (${t("daily_from_to", {
411
- from: dailyStartTime,
412
- to: dailyEndTime,
413
- })})`}</span>
414
- )}
415
- </div>
416
- );
417
- },
418
- )}
419
- <div
420
- className="mt-4"
421
- dangerouslySetInnerHTML={{
422
- __html:
423
- textualContent?.description || t("no_details_available"),
424
- }}
425
- />
426
- {!!pubLines?.length && (
427
- <div>
428
- <br />
429
- <div className={"font-bold"}>{t("affected_lines")}:</div>
430
- <div className={"flex flex-wrap gap-1 text-sm"}>
431
- {pubLines?.map((name) => {
432
- return (
433
- <div
434
- className={
435
- "bg-red rounded-md px-2 py-1 font-bold text-white"
436
- }
437
- key={name}
438
- >
439
- {name}
440
- </div>
441
- );
442
- })}
443
- </div>
444
- </div>
445
- )}
446
- <div>
447
- <br />
448
- <div className={"font-bold"}>{t("affected_stops")}:</div>
449
- <div className={"flex flex-wrap gap-1 text-sm"}>
450
- {stations?.length ? (
451
- stations.map((name) => {
452
- return (
453
- <div
454
- className={
455
- "bg-red rounded-md px-2 py-1 font-bold text-white"
456
- }
457
- key={name}
458
- >
459
- {name}
460
- </div>
461
- );
462
- })
463
- ) : (
464
- <div
465
- className={
466
- "bg-red rounded-md px-2 py-1 font-bold text-white"
467
- }
468
- >
469
- {t("all_line_stops")}
470
- </div>
471
- )}
472
- </div>
473
- </div>
474
- </div>
475
- );
476
- },
477
- )}
478
- </div>
37
+ <SituationDetails situation={situationParsed} />
479
38
  </ShadowOverflow>
480
39
  );
481
40
  }
@@ -1,6 +1,7 @@
1
1
  import Automat from "../../icons/Automat";
2
2
  import InPerson from "../../icons/InPerson";
3
3
  import Video from "../../icons/Video";
4
+ import ShadowOverflow from "../../ShadowOverflow";
4
5
 
5
6
  import type { Feature } from "ol";
6
7
 
@@ -23,24 +24,26 @@ function RvfSellingPointDetails({ feature }: { feature: Feature }) {
23
24
  zip_code: zip,
24
25
  } = feature.getProperties();
25
26
  return (
26
- <div className="flex flex-col gap-4">
27
- <div className="flex gap-2">
28
- <span className={"min-w-[26px]"}>
29
- {ICON_BY_OPERATED_BY[operatedBy]}
30
- </span>
31
- <span>{name}</span>
27
+ <ShadowOverflow className="p-4 text-base">
28
+ <div className="flex flex-col gap-4">
29
+ <div className="flex gap-2">
30
+ <span className={"min-w-[26px]"}>
31
+ {ICON_BY_OPERATED_BY[operatedBy]}
32
+ </span>
33
+ <span>{name}</span>
34
+ </div>
35
+ <div className="flex flex-col">
36
+ {phone && <div>{phone}</div>}
37
+ {street && <div>{street}</div>}
38
+ {(zip || city) && (
39
+ <div className="flex gap-2">
40
+ <span>{zip}</span>
41
+ <span>{city}</span>
42
+ </div>
43
+ )}
44
+ </div>
32
45
  </div>
33
- <div className="flex flex-col">
34
- {phone && <div>{phone}</div>}
35
- {street && <div>{street}</div>}
36
- {(zip || city) && (
37
- <div className="flex gap-2">
38
- <span>{zip}</span>
39
- <span>{city}</span>
40
- </div>
41
- )}
42
- </div>
43
- </div>
46
+ </ShadowOverflow>
44
47
  );
45
48
  }
46
49