Skip to content

Commit 9e40701

Browse files
THS-LMISThomas Schley
andauthored
Anlegen einer Subsite mit dem Titel des entsprechenden Gruppennamens (#17)
Co-authored-by: Thomas Schley <[email protected]>
1 parent 06004ad commit 9e40701

14 files changed

+357
-20
lines changed

app/components/Breadcrumbs.module.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22
background-color: #41485A;
33
display: flex;
44
padding: 10px min(4vh, 2vw);
5+
@media only screen and (max-height: 767px) {
6+
padding-bottom: 5px;
7+
padding-top: 5px;
8+
}
59
a {
610
color: white;
711
font-size: large;
12+
@media only screen and (max-height: 767px) {
13+
font-size: medium;
14+
}
815
text-decoration: none;
916
}
1017
a + a:before {
@@ -15,4 +22,7 @@
1522
a.active {
1623
font-weight: bold;
1724
}
25+
a.disabled {
26+
pointer-events: none;
27+
}
1828
}

app/components/Breadcrumbs.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,25 @@ import {NavLink, useLocation} from 'react-router';
22
import styles from './Breadcrumbs.module.css';
33

44
const routeLabels: { [key: string]: string } = {
5+
group: 'Gruppe',
56
impressum: 'Impressum'
67
};
78

9+
const routeWithoutLink: string[] = [
10+
'group'
11+
];
12+
13+
function calculateNavLinkClassNames(isActive: boolean, disabled: boolean) {
14+
const classNames: string[] = [];
15+
if (isActive) {
16+
classNames.push(styles.active);
17+
}
18+
if (disabled) {
19+
classNames.push(styles.disabled);
20+
}
21+
return classNames.join(' ');
22+
}
23+
824
function Breadcrumbs() {
925
const location = useLocation();
1026
const pathnames = location.pathname.split('/').filter((x) => x);
@@ -20,9 +36,10 @@ function Breadcrumbs() {
2036
{pathnames.map((pathname, index) => {
2137
const to = `/${pathnames.slice(0, index + 1).join('/')}`;
2238
const label: string = routeLabels[pathname] || pathname;
39+
const disabled = routeWithoutLink.includes(pathname);
2340
return <NavLink
24-
to={to}
25-
className={({ isActive }) => (isActive ? styles.active : undefined)}
41+
to={disabled ? '#' : to}
42+
className={({ isActive }) => calculateNavLinkClassNames(isActive, disabled)}
2643
key={pathname}
2744
>
2845
{decodeURIComponent(label)}

app/components/Footer.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
gap: 40px;
66
list-style: none;
77
padding: 20px min(4vh, 2vw);
8+
@media only screen and (max-height: 767px) {
9+
padding-bottom: 10px;
10+
padding-top: 10px;
11+
}
812
a {
913
color: white;
1014
font-size: x-large;

app/components/Header.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,9 @@
55
padding: 20px min(4vh, 2vw);
66
text-align: center;
77
text-decoration: none;
8+
@media only screen and (max-height: 767px) {
9+
font-size: xx-large;
10+
padding-bottom: 10px;
11+
padding-top: 10px;
12+
}
813
}

app/components/OpenLayers.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import React, {useEffect, useRef} from 'react';
22

33
import {Feature, Map, View} from 'ol';
44
import {Coordinate} from 'ol/coordinate';
5-
import {Extent} from "ol/extent";
5+
import {Extent} from 'ol/extent';
66
import {Point, Polygon} from 'ol/geom';
77
import TileLayer from 'ol/layer/Tile';
88
import VectorLayer from "ol/layer/Vector";
99
import {fromLonLat} from 'ol/proj';
1010
import {OSM} from 'ol/source';
1111
import VectorSource from 'ol/source/Vector';
1212
import {Fill, Stroke, Style} from 'ol/style';
13+
import CircleStyle from 'ol/style/Circle';
1314

1415
import 'ol/ol.css';
1516
import styles from './OpenLayers.module.css';
@@ -36,7 +37,9 @@ function createPointVectorLayer(name: string, source: VectorSource<Feature<Point
3637
interface Props {
3738
id: string,
3839
agriCrops: AgriCrop[],
40+
selectedAgriCrop?: AgriCrop|undefined,
3941
selectedGroup: TenantGroup|undefined,
42+
selectedSensor?: Sensor|undefined,
4043
sensors: Sensor[]
4144
}
4245

@@ -52,7 +55,7 @@ function fitMap(mapView: View | undefined, extent: Extent | undefined) {
5255
}
5356
}
5457

55-
function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
58+
function OpenLayers({ agriCrops, id, selectedAgriCrop, selectedGroup, selectedSensor, sensors }: Props) {
5659

5760
const mapRef = useRef<Map | undefined>(undefined);
5861

@@ -70,7 +73,7 @@ function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
7073
let pointVectorLayer = createPointVectorLayer(pointVectorLayerName, pointVectorSource);
7174
let polygonVectorLayer = createPolygonVectorLayer(polygonVectorLayerName, polygonVectorSource);
7275

73-
const style = new Style({
76+
const normalPolygonStyle = new Style({
7477
fill: new Fill({
7578
color: 'rgba(0, 128, 255, 0.4)',
7679
}),
@@ -80,6 +83,29 @@ function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
8083
}),
8184
});
8285

86+
const highlightPolygonStyle = new Style({
87+
fill: new Fill({
88+
color: 'rgba(255, 0, 0, 0.4)',
89+
}),
90+
stroke: new Stroke({
91+
color: 'red',
92+
width: 2,
93+
}),
94+
});
95+
96+
const highlightPointStyle = new Style({
97+
image: new CircleStyle({
98+
fill: new Fill({
99+
color: 'rgba(255, 0, 0, 0.4)'
100+
}),
101+
stroke: new Stroke({
102+
color: 'red',
103+
width: 1
104+
}),
105+
radius: 5
106+
})
107+
});
108+
83109
function updateAgriCrops () {
84110
const map = mapRef.current;
85111
if (!map || agriCrops.length === 0) return;
@@ -93,7 +119,7 @@ function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
93119
lonLatCoordinates.push(fromLonLat(coordinate));
94120
});
95121
const polygonFeature = new Feature({ geometry: new Polygon([lonLatCoordinates]) });
96-
polygonFeature.setStyle(style);
122+
polygonFeature.setStyle(agriCrop.id === selectedAgriCrop?.id ? highlightPolygonStyle : normalPolygonStyle);
97123
features.push(polygonFeature);
98124
});
99125
polygonVectorSource.clear();
@@ -105,7 +131,9 @@ function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
105131
}
106132
polygonVectorLayer = createPolygonVectorLayer(polygonVectorLayerName, polygonVectorSource);
107133
map.addLayer(polygonVectorLayer);
108-
fitMap(map.getView(), polygonVectorSource.getExtent());
134+
if (!selectedAgriCrop && !selectedSensor) {
135+
fitMap(map.getView(), polygonVectorSource.getExtent());
136+
}
109137
}
110138

111139
function updateSensors() {
@@ -116,7 +144,11 @@ function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
116144
return !selectedGroup || sensor.customGroup === selectedGroup.groupId;
117145
});
118146
_sensors.forEach((sensor: Sensor) => {
119-
features.push(new Feature({ geometry: new Point(fromLonLat(sensor.coordinates)) }));
147+
const pointFeature = new Feature({ geometry: new Point(fromLonLat(sensor.coordinates)) });
148+
if (sensor.id === selectedSensor?.id) {
149+
pointFeature.setStyle(highlightPointStyle);
150+
}
151+
features.push(pointFeature);
120152
});
121153
pointVectorSource.clear();
122154
pointVectorSource.addFeatures(features);
@@ -148,13 +180,13 @@ function OpenLayers({ agriCrops, id, selectedGroup, sensors }: Props) {
148180
if (mapRef.current && agriCrops.length > 0) {
149181
updateAgriCrops();
150182
}
151-
}, [agriCrops, selectedGroup]);
183+
}, [agriCrops, selectedGroup, selectedAgriCrop]);
152184

