Skip to content
Snippets Groups Projects
Commit 56425965 authored by Moritz Stückler's avatar Moritz Stückler :cowboy:
Browse files

fix: remove form components

parent d3524cdd
No related branches found
No related tags found
1 merge request!1Fix/upgrade stack
import React from 'react';
import { CheckCircleOutline as CheckIcon } from 'heroicons-react';
interface Props {
label: string;
value: [number | '', number | ''];
required?: boolean;
text?: string;
}
const CoordinateInput: React.FC<Props> = ({ label, text, value, required }) => {
return (
<>
{!!label && (
<span className="form-label">
{label}
{required && `*`}
</span>
)}
<label className="flex mt-1 mb-4 p-2 items-center w-full rounded-lg border-2 border-black border-opacity-20 hover:border-opacity-40 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<CheckIcon className={`${value.filter(Boolean).length ? 'text-indigo-500' : 'text-gray-400'} mr-2`} />
<input readOnly type="text" name={'lat'} value={value[0]} className="hidden" required={required}></input>
<input readOnly type="text" name={'lng'} value={value[1]} className="hidden" required={required}></input>
{!!text && <span className="form-label">{text}</span>}
</label>
</>
);
};
export default CoordinateInput;
import React from 'react';
import { PaperClipOutline as PaperClipIcon, UploadOutline as UploadIcon } from 'heroicons-react';
interface Props {
label: string;
value: File | null;
name: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
required: boolean;
}
const FileInput: React.FC<Props> = ({ name, label, value, onChange, ...inputProps }) => {
return (
<>
<span className="form-label">
{label}
{inputProps?.required && `*`}
</span>
<label
className={`mt-1 flex items-center rounded-lg border-2 border-black mb-6 w-full p-2 text-center focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 cursor-pointer${
value
? ' bg-white text-black border-opacity-20 hover:border-opacity-40 truncate text-sm text-opacity-40'
: ' bg-indigo-500 text-white border-opacity-0 hover:bg-indigo-600 text-md'
}`}
>
{value ? (
<>
<PaperClipIcon size={18} className="flex-shrink-0 mr-2" />
{value.name}
</>
) : (
<>
<UploadIcon size={18} className="flex-shrink-0 mr-2" />
{'Datei auswählen...'}
</>
)}
<input
className="hidden"
type="file"
name={name}
accept="image/png, image/jpeg"
onChange={onChange}
{...inputProps}
/>
</label>
</>
);
};
export default FileInput;
import React from 'react';
import type { NamedProps } from 'react-select';
import CreatableSelect from '../CreatableSelect';
interface Props<OptionType, isMulti extends boolean> extends NamedProps<OptionType, isMulti> {
label: string;
name: string;
required?: boolean;
}
const SelectInput = <OptionType, isMulti extends boolean>({
name,
label,
value,
onChange,
...inputProps
}: Props<OptionType, isMulti>): JSX.Element => {
return (
<label className="block mb-4">
{!!label && (
<span className="form-label">
{label}
{inputProps?.required && `*`}
</span>
)}
<CreatableSelect
name={name}
value={value}
className="p-0 form-input form-input-custom"
onChange={onChange}
{...inputProps}
/>
</label>
);
};
export default SelectInput;
import React, { useEffect, useMemo, useState } from 'react';
import type { Tag as TagType } from 'src/types/PointOfInterest';
import { generateRandomHslColor } from '../../util/color';
import Tag from '../Tag';
interface Props {
label?: string;
tags: TagType[];
options: TagType[];
onTagsChange: (tags: TagType[]) => void;
required?: boolean;
}
const TagInput: React.FC<Props> = ({ label, tags, options, onTagsChange, required }: Props) => {
// All options
const [tagOptions, setTagOptions] = useState<TagType[]>([]);
// The text input
const [draftEntry, setDraftEntry] = useState('');
const [showOptions, setShowOptions] = useState(false);
// Only show options that start with current draftEntry value
const filteredTagOptions = useMemo(
() => tagOptions.filter((tagOption) => tagOption.displayName.toLowerCase().startsWith(draftEntry.toLowerCase())),
[tagOptions, draftEntry],
);
const handleKeyboardInput = (e: React.KeyboardEvent) => {
if (draftEntry === '') {
if (e.nativeEvent.key === 'Backspace') {
const tagsClone = JSON.parse(JSON.stringify(tags));
const removedTag = tagsClone.pop();
if (removedTag) {
onTagsChange(tagsClone);
}
}
} else {
if (e.nativeEvent.key === 'Enter') {
e.preventDefault();
// Check if a Tag with that name already exists
const allTagNames = tags.map((tag) => tag.displayName.toLowerCase());
const isDuplicate = allTagNames.includes(draftEntry.toLowerCase());
if (!isDuplicate) {
onTagsChange([...tags, { displayName: draftEntry, color: generateRandomHslColor(60, 80), id: 'draft' }]);
}
setDraftEntry('');
}
}
};
const handleClickDeleteTag = (tag: TagType) => {
onTagsChange(tags.filter((filterTag) => tag.displayName.toLowerCase() !== filterTag.displayName.toLowerCase()));
};
const handleSelectTag = (tag: TagType) => {
onTagsChange([...tags, tag]);
};
useEffect(() => {
setTagOptions(
options.filter(
(tagOption) => !tags.find((tag) => tag.displayName.toLowerCase() === tagOption.displayName.toLowerCase()),
),
);
}, [tags, options]);
return (
<label className="relative block mb-4">
{!!label && (
<span className="form-label">
{label}
{required && `*`}
</span>
)}
<div
tabIndex={0}
className={`${
showOptions && filteredTagOptions.length ? 'rounded-t-lg hover:border-opacity-40 ' : 'rounded-lg '
}flex w-full border-2 border-black border-opacity-20 focus-within:border-indigo-300 focus-within:ring focus-within:ring-indigo-200 focus-within:ring-opacity-50 input-chevron mt-1`}
>
<div className="flex flex-wrap items-center flex-1 p-2">
{tags.map((selectedTag) => (
<Tag
key={`selected${selectedTag.color}`}
onClickDelete={() => handleClickDeleteTag(selectedTag)}
color={selectedTag.color}
>
{selectedTag.displayName}
</Tag>
))}
<input
onKeyDown={handleKeyboardInput}
onChange={(e) => {
const newValue = e.target.value;
setDraftEntry(newValue);
}}
onFocus={() => setShowOptions(true)}
onBlur={() => {
setShowOptions(false);
}}
type={'text'}
value={draftEntry}
className={`form-input w-16 block p-0 flex-1 rounded-lg border-0 focus:outline-none focus:ring-0 ${
tags.length ? '' : 'px-3'
}`}
/>
</div>
</div>
{/* Options Dropdown */}
{filteredTagOptions.length > 0 && (
<ul
className={`${
showOptions && filteredTagOptions.length ? 'block ' : 'hidden '
}rounded-b-lg z-10 absolute w-full bg-white border-2 border-t-0 shadow-md`}
>
{filteredTagOptions.map((option) => {
return (
<li
key={`${option.id}`}
onMouseDown={() => {
handleSelectTag(option);
}}
className="w-full p-1 hover:bg-gray-100"
>
<Tag color={option.color}>{option.displayName}</Tag>
</li>
);
})}
</ul>
)}
</label>
);
};
export default TagInput;
import React from 'react';
interface Props {
label: string;
value: string;
name: string;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
required?: boolean;
}
const TextAreaInput: React.FC<Props> = ({ name, label, value, onChange, ...textAreaProps }) => {
return (
<label className="block mb-4">
{!!label && (
<span className="form-label">
{label}
{textAreaProps?.required && `*`}
</span>
)}
<textarea
name={name}
value={value}
className="form-textarea form-input-custom"
rows={3}
onChange={onChange}
{...textAreaProps}
></textarea>
</label>
);
};
export default TextAreaInput;
import React from 'react';
interface Props {
label: string;
value: string;
name: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
required?: boolean;
type?: string;
}
const TextInput: React.FC<Props> = ({ name, label, value, onChange, type = 'text', ...inputProps }) => {
return (
<label className="block mb-4">
{!!label && (
<span className="form-label">
{label}
{inputProps?.required && `*`}
</span>
)}
<input
type={type}
name={name}
value={value}
className="form-input form-input-custom"
onChange={onChange}
{...inputProps}
/>
</label>
);
};
export default TextInput;
import React, { useState } from 'react';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import { usePoiData } from '../../hooks';
import { ChevronDownOutline as DownIcon } from 'heroicons-react';
import { useStore } from '../../hooks';
const MapLayerControl: React.FC = () => {
const { data } = usePoiData();
const data = useStore((state) => state.poiData);
const { filterCategories, setFilterCategories } = useFilteredPoiData();
const layers = Array.from(new Set(data?.map((poi) => poi.category)));
const [isOpen, setIsOpen] = useState(false);
......
import React, { useEffect } from 'react';
import { usePoiData, useStore } from '../hooks';
import { useStore } from '../hooks';
interface Props {
poiId: string | null;
}
const PoiLoader: React.FC<Props> = ({ poiId }) => {
const { data } = usePoiData();
const data = useStore((state) => state.poiData);
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
useEffect(() => {
......
import React, { useState } from 'react';
import { usePoiData, useStore } from '../../hooks';
import { useStore } from '../../hooks';
import ListElement from './ListElement';
import SidebarContainer from './SidebarContainer';
import { removeDuplicateObjects } from '../../util/array';
......@@ -13,7 +13,7 @@ const SidebarListView: React.FC = () => {
const tagsToSelectOptions = (tags?: Tag[]) => tags?.map((tag) => ({ label: tag.displayName, value: tag }));
const history = useHistory();
const { data } = usePoiData();
const data = useStore((state) => state.poiData);
const { data: filteredData, filterTags, setFilterTags } = useFilteredPoiData();
const hoveredPoi = useStore((state) => state.hoveredPoi);
const setHoveredPoi = useStore((state) => state.setHoveredPoi);
......
export * from './usePoiData';
export * from './useStore';
import type { poiData } from './usePoiData';
import { usePoiData } from './usePoiData';
import { useStore } from './useStore';
import type { Tag } from '../types/PointOfInterest';
import type { PointOfInterest } from 'src/types/PointOfInterest';
export interface poiData {
data: PointOfInterest[] | undefined;
}
export interface poiFilteredData extends poiData {
filterTags: Tag[] | null;
......@@ -11,7 +15,7 @@ export interface poiFilteredData extends poiData {
}
export const useFilteredPoiData = (): poiFilteredData => {
const { data } = usePoiData();
const data = useStore((state) => state.poiData);
const filterTags = useStore((store) => store.filterTags);
const setFilterTags = useStore((state) => state.setFilterTags);
const filterCategories = useStore((store) => store.filterCategories);
......
import type { PointOfInterest, PointsOfInterestDTO } from 'src/types/PointOfInterest';
import useSWR from 'swr';
export interface poiData {
data: PointOfInterest[] | undefined;
}
export const usePoiData = (): poiData => {
const { data } = useSWR<PointsOfInterestDTO>(
`{
pois {
id
name
description
website
address
lat
lng
image
category
relationStatus
tags {
id
displayName
displayName
color
}
}
}
`,
);
return { data: data?.pois };
};
......@@ -4,8 +4,10 @@ import type { Notification } from 'src/types/Notification';
import type { LatLngTuple } from 'leaflet';
import create from 'zustand';
import { devtools } from 'zustand/middleware';
import jsonData from "../data.json";
interface Store {
poiData: PointOfInterest[] | null;
selectedPoi: PointOfInterest | null;
setSelectedPoi: (poi: PointOfInterest | null) => void;
hoveredPoi: PointOfInterest | null;
......@@ -24,6 +26,7 @@ interface Store {
export const useStore = create<Store>()(
devtools((set) => ({
poiData: jsonData,
selectedPoi: null,
setSelectedPoi: (poi) => {
set({
......
......@@ -9,7 +9,7 @@ export interface PointOfInterestBase {
relationStatus: string;
}
export interface PointOfInterest extends PointOfInterestBase {
id: number;
id: string;
image?: string;
tags: Tag[];
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment