@geops/rvf-mobility-web-component 0.1.20 → 0.1.22

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/package.json CHANGED
@@ -2,11 +2,13 @@
2
2
  "name": "@geops/rvf-mobility-web-component",
3
3
  "license": "UNLICENSED",
4
4
  "description": "Web components for rvf in the domains of mobility and logistics.",
5
- "version": "0.1.20",
5
+ "version": "0.1.22",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
+ "graphql": "^16.10.0",
11
+ "graphql-request": "^7.1.2",
10
12
  "jspdf": "^2.5.2",
11
13
  "lodash.debounce": "^4.0.8",
12
14
  "maplibre-gl": "^4.7.1",
@@ -25,7 +25,7 @@ function Overlay({ children, ScrollableHandlerProps = {} }: OverlayProps) {
25
25
 
26
26
  return (
27
27
  <div
28
- className={`relative z-50 flex flex-col overflow-hidden transition-[min-height,max-height] @lg:transition-[width] ${
28
+ className={`relative z-50 flex flex-col overflow-hidden bg-white transition-[min-height,max-height] @lg:transition-[width] ${
29
29
  children
30
30
  ? "max-h-[70%] min-h-[75px] w-full border-t @lg:h-[100%!important] @lg:max-h-full @lg:w-[350px] @lg:border-r @lg:border-t-0"
31
31
  : "max-h-0 min-h-0 @lg:w-0"
@@ -38,11 +38,12 @@ function FloatingVehiclesDetails({ features }: FloatingVehiclesDetailsProps) {
38
38
  return null;
39
39
  }
40
40
  return (
41
- <li className="p-1" key={idx}>
42
- <a href={scooterLink} key={idx} rel="noreferrer" target="_blank">
41
+ <li key={idx}>
42
+ <div className={"flex items-center gap-2"}>
43
43
  {`${vehicleNumber}`}
44
44
  {distance}
45
- </a>
45
+ <RvfLink href={scooterLink}>{` jetzt buchen`}</RvfLink>
46
+ </div>
46
47
  </li>
47
48
  );
48
49
  });
@@ -51,18 +52,16 @@ function FloatingVehiclesDetails({ features }: FloatingVehiclesDetailsProps) {
51
52
  const link = getLinkByDevice(features?.[0]);
52
53
 
53
54
  return (
54
- <div className="flex flex-1 flex-col gap-2 overflow-scroll">
55
- <div className="flex flex-1 flex-col gap-2 overflow-scroll">
55
+ <div className="flex flex-1 flex-col gap-2">
56
+ <div className="flex flex-1 flex-col gap-2 ">
56
57
  <div>{features.length} Fahrzeuge</div>
57
- <div className="flex flex-1 flex-col overflow-scroll">
58
+ <div className="flex flex-1 flex-col">
58
59
  <div className="text-grey">Fahrzeug mieten</div>
59
- <ul className="list-disc overflow-scroll pl-4 underline underline-offset-2">
60
- {renderedDetails}
61
- </ul>
60
+ <ul className="list-disc pl-4">{renderedDetails}</ul>
62
61
  </div>
63
62
  </div>
64
63
  {feedId && link && (
65
- <div className="flex justify-center">
64
+ <div>
66
65
  <RvfLink href={link}>Über {PROVIDER_BY_FEED_ID[feedId]}</RvfLink>
67
66
  </div>
68
67
  )}
@@ -1,5 +1,6 @@
1
1
  import { Feature } from "ol";
2
- import { useEffect, useState } from "preact/hooks";
2
+ import { useEffect, useMemo, useState } from "preact/hooks";
3
+ import { Fragment } from "preact/jsx-runtime";
3
4
 
4
5
  import callBike from "../../logos/callabike_logo.png";
5
6
  import flinkster from "../../logos/flinkster_logo.png";
@@ -101,19 +102,50 @@ function RvfSharedMobilityDetails({
101
102
  );
102
103
  };
103
104
 
105
+ const featuresByFeedId: Record<string, Feature[]> = useMemo(() => {
106
+ const featuresByFeedId = {};
107
+ if (isStationDetails) {
108
+ return null;
109
+ }
110
+ features.forEach((feature) => {
111
+ const feedId = feature.get("feed_id");
112
+ if (featuresByFeedId[feedId]) {
113
+ featuresByFeedId[feedId].push(feature);
114
+ } else {
115
+ featuresByFeedId[feedId] = [feature];
116
+ }
117
+ });
118
+ return featuresByFeedId;
119
+ }, [features, isStationDetails]);
120
+
104
121
  return (
105
122
  <>
106
- <img
107
- alt="logo"
108
- className="max-w-24"
109
- src={logos[features[0]?.get("feed_id")]}
110
- />
111
- {features.length && isStationDetails && (
112
- <StationDetails feature={features[0]} />
113
- )}
114
- {!!features[0]?.get("form_factor") && isFloatingVehicle && (
115
- <FloatingVehiclesDetails features={features} />
123
+ {isStationDetails && (
124
+ <>
125
+ <img
126
+ alt="logo"
127
+ className="max-w-24"
128
+ src={logos[features[0]?.get("feed_id")]}
129
+ />
130
+ {features.length && isStationDetails && (
131
+ <StationDetails feature={features[0]} />
132
+ )}
133
+ </>
116
134
  )}
135
+ {isFloatingVehicle &&
136
+ featuresByFeedId &&
137
+ Object.entries(featuresByFeedId).map(([key, feats]) => {
138
+ return (
139
+ <Fragment key={key}>
140
+ <img
141
+ alt="logo"
142
+ className="max-w-24"
143
+ src={logos[feats[0]?.get("feed_id")]}
144
+ />
145
+ <FloatingVehiclesDetails features={feats} />
146
+ </Fragment>
147
+ );
148
+ })}
117
149
  </>
118
150
  );
119
151
  }
@@ -1,29 +1,236 @@
1
+ import { gql, GraphQLClient } from "graphql-request";
1
2
  import { Feature } from "ol";
2
- import { useMemo } from "preact/hooks";
3
+ import { useEffect, useMemo, useState } from "preact/hooks";
3
4
 
4
- import RvfButton from "../../../RvfButton";
5
+ import RvfLink from "../../../RvfLink";
5
6
  import getLinkByDevice from "../../../utils/getLinkByDevice";
6
7
  export interface StationDetailsProps {
7
8
  feature: Feature;
8
9
  }
9
10
 
11
+ const document = gql`
12
+ query station($id: String!) {
13
+ station(id: $id) {
14
+ name {
15
+ translation {
16
+ language
17
+ value
18
+ }
19
+ }
20
+ shortName {
21
+ translation {
22
+ language
23
+ value
24
+ }
25
+ }
26
+ lat
27
+ lon
28
+ region {
29
+ id
30
+ }
31
+ rentalUris {
32
+ android
33
+ ios
34
+ web
35
+ }
36
+ isVirtualStation
37
+ rentalMethods
38
+ parkingHoop
39
+ parkingType
40
+ contactPhone
41
+ address
42
+ rentalMethods
43
+ capacity
44
+ vehicleTypesAvailable {
45
+ count
46
+ vehicleType {
47
+ id
48
+ formFactor
49
+ riderCapacity
50
+ cargoVolumeCapacity
51
+ cargoLoadCapacity
52
+ propulsionType
53
+ ecoLabels {
54
+ countryCode
55
+ ecoSticker
56
+ }
57
+ maxRangeMeters
58
+ name {
59
+ translation {
60
+ language
61
+ value
62
+ }
63
+ }
64
+ description {
65
+ translation {
66
+ language
67
+ value
68
+ }
69
+ }
70
+ vehicleAccessories
71
+ gCO2km
72
+ vehicleImage
73
+ make
74
+ model
75
+ color
76
+ wheelCount
77
+ maxPermittedSpeed
78
+ ratedPower
79
+ defaultReserveTime
80
+ returnConstraint
81
+ vehicleAssets {
82
+ iconUrl
83
+ iconUrlDark
84
+ iconLastModified
85
+ }
86
+ defaultReserveTime
87
+ defaultPricingPlan {
88
+ id
89
+ url
90
+ currency
91
+ isTaxable
92
+ description {
93
+ translation {
94
+ language
95
+ value
96
+ }
97
+ }
98
+ perKmPricing {
99
+ start
100
+ rate
101
+ interval
102
+ end
103
+ }
104
+ perMinPricing {
105
+ start
106
+ rate
107
+ interval
108
+ end
109
+ }
110
+ surgePricing
111
+ }
112
+ }
113
+ count
114
+ }
115
+ vehicleDocksCapacity {
116
+ vehicleTypes {
117
+ id
118
+ }
119
+ count
120
+ }
121
+ numVehiclesAvailable
122
+ numDocksDisabled
123
+ numVehiclesDisabled
124
+ numDocksAvailable
125
+ isInstalled
126
+ isRenting
127
+ isReturning
128
+ pricingPlans {
129
+ id
130
+ url
131
+ currency
132
+ isTaxable
133
+ description {
134
+ translation {
135
+ language
136
+ value
137
+ }
138
+ }
139
+ perKmPricing {
140
+ start
141
+ rate
142
+ interval
143
+ end
144
+ }
145
+ perMinPricing {
146
+ start
147
+ rate
148
+ interval
149
+ end
150
+ }
151
+ surgePricing
152
+ }
153
+ }
154
+ }
155
+ `;
156
+ const client = new GraphQLClient("https://api.mobidata-bw.de/sharing/graphql", {
157
+ method: "GET",
158
+ });
159
+
10
160
  function StationDetails({ feature }: StationDetailsProps) {
161
+ const [data, setData] = useState(null);
11
162
  const link = useMemo(() => {
12
163
  return getLinkByDevice(feature);
13
164
  }, [feature]);
14
165
 
166
+ useEffect(() => {
167
+ const stationId = feature.get("station_id");
168
+ if (stationId) {
169
+ const fetchData = async () => {
170
+ const { station }: { station: unknown } = await client.request(
171
+ document,
172
+ {
173
+ __operation: "station",
174
+ id: feature.get("station_id"),
175
+ },
176
+ );
177
+ setData(station);
178
+ // console.log(station);
179
+ };
180
+ fetchData();
181
+ }
182
+ }, [feature]);
183
+
184
+ const hasVehicles = feature.get("num_vehicles_available") > 0;
185
+
15
186
  return (
16
- <div className="flex flex-col gap-2">
17
- {feature.get("num_vehicles_available")} Fahrzeuge
18
- {link && (
19
- <a
20
- className={"my-10 flex items-center justify-center"}
21
- href={link}
22
- rel="noreferrer"
23
- target="_blank"
24
- >
25
- <RvfButton>Jetzt buchen</RvfButton>
26
- </a>
187
+ // <div className="flex flex-col gap-2">
188
+ // {feature.get("num_vehicles_available")} Fahrzeuge
189
+ // {link && (
190
+ // <a
191
+ // className={"my-10 flex items-center justify-center"}
192
+ // href={link}
193
+ // rel="noreferrer"
194
+ // target="_blank"
195
+ // >
196
+ // <RvfButton>Jetzt buchen</RvfButton>
197
+ // </a>
198
+ // )}
199
+ // </div>
200
+ <div className="flex flex-1 flex-col gap-2">
201
+ <div className="flex flex-1 flex-col gap-2 ">
202
+ <div>{feature.get("num_vehicles_available") || 0} Fahrzeuge</div>
203
+ {hasVehicles && data?.vehicleTypesAvailable?.length && (
204
+ <div className="flex flex-1 flex-col">
205
+ <div className="text-grey">Fahrzeug mieten</div>
206
+ <ul className="list-disc pl-4">
207
+ {data?.vehicleTypesAvailable?.map(
208
+ ({ count, vehicleType: { id, name, vehicleImage } }) => {
209
+ return (
210
+ <li key={id}>
211
+ <div className={"flex items-center gap-2"}>
212
+ <span>{`${count} x ${name.translation[0].value}`}</span>
213
+
214
+ {vehicleImage && (
215
+ <img
216
+ alt="vehicle"
217
+ className={"w-12"}
218
+ src={vehicleImage}
219
+ />
220
+ )}
221
+ </div>
222
+ </li>
223
+ );
224
+ },
225
+ )}
226
+ </ul>
227
+ </div>
228
+ )}
229
+ </div>
230
+ {data?.vehicleTypesAvailable?.length > 0 && link && (
231
+ <div>
232
+ <RvfLink href={link}>Jetzt Buchen</RvfLink>
233
+ </div>
27
234
  )}
28
235
  </div>
29
236
  );
@@ -3,13 +3,15 @@ import type { JSX, PreactDOMAttributes } from "preact";
3
3
  import { memo, useMemo } from "preact/compat";
4
4
  import { twMerge } from "tailwind-merge";
5
5
 
6
+ import ArrowUpRight from "../icons/ArrowUpRight";
7
+
6
8
  export type RvfLinkProps = {
7
9
  theme?: "primary" | "secondary";
8
10
  } & JSX.AnchorHTMLAttributes<HTMLAnchorElement> &
9
11
  PreactDOMAttributes;
10
12
 
11
13
  const baseClasses =
12
- "my-1 flex gap-2 text-[14px] @sm/main:text-[16px] @md/main:text-[18px] items-center justify-left font-semibold text-button";
14
+ "my-1 flex items-center leading-[1.1] underline text-[14px] @sm/main:text-[16px] @md/main:text-[18px] items-center justify-left font-semibold";
13
15
 
14
16
  export const themes = {
15
17
  primary: {
@@ -18,7 +20,7 @@ export const themes = {
18
20
  selectedClasses: "bg-darkred border-darkred",
19
21
  },
20
22
  secondary: {
21
- classes: "bg-white text-grey hover:text-red active:text-lightred",
23
+ classes: "bg-white text-black hover:text-red active:text-lightred",
22
24
  selectedClasses: "text-red",
23
25
  },
24
26
  };
@@ -38,6 +40,7 @@ function RvfLink({
38
40
  return (
39
41
  <a className={classes} rel="noreferrer" target="_blank" {...props}>
40
42
  {children}
43
+ <ArrowUpRight />
41
44
  </a>
42
45
  );
43
46
  }
@@ -0,0 +1,35 @@
1
+ import { SVGProps } from "preact/compat";
2
+
3
+ function ArrowUpRight(props: SVGProps<SVGSVGElement>) {
4
+ return (
5
+ <svg
6
+ height="24px"
7
+ style="transform: rotate(-45deg);"
8
+ version="1.1"
9
+ viewBox="0 0 24 24"
10
+ width="24px"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ {...props}
13
+ >
14
+ <title>iconfont/right</title>
15
+ <g
16
+ fill="none"
17
+ fillRule="evenodd"
18
+ id="iconfont/right"
19
+ stroke="none"
20
+ strokeWidth="1"
21
+ style="&#10; /* transform: rotate(-45deg); */&#10;"
22
+ >
23
+ <path
24
+ d="M13.2928932,5.29289322 C13.6834175,4.90236893 14.3165825,4.90236893 14.7071068,5.29289322 L20.7071068,11.2928932 C20.7355731,11.3213595 20.7623312,11.3515341 20.787214,11.3832499 C20.7927155,11.3901576 20.7982466,11.397397 20.8036654,11.4046934 C20.8215099,11.4288693 20.8382813,11.453725 20.8539326,11.4793398 C20.8613931,11.4913869 20.8685012,11.5036056 20.8753288,11.5159379 C20.8862061,11.5357061 20.8966234,11.5561086 20.9063462,11.5769009 C20.914321,11.5939015 20.9218036,11.6112044 20.9287745,11.628664 C20.9366843,11.6484208 20.9438775,11.6682023 20.9504533,11.6882636 C20.9552713,11.7031487 20.9599023,11.7185367 20.9641549,11.734007 C20.9701664,11.7555635 20.9753602,11.7772539 20.9798348,11.7992059 C20.9832978,11.8166247 20.9863719,11.834051 20.9889822,11.8515331 C20.9962388,11.8996379 21,11.9493797 21,12 L20.9962979,11.9137692 C20.9978436,11.9317345 20.9989053,11.9497336 20.9994829,11.9677454 L21,12 C21,12.0112225 20.9998151,12.0224019 20.9994483,12.0335352 C20.9988772,12.050591 20.997855,12.0679231 20.996384,12.0852242 C20.994564,12.1070574 20.9920941,12.1281144 20.9889807,12.1489612 C20.9863719,12.165949 20.9832978,12.1833753 20.9797599,12.2007258 C20.9753602,12.2227461 20.9701664,12.2444365 20.964279,12.2658396 C20.9599023,12.2814633 20.9552713,12.2968513 20.9502619,12.3121425 C20.9438775,12.3317977 20.9366843,12.3515792 20.928896,12.3710585 C20.9218036,12.3887956 20.914321,12.4060985 20.9063266,12.4232215 C20.8966234,12.4438914 20.8862061,12.4642939 20.8751242,12.484277 C20.8685012,12.4963944 20.8613931,12.5086131 20.8540045,12.5207088 C20.8382813,12.546275 20.8215099,12.5711307 20.8036865,12.5951593 C20.774687,12.6343256 20.7425008,12.6717127 20.7071068,12.7071068 L20.787214,12.6167501 C20.7849289,12.6196628 20.7826279,12.6225624 20.7803112,12.625449 L20.7071068,12.7071068 L14.7071068,18.7071068 C14.3165825,19.0976311 13.6834175,19.0976311 13.2928932,18.7071068 C12.9023689,18.3165825 12.9023689,17.6834175 13.2928932,17.2928932 L17.585,13 L4,13 C3.48716416,13 3.06449284,12.6139598 3.00672773,12.1166211 L3,12 C3,11.4477153 3.44771525,11 4,11 L17.585,11 L13.2928932,6.70710678 C12.9324093,6.34662282 12.9046797,5.77939176 13.2097046,5.38710056 Z"
25
+ fill="currentColor"
26
+ fillRule="nonzero"
27
+ id="Combined-Shape"
28
+ style="&#10; /* transform: rotate(-45deg); */&#10;"
29
+ />
30
+ </g>
31
+ </svg>
32
+ );
33
+ }
34
+
35
+ export default ArrowUpRight;
@@ -0,0 +1 @@
1
+ export { default } from "./ArrowUpRight";
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>iconfont/up-open</title>
4
+ <g id="iconfont/up-open" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <path d="M15.7389427,5.27697525 C16.129467,5.66749954 16.129467,6.30066452 15.7389427,6.69118881 L10.414,12.015082 L15.7389427,17.3406471 C16.0994267,17.7011311 16.1271562,18.2683621 15.8221313,18.6606533 L15.7389427,18.7548607 C15.3484184,19.145385 14.7152534,19.145385 14.3247292,18.7548607 L8.29289322,12.7230247 C7.90236893,12.3325005 7.90236893,11.6993355 8.29289322,11.3088112 L14.3247292,5.27697525 C14.7152534,4.88645096 15.3484184,4.88645096 15.7389427,5.27697525 Z" id="Combined-Shape-Copy" fill="#000000" fill-rule="nonzero" transform="translate(12.0159, 12.0159) rotate(450) translate(-12.0159, -12.0159)"></path>
6
+ </g>
7
+ </svg>