153185
useEffect(() => {
154186
if (mapRef.current && sensors.length > 0) {
155187
updateSensors();
156188
}
157-
}, [sensors, selectedGroup]);
189+
}, [sensors, selectedGroup, selectedSensor]);
158190

159191
return <div id={id} className={styles.map}></div>;
160192
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, {ChangeEventHandler, MouseEventHandler} from 'react';
2+
import {Button, Form} from 'react-bootstrap';
3+
import {NavLink} from 'react-router';
4+
5+
import AgriCrop from '../models/AgriCrop';
6+
import Sensor from '../models/Sensor';
7+
8+
import styles from './SingleSelectionGroups.module.css';
9+
10+
interface Props {
11+
agriCrops: AgriCrop[],
12+
handleChange: ChangeEventHandler,
13+
handleReset: MouseEventHandler,
14+
selectedAgriCrop: AgriCrop | undefined,
15+
selectedSensor: Sensor | undefined,
16+
sensors: Sensor[]
17+
}
18+
19+
function SingleSelectionGroupContents({ agriCrops, handleChange, handleReset, selectedAgriCrop, selectedSensor, sensors }: Props) {
20+
return <>
21+
<h4>Übersicht der Gruppeninhalte:</h4>
22+
<Form id="group_contents">
23+
{sensors.map((sensor) => (
24+
<Form.Check
25+
className={styles.groups}
26+
type="radio"
27+
id={sensor.id}
28+
key={sensor.id}
29+
label={sensor.id}
30+
value={sensor.id}
31+
name="group_contents"
32+
checked={selectedSensor?.id === sensor.id}
33+
onChange={handleChange}
34+
/>
35+
))}
36+
{agriCrops.map((agriCrops) => (
37+
<Form.Check
38+
className={styles.groups}
39+
type="radio"
40+
id={agriCrops.id}
41+
key={agriCrops.id}
42+
label={agriCrops.id}
43+
value={agriCrops.id}
44+
name="group_contents"
45+
checked={selectedAgriCrop?.id === agriCrops.id}
46+
onChange={handleChange}
47+
/>
48+
))}
49+
<NavLink className="btn btn-secondary mt-5" to="/">Zurück</NavLink>
50+
<Button className="ms-5 mt-5" variant="secondary" onClick={handleReset} disabled={!selectedSensor && !selectedAgriCrop}>
51+
Auswahl aufheben
52+
</Button>
53+
</Form>
54+
</>;
55+
}
56+
57+
export default SingleSelectionGroupContents;

