Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor firecallitem #13

Merged
merged 12 commits into from
Dec 23, 2023
Merged
22 changes: 22 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000]

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# https://nextjs.org/docs/deployment
# Install dependencies only when needed
FROM node:18-alpine AS base
FROM node:20-alpine AS base
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
Expand Down
100 changes: 57 additions & 43 deletions src/components/FirecallItems/FirecallItemDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import TextField from '@mui/material/TextField';
import moment from 'moment';
import React, { useState } from 'react';
import ConfirmDialog from '../dialogs/ConfirmDialog';
import MyDateTimePicker from '../inputs/DateTimePicker';
import { FirecallItem } from '../firebase/firestore';
import { firecallItemInfo, firecallItems } from './infos/firecallitems';
import { FirecallItemInfo } from './infos/types';
import MyDateTimePicker from '../inputs/DateTimePicker';
import { fcItemClasses, fcItemNames, getItemClass } from './elements';
import { FirecallItemBase } from './elements/FirecallItemBase';
import { CheckBox } from '@mui/icons-material';
import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';

export interface FirecallItemDialogOptions {
onClose: (item?: FirecallItem) => void;
Expand All @@ -31,18 +35,13 @@ export default function FirecallItemDialog({
type: itemType,
}: FirecallItemDialogOptions) {
const [open, setOpen] = useState(true);
const [item, setFirecallItem] = useState<FirecallItem>(
itemDefault || firecallItemInfo(itemType).factory()
const [item, setFirecallItem] = useState<FirecallItemBase>(
getItemClass(itemDefault || ({ type: itemType } as FirecallItem))
);
const [confirmDelete, setConfirmDelete] = useState(false);

const itemInfo: FirecallItemInfo = firecallItemInfo(item.type);

const setItemField = (field: string, value: any) => {
setFirecallItem((prev) => ({
...prev,
[field]: value,
}));
setFirecallItem((prev) => getItemClass({ ...prev.data(), [field]: value }));
};

const onChange =
Expand All @@ -52,24 +51,24 @@ export default function FirecallItemDialog({

const handleChange = (event: SelectChangeEvent) => {
setItemField('type', event.target.value);
setFirecallItem((prev) => ({
...firecallItemInfo(event.target.value).factory(),
...prev,
}));
// setFirecallItem((prev) => ({
// ...firecallItemInfo(event.target.value).factory(),
// ...prev,
// }));
};

return (
<>
<Dialog open={open} onClose={() => onClose()}>
<DialogTitle>
{item.id ? (
<>{itemInfo.name} bearbeiten</>
<>{item.markerName()} bearbeiten</>
) : (
<>Neu: {itemInfo.name} hinzufügen</>
<>Neu: {item.markerName()} hinzufügen</>
)}
</DialogTitle>
<DialogContent>
<DialogContentText>{itemInfo.dialogText(item)}</DialogContentText>
<DialogContentText>{item.dialogText()}</DialogContentText>
{allowTypeChange && (
<FormControl fullWidth variant="standard">
<InputLabel id="firecall-item-type-label">Element Typ</InputLabel>
Expand All @@ -80,19 +79,19 @@ export default function FirecallItemDialog({
label="Art"
onChange={handleChange}
>
{Object.entries(firecallItems)
.filter(([key, fcItem]) => key !== 'fallback')
.map(([key, fcItem]) => (
{Object.entries(fcItemNames)
.filter(([key, name]) => key !== 'fallback')
.map(([key, name]) => (
<MenuItem key={key} value={key}>
{fcItem.name}
{name}
</MenuItem>
))}
</Select>
</FormControl>
)}
{Object.entries(itemInfo.fields).map(([key, label]) => (
{Object.entries(item.fields()).map(([key, label]) => (
<React.Fragment key={key}>
{itemInfo.dateFields.includes(key) && (
{item.dateFields().includes(key) && (
<MyDateTimePicker
label={label}
value={
Expand All @@ -105,21 +104,38 @@ export default function FirecallItemDialog({
}}
/>
)}
{!itemInfo.dateFields.includes(key) && (
<TextField
margin="dense"
id={key}
key={key}
label={label}
type={
(itemInfo.fieldTypes && itemInfo.fieldTypes[key]) || 'text'
}
fullWidth
variant="standard"
onChange={onChange(key)}
value={((item as any)[key] as string) || ''}
/>
{item.fieldTypes()[key] === 'boolean' && (
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={
(item as any)[key] === 'true' ||
(item as any)[key] === true
}
onChange={(event, checked) => {
setItemField(key, checked ? 'true' : 'false');
}}
/>
}
label={label}
/>
</FormGroup>
)}
{!item.dateFields().includes(key) &&
item.fieldTypes()[key] !== 'boolean' && (
<TextField
margin="dense"
id={key}
key={key}
label={label}
type={item.fieldTypes()[key] || 'text'}
fullWidth
variant="standard"
onChange={onChange(key)}
value={((item as any)[key] as string) || ''}
/>
)}
</React.Fragment>
))}
</DialogContent>
Expand All @@ -146,7 +162,7 @@ export default function FirecallItemDialog({
color="primary"
onClick={() => {
setOpen(false);
onClose(item);
onClose(item.filteredData());
}}
>
{item.id ? 'Aktualisieren' : 'Hinzufügen'}
Expand All @@ -155,10 +171,8 @@ export default function FirecallItemDialog({
</Dialog>
{confirmDelete && (
<ConfirmDialog
title={`${itemInfo.name} ${itemInfo.title(item)} löschen`}
text={`Element ${itemInfo.name} ${itemInfo.title(
item
)} wirklich löschen?`}
title={`${item.title()} löschen`}
text={`${item.title()} wirklich löschen?`}
onConfirm={(result) => {
setConfirmDelete(false);
if (result) {
Expand Down
112 changes: 112 additions & 0 deletions src/components/FirecallItems/elements/CircleMarker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import L, { Icon, IconOptions } from 'leaflet';
import { ReactNode } from 'react';
import { Circle as LeafletCircle } from 'react-leaflet';
import { Circle, FirecallItem } from '../../firebase/firestore';
import { circleIcon } from '../icons';
import { FirecallItemBase } from './FirecallItemBase';

export class CircleMarker extends FirecallItemBase {
color: string;
radius: number;
opacity: number;
fill: string;

public constructor(firecallItem?: Circle) {
super(firecallItem);
this.color = firecallItem?.color || 'green';
this.radius = firecallItem?.radius || 50;
this.opacity = firecallItem?.opacity || 100;
this.fill = firecallItem?.fill === undefined ? 'true' : firecallItem.fill;
}

public data(): FirecallItem {
return {
...super.data(),
color: this.color,
radius: this.radius,
opacity: this.opacity,
fill: this.fill,
} as Circle;
}

public markerName(): string {
return `Kreis`;
}

public title(): string {
return `${this.markerName()} ${this.name}`;
}
public info(): string {
return `Radius: ${this.radius || 0}m`;
}

public body(): string {
return `${this.lat},${this.lng}\nUmkreis: ${this.radius || 0}m`;
}

public dialogText(): ReactNode {
return (
<>
Um die Kreis zu zeichnen, auf die gewünschten Positionen klicken. <br />
{this.name || ''}
</>
);
}

public fields(): { [fieldName: string]: string } {
return {
...super.fields(),
radius: 'Radius (m)',
color: 'Farbe (HTML bzw. Englisch)',
fill: 'Kreis ausfüllen',
opacity: 'Deckkraft (in Prozent)',
};
}

public dateFields(): string[] {
return [];
}

public fieldTypes(): { [fieldName: string]: string } {
return {
fill: 'boolean',
};
}
public popupFn(): ReactNode {
return (
<>
<b>Kreis {this.name}</b>
<br />
{this.radius || 0}m
</>
);
}
public titleFn(): string {
return `Kreis ${this.name}: Radius ${this.radius || 0}m`;
}
public icon(): Icon<IconOptions> {
return circleIcon;
}

public static factory(): FirecallItemBase {
return new CircleMarker();
}

public renderMarker(selectItem: (item: FirecallItem) => void) {
return (
<>
{super.renderMarker(selectItem)}
<LeafletCircle
key={'circle' + this.id}
color={this.color}
radius={this.radius}
center={L.latLng(this.lat, this.lng)}
opacity={this.opacity / 100}
fill={this.fill === 'true'}
>
{this.renderPopup(selectItem)}
</LeafletCircle>
</>
);
}
}
Loading