@geops/rvf-mobility-web-component 0.1.41 → 0.1.42

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 (40) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/index.js +148 -272
  3. package/package.json +1 -1
  4. package/src/NotificationLayer/NotificationLayer.tsx +1 -0
  5. package/src/NotificationLayer/notificationUtils.ts +234 -160
  6. package/src/RvfFeatureDetails/RvfLineNetworkDetails/RvfLineNetworkDetails.tsx +172 -131
  7. package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +198 -44
  8. package/src/icons/disruption-type-banner.xcf +0 -0
  9. package/src/icons/sbb_add_stop.png +0 -0
  10. package/src/icons/sbb_add_stop.svg +6 -0
  11. package/src/icons/sbb_alternative.png +0 -0
  12. package/src/icons/sbb_alternative.svg +6 -0
  13. package/src/icons/sbb_cancellation.png +0 -0
  14. package/src/icons/sbb_cancellation.svg +7 -0
  15. package/src/icons/sbb_construction.png +0 -0
  16. package/src/icons/sbb_construction.svg +6 -0
  17. package/src/icons/sbb_construction_banner.png +0 -0
  18. package/src/icons/sbb_delay.png +0 -0
  19. package/src/icons/sbb_delay.svg +6 -0
  20. package/src/icons/sbb_disruption.png +0 -0
  21. package/src/icons/sbb_disruption.svg +6 -0
  22. package/src/icons/sbb_disruption_banner.png +0 -0
  23. package/src/icons/sbb_info.png +0 -0
  24. package/src/icons/sbb_info.svg +6 -0
  25. package/src/icons/sbb_missed_connection.png +0 -0
  26. package/src/icons/sbb_missed_connection.svg +6 -0
  27. package/src/icons/sbb_missed_connection_banner.png +0 -0
  28. package/src/icons/sbb_platform_change.png +0 -0
  29. package/src/icons/sbb_platform_change.svg +7 -0
  30. package/src/icons/sbb_replacementbus.png +0 -0
  31. package/src/icons/sbb_replacementbus.svg +6 -0
  32. package/src/icons/sbb_replacementbus_banner.png +0 -0
  33. package/src/icons/sbb_reroute.png +0 -0
  34. package/src/icons/sbb_reroute.svg +7 -0
  35. package/src/icons/warning-banner.xcf +0 -0
  36. package/src/utils/addSourceAndLayers.ts +51 -5
  37. package/src/utils/{getFeatureInformationTitle.ts → getFeatureInformationTitle.tsx} +14 -2
  38. package/src/utils/i18n.ts +6 -5
  39. package/src/utils/sharingGraphqlUtils.ts +16 -9
  40. package/src/utils/translations.ts +16 -0
@@ -1,3 +1,4 @@
1
+ import { VectorTileSource } from "maplibre-gl";
1
2
  import { RealtimeLine } from "mobility-toolbox-js/types";
2
3
  import { Feature } from "ol";
3
4
  import { useEffect, useMemo, useState } from "preact/hooks";
@@ -19,6 +20,7 @@ interface LineInfo {
19
20
  long_name: string;
20
21
  mot: string;
21
22
  operator_name: string;
23
+ runs: number;
22
24
  short_name: string;
23
25
  text_color: string;
24
26
  }
@@ -31,6 +33,12 @@ interface StopInfo {
31
33
  visibility_level: number;
32
34
  }
33
35
 