app/components/SingleSelectionGroups.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import styles from './SingleSelectionGroups.module.css';
22

3-
import React from 'react';
3+
import React, {ChangeEventHandler, MouseEventHandler} from 'react';
44
import {Button, Form} from 'react-bootstrap';
5+
import {NavLink} from 'react-router';
56

67
import TenantGroup from '../models/TenantGroup';
78

89
interface Props {
910
groups: TenantGroup[],
10-
handleChange: any,
11-
handleReset: any,
11+
handleChange: ChangeEventHandler,
12+
handleReset: MouseEventHandler,
1213
selectedGroup: TenantGroup|undefined
1314
}
1415

@@ -31,9 +32,13 @@ function SingleSelectionGroups({ handleChange, handleReset, groups, selectedGrou
3132
onChange={handleChange}
3233
/>
3334
))}
34-
<Button className="mt-5" variant="secondary" onClick={handleReset}>
35+
<Button className="mt-5" variant="secondary" onClick={handleReset} disabled={!selectedGroup}>
3536
Auswahl aufheben
3637
</Button>
38+
<NavLink className={`btn btn-secondary ms-5 mt-5 ${selectedGroup ? '' : 'disabled'}`}
39+
to={selectedGroup ? '/group/' + selectedGroup.name : '#'}>
40+
Weiter
41+
</NavLink>
3742
</Form>
3843
</>
3944
);

app/models/Sensor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {Coordinate} from 'ol/coordinate';
33
export default interface Sensor {
44
id: string,
55
type: string,
6+
name: string,
67
customGroup: string,
78
coordinates: Coordinate
89
}

app/models/SensorResponse.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ export default interface SensorResponse {
44
id: string,
55
type: string,
66
customGroup: string,
7-
location: SingleLocationResponse | null
7+
location: SingleLocationResponse | null,
8+
name: string
89
}

app/routes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ import {index, route, RouteConfig} from '@react-router/dev/routes';
22

33
export default [
44
index('./routes/Home.tsx'),
5-
route('impressum', './routes/Impressum.tsx')
5+
route('impressum', './routes/Impressum.tsx'),
6+
route('group/:groupName', './routes/Group.tsx')
67
] satisfies RouteConfig;

0 commit comments

Comments
 (0)