36
+ const LNP_SOURCE_ID = "network_plans";
37
+ const LNP_MD_LINES = "geops.lnp.lines";
38
+ const LNP_MD_STOPS = "geops.lnp.stops";
39
+ const RUNS_PROP = "runs";
40
+ const ORIGINAL_LINE_ID_PROP = "original_line_id";
41
+
34
42
  function RvfLineNetworkDetails({
35
43
  feature,
36
44
  features,
@@ -38,46 +46,66 @@ function RvfLineNetworkDetails({
38
46
  feature: Feature;
39
47
  features: Feature[];
40
48
  }) {
41
- const { apikey, mapsurl } = useMapContext();
49
+ const { baseLayer } = useMapContext();
42
50
  const [lineInfos, setLineInfos] = useState<LineInfo[]>(null);
43
51
  const [stopInfos, setStopInfos] = useState<StopInfo[]>(null);
44
52
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
53
  const [stopInfosOpenId, setStopInfosOpenId] = useState<string>(null);
46
54
 
47
55
  useEffect(() => {
48
- const fetchInfos = async () => {
56
+ const source = baseLayer?.mapLibreMap?.getSource(
57
+ LNP_SOURCE_ID,
58
+ ) as VectorTileSource;
59
+ const abortController = new AbortController();
60
+ const fetchInfos = async (url) => {
49
61
  if (!cacheLineInfosById) {
50
- const response = await fetch(
51
- `${mapsurl}/data/network_plans_rvf_prototype.json?key=${apikey}`,
52
- );
62
+ const response = await fetch(url, { signal: abortController.signal });
53
63
  const data = await response.json();
54
- cacheLineInfosById = data["geops.lnp.lines"];
55
- cacheStopInfosById = data["geops.lnp.stops"];
64
+ cacheLineInfosById = data[LNP_MD_LINES];
65
+ cacheStopInfosById = data[LNP_MD_STOPS];
56
66
  }
57
67
  setLineInfos(cacheLineInfosById);
58
68
  setStopInfos(cacheStopInfosById);
59
69
  };
60
- fetchInfos();
61
- }, [apikey, mapsurl]);
70
+ if (source?.url) {
71
+ fetchInfos(source?.url);
72
+ }
73
+ return () => {
74
+ abortController?.abort();
75
+ };
76
+ }, [baseLayer?.mapLibreMap]);
62
77
 
63
78
  const lineInfosByOperator: Record<string, LineInfo[]> = useMemo(() => {
64
79
  const byOperators = {};
80
+
65
81
  [
66
82
  ...new Set(
67
83
  features.map((f) => {
68
- return f.get("original_line_id");
84
+ return f.get(ORIGINAL_LINE_ID_PROP);
69
85
  }),
70
86
  ),
71
87
  ]
72
88
  .filter((id) => {
73
89
  return !!id && !!lineInfos?.[id];
74
90
  })
75
- .map((id) => {
91
+ .forEach((id) => {
76
92
  const { operator_name: operatorName } = lineInfos[id];
77
93
  if (!byOperators[operatorName]) {
78
94
  byOperators[operatorName] = [];
95
+ byOperators[operatorName].runs = 0;
79
96
  }
80
97
  lineInfos[id].id = id;
98
+
99
+ const runs = features
100
+ .filter((f) => {
101
+ return f.get(ORIGINAL_LINE_ID_PROP) === id;
102
+ })
103
+ .reduce((acc, feature) => {
104
+ return acc + feature.get(RUNS_PROP);
105
+ }, 0);
106
+ lineInfos[id].id = id;
107
+ lineInfos[id].runs = runs;
108
+ byOperators[operatorName].runs += runs;
81
109
  byOperators[operatorName].push(lineInfos[id]);
82
110
  });
83
111
 
@@ -88,7 +116,7 @@ function RvfLineNetworkDetails({
88
116
  const stopInfoIdsByLineId: Record<string, string[]> = useMemo(() => {
89
117
  const byLineId = {};
90
118
  features.forEach((f) => {
91
- const lineId = f.get("original_line_id");
119
+ const lineId = f.get(ORIGINAL_LINE_ID_PROP);
92
120
  if (lineId && !byLineId[lineId]) {
93
121
  try {
94
122
  byLineId[lineId] = JSON.parse(f.get("stop_ids"));
@@ -107,129 +135,142 @@ function RvfLineNetworkDetails({
107
135
 
108
136
  return (
109
137
  <div className="flex flex-col gap-4">
110
- {Object.entries(lineInfosByOperator).map(([operatorName, linesInfos]) => {
111
- return (
112
- <div className={"flex flex-col gap-2"} key={operatorName}>
113
- <div>{operatorName}</div>
114
- {linesInfos
115
- .sort((a, b) => {
116
- return a.short_name?.localeCompare(b.short_name);
117
- })
118
- .map((lineInfo) => {
119
- const {
120
- color: backgroundColor,
121
- id,
122
- // color,
123
- // external_id,
124
- long_name,
125
- mot,
126
- short_name: shortName,
127
- text_color: textColor,
128
- } = lineInfo;
129
- let longName = long_name;
130
-
131
- let stops = null;
132
- //stopInfoIdsByLineId?.[id] || null;
133
- if (!stops?.length) {
134
- stops = null;
135
- }
136
- console.log("stops", stops, longName);
137
- if (!longName && stops) {
138
- const names = stops.map((stopId) => {
139
- return stopInfos[stopId].short_name;
140
- });
141
- console.log("stops", names);
142
-
143
- longName = [
144
- ...new Set([names[0], names[names.length - 1]]),
145
- ].join(" - ");
146
- }
147
-
148
- // Build a line object
149
- const line: { type: string } & RealtimeLine = {
150
- color: null,
151
- id: null,
152
- name: shortName,
153
- stroke: null,
154
- text_color: null,
155
- type: mot,
156
- };
157
-
158
- if (textColor) {
159
- line.text_color =
160
- textColor[0] === "#" ? textColor : "#" + textColor;
161
- }
162
-
163
- if (backgroundColor) {
164
- line.color =
165
- backgroundColor[0] === "#"
166
- ? backgroundColor
167
- : "#" + backgroundColor;
168
- }
169
-
170
- return (
171
- <div key={shortName}>
172
- <div
173
- className={
174
- "flex w-full items-center justify-between gap-2"
175
- }
176
- // onClick={() => {
177
- // setStopInfosOpenId(stopInfosOpenId === id ? null : id);
178
- // }}
179
- >
180
- <div>
181
- <RouteIcon line={line}></RouteIcon>
138
+ {Object.entries(lineInfosByOperator)
139
+ .sort(([operatorNameA], [operatorNameB]) => {
140
+ return lineInfosByOperator[operatorNameA].runs <
141
+ lineInfosByOperator[operatorNameB].runs
142
+ ? 1
143
+ : -1;
144
+ })
145
+ .map(([operatorName, linesInfos]) => {
146
+ return (
147
+ <div className={"flex flex-col gap-2"} key={operatorName}>
148
+ <div>{operatorName}</div>
149
+ {linesInfos
150
+ .sort((a, b) => {
151
+ return a.runs < b.runs ? 1 : -1;
152
+ })
153
+ .map((lineInfo) => {
154
+ const {
155
+ color: backgroundColor,
156
+ id,
157
+ // color,
158
+ // external_id,
159
+ long_name,
160
+ mot,
161
+ // runs,
162
+ short_name: shortName,
163
+ text_color: textColor,
164
+ } = lineInfo;
165
+ let longName = long_name;
166
+
167
+ let stops = null;
168
+ //stopInfoIdsByLineId?.[id] || null;
169
+ if (!stops?.length) {
170
+ stops = null;
171
+ }
172
+
173
+ if (!longName && stops) {
174
+ const names = stops.map((stopId) => {
175
+ return stopInfos[stopId].short_name;
176
+ });
177
+
178
+ longName = [
179
+ ...new Set([names[0], names[names.length - 1]]),
180
+ ].join(" - ");
181
+ }
182
+
183
+ // Build a line object
184
+ const line: { type: string } & RealtimeLine = {
185
+ color: null,
186
+ id: null,
187
+ name: shortName,
188
+ stroke: null,
189
+ text_color: null,
190
+ type: mot,
191
+ };
192
+
193
+ if (textColor) {
194
+ line.text_color =
195
+ textColor[0] === "#" ? textColor : "#" + textColor;
196
+ }
197
+
198
+ if (backgroundColor) {
199
+ line.color =
200
+ backgroundColor[0] === "#"
201
+ ? backgroundColor
202
+ : "#" + backgroundColor;
203
+ }
204
+
205
+ return (
206
+ <div key={shortName}>
207
+ <div
208
+ className={
209
+ "flex w-full items-center justify-between gap-2"
210
+ }
211
+ // onClick={() => {
212
+ // setStopInfosOpenId(stopInfosOpenId === id ? null : id);
213
+ // }}
214
+ >
215
+ <div>
216
+ <RouteIcon line={line}></RouteIcon>
217
+ </div>
218
+ {!!longName && (
219
+ <div className={"flex-1 text-left"}>{longName}</div>
220
+ )}
221
+ {/* <div className={"text-xs"}>{runs}</div> */}
222
+
223
+ {!!stops && (
224
+ <button className={"shrink-0"}>
225
+ {stopInfosOpenId === id ? (
226
+ <ArrowUp />
227
+ ) : (
228
+ <ArrowDown />
229
+ )}
230
+ </button>
231
+ )}
182
232
  </div>
183
- {!!longName && (
184
- <div className={"flex-1 text-left"}>{longName}</div>
185
- )}
186
233
  {!!stops && (
187
- <button className={"shrink-0"}>
188
- {stopInfosOpenId === id ? <ArrowUp /> : <ArrowDown />}
189
- </button>
234
+ <div
235
+ className={`${stopInfosOpenId === id ? "" : "hidden"}`}
236
+ >
237
+ {stops?.map((stopId, index, arr) => {
238
+ const stop = stopInfos[stopId];
239
+ return (
240
+ <div
241
+ className={"flex items-center gap-2"}
242
+ key={stopId}
243
+ >
244
+ <RouteStopContext.Provider
245
+ value={{
246
+ index,
247
+ status: {
248
+ isFirst: !index,
249
+ isLast: index === arr.length - 1,
250
+ isLeft: false,
251
+ isPassed: false,
252
+ progress: !index ? 50 : 0,
253
+ },
254
+ stop,
255
+ }}
256
+ >
257
+ <RouteStopProgress
258
+ className="relative flex size-8 shrink-0 items-center justify-center"
259
+ lineColor={line.color}
260
+ />
261
+ <div>{stop.short_name}</div>
262
+ </RouteStopContext.Provider>
263
+ </div>
264
+ );
265
+ })}
266
+ </div>
190
267
  )}
191
268
  </div>
192
- {!!stops && (
193
- <div
194
- className={`${stopInfosOpenId === id ? "" : "hidden"}`}
195
- >
196
- {stops?.map((stopId, index, arr) => {
197
- const stop = stopInfos[stopId];
198
- return (
199
- <div
200
- className={"flex items-center gap-2"}
201
- key={stopId}
202
- >
203
- <RouteStopContext.Provider
204
- value={{
205
- index,
206
- status: {
207
- isFirst: !index,
208
- isLast: index === arr.length - 1,
209
- isLeft: false,
210
- isPassed: false,
211
- progress: !index ? 50 : 0,
212
- },
213
- stop,
214
- }}
215
- >
216
- <RouteStopProgress
217
- className="relative flex size-8 shrink-0 items-center justify-center"
218
- lineColor={line.color}
219
- />
220
- <div>{stop.short_name}</div>
221
- </RouteStopContext.Provider>
222
- </div>
223
- );
224
- })}
225
- </div>
226
- )}
227
- </div>
228
- );
229
- })}
230
- </div>
231
- );
232
- })}
269
+ );
270
+ })}
271
+ </div>
272
+ );
273
+ })}
233
274
  </div>
234
275
  );
235
276
  }
@@ -1,80 +1,234 @@
1
1
  import { Feature } from "ol";
2
- import showdown from "showdown";
3
2
 
4
- import RvfLink from "../../RvfLink";
5
- const converter = new showdown.Converter();
3
+ // import RvfLink from "../../RvfLink";
4
+ // import { icons } from "../../utils/addSourceAndLayers";
5
+ import getBgColor from "../../utils/getBgColor";
6
+ import useI18n from "../../utils/hooks/useI18n";
7
+
8
+ const toShortDate = (date, showTime) => {
9
+ const time = date.toLocaleTimeString(["de"], {
10
+ hour: "2-digit",
11
+ minute: "2-digit",
12
+ });
13
+ const dateString = date.toLocaleDateString(["de"], {
14
+ day: "2-digit",
15
+ month: "short",
16
+ weekday: "short",
17
+ });
18
+
19
+ return `${dateString}${showTime && showTime !== time ? ` ${time}` : ""}`
20
+ .replace(",", "")
21
+ .replace(/\./, "")
22
+ .replace(/\.$/, "");
23
+ };
24
+
25
+ let rvfLines = null;
26
+ fetch("https://tralis-tracker-api.dev.geops.io/api/lines/rvf/")
27
+ .then((res) => {
28
+ return res.json();
29
+ })
30
+ .then((data) => {
31
+ rvfLines = data;
32
+ });
33
+ const getLine = (name) => {
34
+ if (rvfLines) {
35
+ const line = rvfLines.find((line) => {
36
+ return line.name === name;
37
+ });
38
+ if (line) {
39
+ return line;
40
+ }
41
+ }
42
+ return { mot: "bus", name };
43
+ };
44
+
6
45
  // text = "# hello, markdown!",
7
46
  // html = converter.makeHtml(text);
8
47
  function RvfNotificationDetails({ feature }: { feature: Feature }) {
48
+ const { t } = useI18n();
49
+
9
50
  const {
10
51
  affected_products: affectedProducts,
11
52
  affected_time_intervals: timeIntervals,
12
- links,
13
- long_description: description,
14
- title,
53
+ consequence_de: consequence,
54
+ description_de: descriptionDe,
55
+ // disruption_type: disruptionType,
56
+ // duration_text_de: durationText,
57
+ // links,
58
+ // long_description: description,
59
+ reason_de: reason,
60
+ recommendation_de: recommendation,
61
+ summary_de: summary,
62
+ // title,
15
63
  } = feature.getProperties();
16
64
 
65
+ // "title_de": "",
66
+ // "title_fr": "",
67
+ // "title_it": "",
68
+ // "title_en": "",
69
+ // "summary_de": "",
70
+ // "summary_fr": "",
71
+ // "summary_it": "",
72
+ // "summary_en": "",
73
+ // "reason_de": "Baustelle",
74
+ // "reason_fr": "",
75
+ // "reason_it": "",
76
+ // "reason_en": "",
77
+ // "description_de": "",
78
+ // "description_fr": "",
79
+ // "description_it": "",
80
+ // "description_en": "",
81
+ // "consequence_de": "Umleitung",
82
+ // "consequence_fr": "",
83
+ // "consequence_it": "",
84
+ // "consequence_en": "",
85
+ // "duration_text_de": "",
86
+ // "duration_text_fr": "",
87
+ // "duration_text_it": "",
88
+ // "duration_text_en": "",
89
+ // "recommendation_de": "",
90
+ // "recommendation_fr": "",
91
+ // "recommendation_it": "",
92
+ // "recommendation_en": "",
93
+ // "reasons": [
94
+ // "Ausfall des Aufzuges"
95
+ // ]
96
+
17
97
  let end = "",
18
98
  start = "";
19
99
  let products = [];
20
- let externalLinks = [];
100
+ // let externalLinks = [];
101
+
21
102
  try {
22
103
  const timeInterval = JSON.parse(timeIntervals)?.[0] || {};
23
- const dateStart = new Date(timeInterval.start);
24
- start =
25
- dateStart.toLocaleDateString() + " " + dateStart.toLocaleTimeString();
26
- const dateEnd = new Date(timeInterval.end);
27
- end = dateEnd.toLocaleDateString() + " " + dateEnd.toLocaleTimeString();
104
+ start = timeInterval.start;
105
+ end = timeInterval.end;
106
+ // const dateStart = new Date(timeInterval.start);
107
+ // start =
108
+ // dateStart.toLocaleDateString() + " " + dateStart.toLocaleTimeString();
109
+ // const dateEnd = new Date(timeInterval.end);
110
+ // console.log("dateEnd", dateEnd);
111
+ // end = dateEnd.toLocaleDateString() + " " + dateEnd.toLocaleTimeString();
28
112
 
29
113
  products = JSON.parse(affectedProducts);
30
- products.sort((a, b) => {
114
+ products?.sort((a, b) => {
31
115
  return a.name.localeCompare(b.name);
32
116
  });
33
- externalLinks = JSON.parse(links);
117
+ // externalLinks = JSON.parse(links);
34
118
  } catch (e) {
35
119
  console.error(e);
36
120
  }
37
121
  return (
38
- <div className={"flex flex-col gap-4 text-sm"}>
39
- <div>
40
- <div className="text-base font-bold">{title}</div>
41
- <div className="text-xs">
42
- {start} - {end}
43
- </div>
44
- </div>
45
- <div
46
- className={"flex flex-col gap-2"}
47
- dangerouslySetInnerHTML={{
48
- __html: converter.makeHtml(description).replace("<hr />", "<br />"),
49
- }}
50
- ></div>
51
- {externalLinks?.map(({ label, uri }) => {
52
- return (
53
- <RvfLink href={uri} key={uri}>
54
- {label}
55
- </RvfLink>
56
- );
57
- })}
122
+ // <div className={"flex gap-2 text-sm"}>
123
+ // <div className="min-w-8 shrink-0 grow-0">
124
+ // <img
125
+ // alt={disruptionType}
126
+ // className={"w-8"}
127
+ // src={icons[disruptionType]}
128
+ // ></img>
129
+ // </div>
130
+ <div className={"flex flex-col gap-2 text-sm"}>
131
+ {/* <div className="text-base font-bold">{title}</div> */}
58
132
  {!!products?.length && (
59
- <>
60
- <div className={"font-bold"}>Betroffene Lines:</div>
61
- <div className={"flex flex-wrap gap-1"}>
62
- {products?.map(({ name }) => {
63
- return (
133
+ <div className="flex flex-wrap gap-2">
134
+ {products?.map(({ name }) => {
135
+ const line = getLine(name);
136
+ return (
137
+ <>
64
138
  <div
65
139
  className={
66
- "rounded-md bg-red px-[12px] py-[9px] font-bold leading-none text-white"
140
+ "w-fit rounded-md bg-red px-[12px] py-[9px] font-bold leading-none text-white"
67
141
  }
68
142
  key={name}
143
+ style={{
144
+ backgroundColor: getBgColor(line.mot, line),
145
+ }}
69
146
  >
70
147
  {name}
71
148
  </div>
72
- );
73
- })}
74
- </div>
75
- </>
149
+ </>
150
+ );
151
+ })}
152
+ </div>
76
153
  )}
154
+ <div className="text-base font-bold">
155
+ {!!start && !end && "ab" + toShortDate(new Date(start), true)}
156
+ {!start && !!end && "bis" + toShortDate(new Date(end), true)}
157
+ {!!start &&
158
+ !!end &&
159
+ `${toShortDate(new Date(start), true)} - ${toShortDate(new Date(end), true)}`}
160
+ </div>
161
+ <div className={"flex flex-col gap-2"}>
162
+ <p className={"text-base"}>{summary}</p>
163
+ <p className={"text-base"}>{descriptionDe}</p>
164
+
165
+ {[
166
+ {
167
+ content: recommendation,
168
+ label: "recommendation",
169
+ },
170
+ {
171
+ content: reason,
172
+ label: "reason",
173
+ },
174
+ {
175
+ content: consequence,
176
+ label: "consequence",
177
+ },
178
+ ].map((item) => {
179
+ if (!item.content) {
180
+ return null;
181
+ }
182
+ return (
183
+ <div key={item.content}>
184
+ {!!item.label && <p className="font-bold">{t(item.label)}:</p>}
185
+ <p>{item.content}</p>
186
+ </div>
187
+ );
188
+ })}
189
+ </div>
77
190
  </div>
191
+ // </div>
192
+ // <div className={"flex flex-col gap-4 text-sm"}>
193
+ // <div>
194
+ // <div className="text-base font-bold">{title}</div>
195
+ // <div className="text-xs">
196
+ // {start} - {end}
197
+ // </div>
198
+ // </div>
199
+ // <div
200
+ // className={"flex flex-col gap-2"}
201
+ // dangerouslySetInnerHTML={{
202
+ // __html: converter.makeHtml(description).replace("<hr />", "<br />"),
203
+ // }}
204
+ // ></div>
205
+ // {externalLinks?.map(({ label_de: label, uri }) => {
206
+ // return (
207
+ // <RvfLink href={uri} key={uri}>
208
+ // {label}
209
+ // </RvfLink>
210
+ // );
211
+ // })}
212
+ // {!!products?.length && (
213
+ // <>
214
+ // <div className={"font-bold"}>Betroffene Lines:</div>
215
+ // <div className={"flex flex-wrap gap-1"}>
216
+ // {products?.map(({ name }) => {
217
+ // return (
218
+ // <div
219
+ // className={
220
+ // "rounded-md bg-red px-[12px] py-[9px] font-bold leading-none text-white"
221
+ // }
222
+ // key={name}
223
+ // >
224
+ // {name}
225
+ // </div>
226
+ // );
227
+ // })}
228
+ // </div>
229
+ // </>
230
+ // )}
231
+ // </div>
78
232
  );
79
233
  }
80
234
 
Binary file
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
2
+ <g fill="none" fill-rule="evenodd">
3
+ <rect width="16" height="16" fill="#EB0000" rx="2"/>
4
+ <path fill="#FFF" d="M8.559,10.646 C8.331,10.707 8.097,10.75 7.85,10.75 C7.573,10.75 7.311,10.697 7.059,10.62 C5.93,10.278 5.1,9.24 5.1,8 C5.1,6.761 5.93,5.722 7.059,5.38 C7.311,5.304 7.573,5.25 7.85,5.25 C8.097,5.25 8.331,5.293 8.559,5.355 C9.731,5.67 10.6,6.73 10.6,8 C10.6,9.27 9.731,10.331 8.559,10.646 M8.559,3.822 L8.559,0 L7.059,0 L7.059,3.83 C5.093,4.203 3.6,5.928 3.6,8 C3.6,10.073 5.093,11.798 7.059,12.17 L7.059,16 L8.559,16 L8.559,12.179 C10.565,11.839 12.1,10.101 12.1,8 C12.1,5.9 10.565,4.162 8.559,3.822"/>
5
+ </g>
6
+ </svg>
Binary file