Merge pull request #90 from utopia-os/remove-get-value

Improves typing of items
Removes getValue because it is super hard to type and was introduced for a customisation which is not used anymore. if needed, we could have a cleaner solution to offer that functionality.
This commit is contained in:
Max 2025-02-17 18:28:15 +01:00 committed by GitHub
commit c38d1283e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 3557 additions and 1590 deletions

4648
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -94,6 +94,7 @@
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet.locatecontrol": "^0.79.0", "leaflet.locatecontrol": "^0.79.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"radash": "^12.1.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-image-crop": "^10.1.8", "react-image-crop": "^10.1.8",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",

View File

@ -1,16 +1,9 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { Children, isValidElement, useEffect, useState } from 'react' import { Children, isValidElement, useEffect, useState } from 'react'
import { Marker, Tooltip } from 'react-leaflet' import { Marker, Tooltip } from 'react-leaflet'
import { encodeTag } from '#utils/FormatTags' import { encodeTag } from '#utils/FormatTags'
import { getValue } from '#utils/GetValue'
import { hashTagRegex } from '#utils/HashTagRegex' import { hashTagRegex } from '#utils/HashTagRegex'
import MarkerIconFactory from '#utils/MarkerIconFactory' import MarkerIconFactory from '#utils/MarkerIconFactory'
import { randomColor } from '#utils/RandomColor' import { randomColor } from '#utils/RandomColor'
@ -47,17 +40,6 @@ export const Layer = ({
markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)', markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)',
api, api,
itemType, itemType,
itemNameField = 'name',
itemSubnameField,
itemTextField = 'text',
itemAvatarField,
itemColorField,
itemOwnerField,
itemLatitudeField = 'position.coordinates.1',
itemLongitudeField = 'position.coordinates.0',
itemTagsField,
itemOffersField,
itemNeedsField,
onlyOnePerOwner = false, onlyOnePerOwner = false,
customEditLink, customEditLink,
customEditParameter, customEditParameter,
@ -110,16 +92,8 @@ export const Layer = ({
markerDefaultColor2, markerDefaultColor2,
api, api,
itemType, itemType,
itemNameField,
itemSubnameField,
itemTextField,
itemAvatarField,
itemColorField,
itemOwnerField,
itemTagsField,
itemOffersField,
itemNeedsField,
onlyOnePerOwner, onlyOnePerOwner,
// Can we just use editCallback for all cases?
customEditLink, customEditLink,
customEditParameter, customEditParameter,
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
@ -127,6 +101,7 @@ export const Layer = ({
listed, listed,
setItemFormPopup, setItemFormPopup,
itemFormPopup, itemFormPopup,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
clusterRef, clusterRef,
}) })
api && api &&
@ -143,15 +118,6 @@ export const Layer = ({
markerDefaultColor2, markerDefaultColor2,
api, api,
itemType, itemType,
itemNameField,
itemSubnameField,
itemTextField,
itemAvatarField,
itemColorField,
itemOwnerField,
itemTagsField,
itemOffersField,
itemNeedsField,
onlyOnePerOwner, onlyOnePerOwner,
customEditLink, customEditLink,
customEditParameter, customEditParameter,
@ -160,6 +126,7 @@ export const Layer = ({
listed, listed,
setItemFormPopup, setItemFormPopup,
itemFormPopup, itemFormPopup,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
clusterRef, clusterRef,
}) })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -201,29 +168,19 @@ export const Layer = ({
visibleGroupTypes.length === 0, visibleGroupTypes.length === 0,
) )
.map((item: Item) => { .map((item: Item) => {
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) { if (item.position?.coordinates[0] && item.position?.coordinates[1]) {
// eslint-disable-next-line security/detect-object-injection
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField)
// eslint-disable-next-line security/detect-object-injection
else item[itemTextField] = ''
if (item.tags) { if (item.tags) {
// eslint-disable-next-line security/detect-object-injection item.text += '\n\n'
item[itemTextField] = item[itemTextField] + '\n\n'
item.tags.map((tag) => { item.tags.map((tag) => {
// eslint-disable-next-line security/detect-object-injection if (!item.text?.includes(`#${encodeTag(tag)}`)) {
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) { item.text += `#${encodeTag(tag)}`
// eslint-disable-next-line security/detect-object-injection
return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `)
} }
// eslint-disable-next-line security/detect-object-injection return item.text
return item[itemTextField]
}) })
} }
if (allTagsLoaded && allItemsLoaded) { if (allTagsLoaded && allItemsLoaded) {
// eslint-disable-next-line security/detect-object-injection item.text?.match(hashTagRegex)?.map((tag) => {
item[itemTextField].match(hashTagRegex)?.map((tag) => {
if ( if (
!tags.find( !tags.find(
(t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase(), (t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase(),
@ -246,20 +203,19 @@ export const Layer = ({
const itemTags = getItemTags(item) const itemTags = getItemTags(item)
const latitude = const latitude = item.position.coordinates[1]
itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined const longitude = item.position.coordinates[0]
const longitude =
itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined
let color1 = markerDefaultColor let color1 = markerDefaultColor
let color2 = markerDefaultColor2 let color2 = markerDefaultColor2
if (itemColorField && getValue(item, itemColorField) != null) if (item.color) {
color1 = getValue(item, itemColorField) color1 = item.color
else if (itemTags && itemTags[0]) { } else if (itemTags[0]) {
color1 = itemTags[0].color color1 = itemTags[0].color
} }
if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color if (itemTags[0] && item.color) {
else if (itemTags && itemTags[1]) { color2 = itemTags[0].color
} else if (itemTags[1]) {
color2 = itemTags[1].color color2 = itemTags[1].color
} }
return ( return (
@ -319,7 +275,7 @@ export const Layer = ({
)} )}
<Tooltip offset={[0, -38]} direction='top'> <Tooltip offset={[0, -38]} direction='top'>
{item.name ? item.name : getValue(item, itemNameField)} {item.name}
</Tooltip> </Tooltip>
</Marker> </Marker>
) )

View File

@ -23,7 +23,6 @@ import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
import { useTags } from '#components/Map/hooks/useTags' import { useTags } from '#components/Map/hooks/useTags'
import useWindowDimensions from '#components/Map/hooks/useWindowDimension' import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
import { decodeTag } from '#utils/FormatTags' import { decodeTag } from '#utils/FormatTags'
import { getValue } from '#utils/GetValue'
import MarkerIconFactory from '#utils/MarkerIconFactory' import MarkerIconFactory from '#utils/MarkerIconFactory'
import { LocateControl } from './LocateControl' import { LocateControl } from './LocateControl'
@ -73,12 +72,10 @@ export const SearchControl = () => {
searchGeo() searchGeo()
setItemsResults( setItemsResults(
items.filter((item) => { items.filter((item) => {
if (item.layer?.itemNameField) item.name = getValue(item, item.layer.itemNameField)
if (item.layer?.itemTextField) item.text = getValue(item, item.layer.itemTextField)
return ( return (
value.length > 2 && value.length > 2 &&
((item.layer?.listed && item.name.toLowerCase().includes(value.toLowerCase())) || ((item.layer?.listed && item.name.toLowerCase().includes(value.toLowerCase())) ||
item.text.toLowerCase().includes(value.toLowerCase())) item.text?.toLowerCase().includes(value.toLowerCase()))
) )
}), }),
) )

View File

@ -189,7 +189,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
key={props.position.toString()} key={props.position.toString()}
placeholder='Text' placeholder='Text'
dataField='text' dataField='text'
defaultValue={props.item ? props.item.text : ''} defaultValue={props.item?.text ?? ''}
inputStyle='tw-h-40 tw-mt-5' inputStyle='tw-h-40 tw-mt-5'
/> />
</> </>

View File

@ -15,7 +15,6 @@ import { useNavigate } from 'react-router-dom'
import { useAppState } from '#components/AppShell/hooks/useAppState' import { useAppState } from '#components/AppShell/hooks/useAppState'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions' import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import DialogModal from '#components/Templates/DialogModal' import DialogModal from '#components/Templates/DialogModal'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
import type { ItemsApi } from '#types/ItemsApi' import type { ItemsApi } from '#types/ItemsApi'
@ -26,9 +25,6 @@ export function HeaderView({
editCallback, editCallback,
deleteCallback, deleteCallback,
setPositionCallback, setPositionCallback,
itemNameField,
itemSubnameField,
itemAvatarField,
loading, loading,
hideMenu = false, hideMenu = false,
big = false, big = false,
@ -41,9 +37,6 @@ export function HeaderView({
editCallback?: any editCallback?: any
deleteCallback?: any deleteCallback?: any
setPositionCallback?: any setPositionCallback?: any
itemNameField?: string
itemAvatarField?: string
itemSubnameField?: string
loading?: boolean loading?: boolean
hideMenu?: boolean hideMenu?: boolean
big?: boolean big?: boolean
@ -64,22 +57,10 @@ export function HeaderView({
}, [item]) }, [item])
const avatar = const avatar =
itemAvatarField && getValue(item, itemAvatarField) item.image &&
? appState.assetsApi.url + appState.assetsApi.url + item.image + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
getValue(item, itemAvatarField) + const title = item.name
`${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}` const subtitle = item.subname
: item.layer?.itemAvatarField &&
item &&
getValue(item, item.layer?.itemAvatarField) &&
appState.assetsApi.url +
getValue(item, item.layer?.itemAvatarField) +
`${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
const title = itemNameField
? getValue(item, itemNameField)
: item.layer?.itemNameField && item && getValue(item, item.layer.itemNameField)
const subtitle = itemSubnameField
? getValue(item, itemSubnameField)
: item.layer?.itemSubnameField && item && getValue(item, item.layer.itemSubnameField)
const [address] = useState<string>('') const [address] = useState<string>('')
@ -168,7 +149,7 @@ export function HeaderView({
onClick={(e) => onClick={(e) =>
item.layer?.customEditLink item.layer?.customEditLink
? navigate( ? navigate(
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && '?' + params}` : ''} `, `${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params && '?' + params}` : ''} `,
) )
: editCallback(e) : editCallback(e)
} }

View File

@ -3,7 +3,6 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useGetItemTags } from '#components/Map/hooks/useTags' import { useGetItemTags } from '#components/Map/hooks/useTags'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
@ -11,23 +10,21 @@ export const PopupButton = ({
url, url,
parameterField, parameterField,
text, text,
colorField,
item, item,
}: { }: {
url: string url: string
parameterField?: string parameterField?: string
text: string text: string
colorField?: string
item?: Item item?: Item
}) => { }) => {
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
const getItemTags = useGetItemTags() const getItemTags = useGetItemTags()
return ( return (
<Link to={`${url}/${parameterField ? getValue(item, parameterField) : ''}?${params}`}> <Link to={`${url}/${parameterField ? item?.id : ''}?${params}`}>
<button <button
style={{ style={{
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : item && getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor ? item?.layer?.markerDefaultColor : '#000'}`, backgroundColor: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
}} }}
className='tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1' className='tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1'
> >

View File

@ -12,7 +12,6 @@ import remarkBreaks from 'remark-breaks'
import { useAddFilterTag } from '#components/Map/hooks/useFilter' import { useAddFilterTag } from '#components/Map/hooks/useFilter'
import { useTags } from '#components/Map/hooks/useTags' import { useTags } from '#components/Map/hooks/useTags'
import { decodeTag } from '#utils/FormatTags' import { decodeTag } from '#utils/FormatTags'
import { getValue } from '#utils/GetValue'
import { hashTagRegex } from '#utils/HashTagRegex' import { hashTagRegex } from '#utils/HashTagRegex'
import { fixUrls, mailRegex } from '#utils/ReplaceURLs' import { fixUrls, mailRegex } from '#utils/ReplaceURLs'
@ -21,32 +20,37 @@ import type { Tag } from '#types/Tag'
export const TextView = ({ export const TextView = ({
item, item,
itemId,
text,
truncate = false, truncate = false,
itemTextField,
rawText, rawText,
}: { }: {
item?: Item item?: Item
itemId: string
text?: string
truncate?: boolean truncate?: boolean
itemTextField?: string
rawText?: string rawText?: string
}) => { }) => {
if (item) {
text = item.text
itemId = item.id
}
const tags = useTags() const tags = useTags()
const addFilterTag = useAddFilterTag() const addFilterTag = useAddFilterTag()
let text = '' let innerText = ''
let replacedText = '' let replacedText = ''
if (rawText) { if (rawText) {
text = replacedText = rawText innerText = replacedText = rawText
} else if (itemTextField && item) { } else if (text) {
text = getValue(item, itemTextField) innerText = text
} else {
text = item?.layer?.itemTextField && item ? getValue(item, item.layer.itemTextField) : ''
} }
if (item && text && truncate) text = truncateText(removeMarkdownKeepLinksAndParagraphs(text), 100) if (innerText && truncate)
innerText = truncateText(removeMarkdownKeepLinksAndParagraphs(innerText), 100)
if (item && text) replacedText = fixUrls(text) if (innerText) replacedText = fixUrls(innerText)
if (replacedText) { if (replacedText) {
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => { replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
@ -114,16 +118,16 @@ export const TextView = ({
const CustomHashTagLink = ({ const CustomHashTagLink = ({
children, children,
tag, tag,
item, itemId,
}: { }: {
children: string children: string
tag: Tag tag: Tag
item?: Item itemId: string
}) => { }) => {
return ( return (
<a <a
style={{ color: tag ? tag.color : '#faa', fontWeight: 'bold', cursor: 'pointer' }} style={{ color: tag ? tag.color : '#faa', fontWeight: 'bold', cursor: 'pointer' }}
key={tag ? tag.name + item?.id : item?.id} key={tag ? tag.name + itemId : itemId}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
addFilterTag(tag) addFilterTag(tag)
@ -173,7 +177,7 @@ export const TextView = ({
) )
if (tag) if (tag)
return ( return (
<CustomHashTagLink tag={tag} item={item}> <CustomHashTagLink tag={tag} itemId={itemId}>
{children} {children}
</CustomHashTagLink> </CustomHashTagLink>
) )

View File

@ -105,7 +105,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
: '', : '',
) )
) : ( ) : (
<TextView item={props.item} /> <TextView text={props.item.text} itemId={props.item.id} />
)} )}
</div> </div>
<div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 tw-mt-1'> <div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 tw-mt-1'>

View File

@ -15,7 +15,6 @@ import { toast } from 'react-toastify'
import './UtopiaMap.css' import './UtopiaMap.css'
import { containsUUID } from '#utils/ContainsUUID' import { containsUUID } from '#utils/ContainsUUID'
import { getValue } from '#utils/GetValue'
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef' import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
import { useAddVisibleLayer } from './hooks/useFilter' import { useAddVisibleLayer } from './hooks/useFilter'
@ -75,9 +74,10 @@ export function UtopiaMapInner({
setTimeout(() => { setTimeout(() => {
toast( toast(
<> <>
<TextView rawText={'## Do you like this Map?'} /> <TextView itemId='' rawText={'## Do you like this Map?'} />
<div> <div>
<TextView <TextView
itemId=''
rawText={'Support us building free opensource maps and help us grow 🌱☀️'} rawText={'Support us building free opensource maps and help us grow 🌱☀️'}
/> />
<PopupButton url={'https://opencollective.com/utopia-project'} text={'Donate'} /> <PopupButton url={'https://opencollective.com/utopia-project'} text={'Donate'} />
@ -120,7 +120,6 @@ export function UtopiaMapInner({
} }
let title = '' let title = ''
if (item?.name) title = item.name if (item?.name) title = item.name
else if (item?.layer?.itemNameField) title = getValue(item, item.layer.itemNameField)
document.title = `${document.title.split('-')[0]} - ${title}` document.title = `${document.title.split('-')[0]} - ${title}`
} }
}, },
@ -142,15 +141,13 @@ export function UtopiaMapInner({
}) })
let title = '' let title = ''
if (ref.item.name) title = ref.item.name if (ref.item.name) title = ref.item.name
else if (ref.item.layer?.itemNameField)
title = getValue(ref.item.name, ref.item.layer.itemNameField)
document.title = `${document.title.split('-')[0]} - ${title}` document.title = `${document.title.split('-')[0]} - ${title}`
document document
.querySelector('meta[property="og:title"]') .querySelector('meta[property="og:title"]')
?.setAttribute('content', ref.item.name) ?.setAttribute('content', ref.item.name)
document document
.querySelector('meta[property="og:description"]') .querySelector('meta[property="og:description"]')
?.setAttribute('content', ref.item.text) ?.setAttribute('content', ref.item.text ?? '')
} }
} }
} }

View File

@ -3,7 +3,7 @@
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-misused-promises */
import { useCallback, useReducer, createContext, useContext, useState } from 'react' import { useCallback, useReducer, createContext, useContext, useState } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
@ -82,6 +82,7 @@ function useItemsManager(initialItems: Item[]): {
}, },
}) })
result.map((item) => { result.map((item) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
dispatch({ type: 'ADD', item: { ...item, layer } }) dispatch({ type: 'ADD', item: { ...item, layer } })
return null return null
}) })

View File

@ -63,7 +63,7 @@ function useSelectPositionManager(): {
if ('menuIcon' in selectPosition) { if ('menuIcon' in selectPosition) {
mapClicked && mapClicked &&
mapClicked.setItemFormPopup({ mapClicked.setItemFormPopup({
layer: selectPosition as LayerProps, layer: selectPosition,
position: mapClicked.position, position: mapClicked.position,
}) })
setSelectPosition(null) setSelectPosition(null)

View File

@ -5,12 +5,8 @@
/* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { useCallback, useReducer, createContext, useContext, useState } from 'react' import { useCallback, useReducer, createContext, useContext, useState } from 'react'
import { getValue } from '#utils/GetValue'
import { hashTagRegex } from '#utils/HashTagRegex' import { hashTagRegex } from '#utils/HashTagRegex'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
@ -96,8 +92,7 @@ function useTagsManager(initialTags: Tag[]): {
const getItemTags = useCallback( const getItemTags = useCallback(
(item: Item) => { (item: Item) => {
const text = const text = item.text
item.layer?.itemTextField && item ? getValue(item, item.layer.itemTextField) : undefined
const itemTagStrings = text?.match(hashTagRegex) const itemTagStrings = text?.match(hashTagRegex)
const itemTags: Tag[] = [] const itemTags: Tag[] = []
itemTagStrings?.map((tag) => { itemTagStrings?.map((tag) => {
@ -108,18 +103,15 @@ function useTagsManager(initialTags: Tag[]): {
} }
return null return null
}) })
item.layer?.itemOffersField && // Could be refactored as it occurs in multiple places
getValue(item, item.layer.itemOffersField)?.map((o) => { item.offers?.forEach((o) => {
const offer = tags.find((t) => t.id === o.tags_id) const offer = tags.find((t) => t.id === o.tags_id)
offer && itemTags.push(offer) offer && itemTags.push(offer)
return null })
}) item.needs?.forEach((n) => {
item.layer?.itemNeedsField && const need = tags.find((t) => t.id === n.tags_id)
getValue(item, item.layer.itemNeedsField)?.map((n) => { need && itemTags.push(need)
const need = tags.find((t) => t.id === n.tags_id) })
need && itemTags.push(need)
return null
})
return itemTags return itemTags
}, },

View File

@ -1,8 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
@ -14,7 +11,6 @@ import { useLayers } from '#components/Map/hooks/useLayers'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions' import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags' import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
import { MapOverlayPage } from '#components/Templates' import { MapOverlayPage } from '#components/Templates'
import { getValue } from '#utils/GetValue'
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions' import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
import { FormHeader } from './Subcomponents/FormHeader' import { FormHeader } from './Subcomponents/FormHeader'
@ -23,11 +19,12 @@ import { OnepagerForm } from './Templates/OnepagerForm'
import { SimpleForm } from './Templates/SimpleForm' import { SimpleForm } from './Templates/SimpleForm'
import { TabsForm } from './Templates/TabsForm' import { TabsForm } from './Templates/TabsForm'
import type { FormState } from '#types/FormState'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
import type { Tag } from '#types/Tag' import type { Tag } from '#types/Tag'
export function ProfileForm() { export function ProfileForm() {
const [state, setState] = useState({ const [state, setState] = useState<FormState>({
color: '', color: '',
id: '', id: '',
group_type: 'wuerdekompass', group_type: 'wuerdekompass',
@ -91,11 +88,10 @@ export function ProfileForm() {
useEffect(() => { useEffect(() => {
const newColor = const newColor =
item.layer?.itemColorField && getValue(item, item.layer.itemColorField) item.color ??
? getValue(item, item.layer.itemColorField) (getItemTags(item) && getItemTags(item)[0]?.color
: getItemTags(item) && getItemTags(item)[0]?.color ? getItemTags(item)[0].color
? getItemTags(item)[0].color : item.layer?.markerDefaultColor)
: item.layer?.markerDefaultColor
const offers = (item.offers ?? []).reduce((acc: Tag[], o) => { const offers = (item.offers ?? []).reduce((acc: Tag[], o) => {
const offer = tags.find((t) => t.id === o.tags_id) const offer = tags.find((t) => t.id === o.tags_id)
@ -116,7 +112,7 @@ export function ProfileForm() {
}, []) }, [])
setState({ setState({
color: newColor, color: newColor ?? '',
id: item?.id ?? '', id: item?.id ?? '',
group_type: item?.group_type ?? '', group_type: item?.group_type ?? '',
status: item?.status ?? '', status: item?.status ?? '',
@ -127,7 +123,8 @@ export function ProfileForm() {
telephone: item?.telephone ?? '', telephone: item?.telephone ?? '',
next_appointment: item?.next_appointment ?? '', next_appointment: item?.next_appointment ?? '',
image: item?.image ?? '', image: item?.image ?? '',
marker_icon: item?.marker_icon ?? '', // Do we actually mean marker_icon here?
marker_icon: item?.markerIcon ?? '',
offers, offers,
needs, needs,
relations, relations,
@ -140,7 +137,7 @@ export function ProfileForm() {
const [template, setTemplate] = useState<string>('') const [template, setTemplate] = useState<string>('')
useEffect(() => { useEffect(() => {
setTemplate(item.layer?.itemType.template || appState.userType) setTemplate(item.layer?.itemType.template ?? appState.userType)
}, [appState.userType, item]) }, [appState.userType, item])
return ( return (
@ -198,7 +195,8 @@ export function ProfileForm() {
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'} className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
type='submit' type='submit'
style={{ style={{
backgroundColor: `${item.layer?.itemColorField && getValue(item, item.layer?.itemColorField) ? getValue(item, item.layer?.itemColorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor}`, // We could refactor this, it is used several times at different locations
backgroundColor: `${item.color ?? (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`,
color: '#fff', color: '#fff',
}} }}
> >

View File

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/await-thenable */
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import { LatLng } from 'leaflet' import { LatLng } from 'leaflet'
@ -21,7 +21,6 @@ import { useSelectPosition, useSetSelectPosition } from '#components/Map/hooks/u
import { useTags } from '#components/Map/hooks/useTags' import { useTags } from '#components/Map/hooks/useTags'
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView' import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
import { MapOverlayPage } from '#components/Templates' import { MapOverlayPage } from '#components/Templates'
import { getValue } from '#utils/GetValue'
import { handleDelete, linkItem, unlinkItem } from './itemFunctions' import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
import { FlexView } from './Templates/FlexView' import { FlexView } from './Templates/FlexView'
@ -32,6 +31,7 @@ import { TabsView } from './Templates/TabsView'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
import type { ItemsApi } from '#types/ItemsApi' import type { ItemsApi } from '#types/ItemsApi'
import type { Tag } from '#types/Tag' import type { Tag } from '#types/Tag'
import type { Marker } from 'leaflet'
export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any> }) { export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any> }) {
const [item, setItem] = useState<Item>() const [item, setItem] = useState<Item>()
@ -88,30 +88,25 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
setNeeds([]) setNeeds([])
setRelations([]) setRelations([])
item?.layer?.itemOffersField && item?.offers?.forEach((o) => {
getValue(item, item.layer.itemOffersField)?.map((o) => { const tag = tags.find((t) => t.id === o.tags_id)
const tag = tags.find((t) => t.id === o.tags_id) tag && setOffers((current) => [...current, tag])
tag && setOffers((current) => [...current, tag]) })
return null item?.needs?.forEach((n) => {
}) const tag = tags.find((t) => t.id === n.tags_id)
item?.layer?.itemNeedsField && tag && setNeeds((current) => [...current, tag])
getValue(item, item.layer.itemNeedsField)?.map((n) => { })
const tag = tags.find((t) => t.id === n.tags_id) item?.relations?.forEach((r) => {
tag && setNeeds((current) => [...current, tag])
return null
})
item?.relations?.map((r) => {
const item = items.find((i) => i.id === r.related_items_id) const item = items.find((i) => i.id === r.related_items_id)
item && setRelations((current) => [...current, item]) item && setRelations((current) => [...current, item])
return null
}) })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, items]) }, [item, items])
useEffect(() => { useEffect(() => {
const setMap = async (marker, x) => { const setMap = (marker: Marker, x: number) => {
await map.setView( map.setView(
new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4), new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4),
undefined, undefined,
) )
@ -164,7 +159,7 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
}, [selectPosition]) }, [selectPosition])
useEffect(() => { useEffect(() => {
setTemplate(item?.layer?.itemType.template || appState.userType) setTemplate(item?.layer?.itemType.template ?? appState.userType)
}, [appState.userType, item]) }, [appState.userType, item])
return ( return (

View File

@ -10,7 +10,6 @@ import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useGetItemTags } from '#components/Map/hooks/useTags' import { useGetItemTags } from '#components/Map/hooks/useTags'
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView' import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
import DialogModal from '#components/Templates/DialogModal' import DialogModal from '#components/Templates/DialogModal'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
@ -20,7 +19,6 @@ export function ActionButton({
triggerItemSelected, triggerItemSelected,
existingRelations, existingRelations,
itemType, itemType,
colorField,
collection = 'items', collection = 'items',
customStyle, customStyle,
}: { }: {
@ -28,7 +26,6 @@ export function ActionButton({
triggerItemSelected?: any triggerItemSelected?: any
existingRelations: Item[] existingRelations: Item[]
itemType?: string itemType?: string
colorField?: string
collection?: string collection?: string
customStyle?: string customStyle?: string
item: Item item: Item
@ -45,6 +42,12 @@ export function ActionButton({
.filter((i) => !existingRelations.some((s) => s.id === i.id)) .filter((i) => !existingRelations.some((s) => s.id === i.id))
.filter((i) => i.id !== item.id) .filter((i) => i.id !== item.id)
const backgroundColor =
item.color ??
(getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color
? getItemTags(item)[0].color
: item.layer?.markerDefaultColor)
return ( return (
<> <>
{hasUserPermission(collection, 'update', item) && ( {hasUserPermission(collection, 'update', item) && (
@ -58,7 +61,7 @@ export function ActionButton({
setModalOpen(true) setModalOpen(true)
}} }}
style={{ style={{
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item.layer?.markerDefaultColor}`, backgroundColor,
color: '#fff', color: '#fff',
}} }}
> >
@ -82,7 +85,7 @@ export function ActionButton({
triggerAddButton() triggerAddButton()
}} }}
style={{ style={{
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item.layer?.markerDefaultColor}`, backgroundColor,
color: '#fff', color: '#fff',
}} }}
> >

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/restrict-plus-operands */
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'

View File

@ -1,7 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { useState } from 'react' import { useState } from 'react'
import { RowsPhotoAlbum } from 'react-photo-album' import { RowsPhotoAlbum } from 'react-photo-album'
import ReactLightbox from 'yet-another-react-lightbox' import ReactLightbox from 'yet-another-react-lightbox'
@ -15,7 +11,7 @@ import type { Item } from '#types/Item'
export const GalleryView = ({ item }: { item: Item }) => { export const GalleryView = ({ item }: { item: Item }) => {
const [index, setIndex] = useState(-1) const [index, setIndex] = useState(-1)
const appState = useAppState() const appState = useAppState()
const images = item.gallery.map((i, j) => { const images = item.gallery?.map((i, j) => {
return { return {
src: appState.assetsApi.url + `${i.directus_files_id.id}.jpg`, src: appState.assetsApi.url + `${i.directus_files_id.id}.jpg`,
width: i.directus_files_id.width, width: i.directus_files_id.width,
@ -23,6 +19,9 @@ export const GalleryView = ({ item }: { item: Item }) => {
index: j, index: j,
} }
}) })
if (!images) throw new Error('GalleryView: images is undefined')
return ( return (
<div className='tw-mx-6 tw-mb-6'> <div className='tw-mx-6 tw-mb-6'>
<RowsPhotoAlbum <RowsPhotoAlbum

View File

@ -4,46 +4,29 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { useEffect } from 'react' import { useEffect } from 'react'
import { useAppState } from '#components/AppShell/hooks/useAppState' import { useAppState } from '#components/AppShell/hooks/useAppState'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
export function LinkedItemsHeaderView({ export function LinkedItemsHeaderView({
item, item,
unlinkCallback, unlinkCallback,
itemNameField,
itemAvatarField,
loading, loading,
unlinkPermission, unlinkPermission,
itemSubnameField,
}: { }: {
item: Item item: Item
unlinkCallback?: any unlinkCallback?: any
itemNameField?: string
itemAvatarField?: string
itemSubnameField?: string
loading?: boolean loading?: boolean
unlinkPermission: boolean unlinkPermission: boolean
}) { }) {
const appState = useAppState() const appState = useAppState()
const avatar = const avatar = appState.assetsApi.url + item.image
itemAvatarField && getValue(item, itemAvatarField) const title = item.name
? appState.assetsApi.url + getValue(item, itemAvatarField) const subtitle = item.subname
: item.layer?.itemAvatarField &&
item &&
getValue(item, item.layer?.itemAvatarField) &&
appState.assetsApi.url + getValue(item, item.layer?.itemAvatarField)
const title = itemNameField
? getValue(item, itemNameField)
: item.layer?.itemNameField && item && getValue(item, item.layer.itemNameField)
const subtitle = itemSubnameField
? getValue(item, itemSubnameField)
: item.layer?.itemSubnameField && item && getValue(item, item.layer.itemSubnameField)
useEffect(() => {}, [item]) useEffect(() => {}, [item])

View File

@ -5,7 +5,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { TextAreaInput } from '#components/Input' import { TextAreaInput } from '#components/Input'
import { getValue } from '#utils/GetValue'
import { MarkdownHint } from './MarkdownHint' import { MarkdownHint } from './MarkdownHint'
@ -14,6 +13,7 @@ import type { FormState } from '#types/FormState'
export const ProfileTextForm = ({ export const ProfileTextForm = ({
state, state,
setState, setState,
// Is this really used?
dataField, dataField,
heading, heading,
size, size,
@ -49,7 +49,8 @@ export const ProfileTextForm = ({
</div> </div>
<TextAreaInput <TextAreaInput
placeholder={'...'} placeholder={'...'}
defaultValue={getValue(state, field)} // eslint-disable-next-line security/detect-object-injection
defaultValue={state[field]}
updateFormValue={(v) => updateFormValue={(v) =>
setState((prevState) => ({ setState((prevState) => ({
...prevState, ...prevState,

View File

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { get } from 'radash'
import { TextView } from '#components/Map' import { TextView } from '#components/Map'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
export const ProfileTextView = ({ export const ProfileTextView = ({
item, item,
dataField, dataField = 'text',
heading, heading,
hideWhenEmpty, hideWhenEmpty,
}: { }: {
@ -15,13 +15,19 @@ export const ProfileTextView = ({
heading: string heading: string
hideWhenEmpty: boolean hideWhenEmpty: boolean
}) => { }) => {
const text = get(item, dataField)
if (typeof text !== 'string') {
throw new Error('ProfileTextView: text is not a string')
}
return ( return (
<div className='tw-my-10 tw-mt-2 tw-px-6'> <div className='tw-my-10 tw-mt-2 tw-px-6'>
{!(getValue(item, dataField) === '' && hideWhenEmpty) && ( {!(text === '' && hideWhenEmpty) && (
<h2 className='tw-text-lg tw-font-semibold'>{heading}</h2> <h2 className='tw-text-lg tw-font-semibold'>{heading}</h2>
)} )}
<div className='tw-mt-2 tw-text-sm'> <div className='tw-mt-2 tw-text-sm'>
<TextView rawText={dataField ? getValue(item, dataField) : getValue(item, 'text')} /> <TextView itemId={item.id} rawText={text} />
</div> </div>
</div> </div>
) )

View File

@ -1,7 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoForm' import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoForm'
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm' import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView' import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
import { GalleryView } from '#components/Profile/Subcomponents/GalleryView' import { GalleryView } from '#components/Profile/Subcomponents/GalleryView'
@ -9,6 +7,7 @@ import { ProfileStartEndView } from '#components/Profile/Subcomponents/ProfileSt
import { ProfileTextView } from '#components/Profile/Subcomponents/ProfileTextView' import { ProfileTextView } from '#components/Profile/Subcomponents/ProfileTextView'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
import type { Key } from 'react'
const componentMap = { const componentMap = {
groupSubheaders: GroupSubHeaderView, groupSubheaders: GroupSubHeaderView,
@ -24,14 +23,17 @@ export const FlexView = ({ item }: { item: Item }) => {
console.log(item) console.log(item)
return ( return (
<div className='tw-h-full tw-overflow-y-auto fade'> <div className='tw-h-full tw-overflow-y-auto fade'>
{item.layer?.itemType.profileTemplate.map((templateItem) => { {item.layer?.itemType.profileTemplate.map(
const TemplateComponent = componentMap[templateItem.collection] // eslint-disable-next-line @typescript-eslint/no-explicit-any
return TemplateComponent ? ( (templateItem: { collection: string | number; id: Key | null | undefined; item: any }) => {
<TemplateComponent key={templateItem.id} item={item} {...templateItem.item} /> const TemplateComponent = componentMap[templateItem.collection]
) : ( return TemplateComponent ? (
<div key={templateItem.id}>Component not found</div> <TemplateComponent key={templateItem.id} item={item} {...templateItem.item} />
) ) : (
})} <div key={templateItem.id}>Component not found</div>
)
},
)}
</div> </div>
) )
} }

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
import { TextView } from '#components/Map' import { TextView } from '#components/Map'
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView' import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
@ -16,14 +15,14 @@ export const OnepagerView = ({ item }: { item: Item }) => {
{item.user_created?.first_name && <ContactInfoView heading='Du hast Fragen?' item={item} />} {item.user_created?.first_name && <ContactInfoView heading='Du hast Fragen?' item={item} />}
{/* Description Section */} {/* Description Section */}
<div className='tw-my-10 tw-mt-2 tw-px-6 tw-text-sm '> <div className='tw-my-10 tw-mt-2 tw-px-6 tw-text-sm '>
<TextView rawText={item.text || 'Keine Beschreibung vorhanden'} /> <TextView itemId={item.id} rawText={item.text ?? 'Keine Beschreibung vorhanden'} />
</div> </div>
{/* Next Appointment Section */} {/* Next Appointment Section */}
{item.next_appointment && ( {item.next_appointment && (
<div className='tw-my-10 tw-px-6'> <div className='tw-my-10 tw-px-6'>
<h2 className='tw-text-lg tw-font-semibold'>Nächste Termine</h2> <h2 className='tw-text-lg tw-font-semibold'>Nächste Termine</h2>
<div className='tw-mt-2 tw-text-sm'> <div className='tw-mt-2 tw-text-sm'>
<TextView rawText={item.next_appointment} /> <TextView itemId={item.id} rawText={item.next_appointment} />
</div> </div>
</div> </div>
)} )}

View File

@ -5,7 +5,7 @@ import type { Item } from '#types/Item'
export const SimpleView = ({ item }: { item: Item }) => { export const SimpleView = ({ item }: { item: Item }) => {
return ( return (
<div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'> <div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'>
<TextView item={item} /> <TextView text={item.text} itemId={item.id} />
</div> </div>
) )
} }

View File

@ -197,7 +197,7 @@ export const TabsForm = ({
loading={loading} loading={loading}
/> />
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'> <div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
<TextView truncate item={i} /> <TextView truncate itemId={item.id} />
</div> </div>
</div> </div>
))} ))}
@ -208,7 +208,6 @@ export const TabsForm = ({
item={item} item={item}
existingRelations={state.relations} existingRelations={state.relations}
triggerItemSelected={(id) => linkItem(id, item, updateItem)} triggerItemSelected={(id) => linkItem(id, item, updateItem)}
colorField={item.layer.itemColorField}
></ActionButton> ></ActionButton>
)} )}
</div> </div>

View File

@ -108,9 +108,9 @@ export const TabsView = ({
<StartEndView item={item}></StartEndView> <StartEndView item={item}></StartEndView>
</div> </div>
)} )}
<TextView item={item} /> <TextView text={item.text} itemId={item.id} />
<div className='tw-h-4'></div> <div className='tw-h-4'></div>
<TextView item={item} itemTextField='contact' /> <TextView text={item.contact} itemId={item.id} />
</div> </div>
{item.layer?.itemType.questlog && ( {item.layer?.itemType.questlog && (
<> <>
@ -267,7 +267,7 @@ export const TabsView = ({
loading={loading} loading={loading}
/> />
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'> <div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
<TextView truncate item={i} /> <TextView truncate text={i.text} itemId={item.id} />
</div> </div>
</div> </div>
))} ))}
@ -277,7 +277,6 @@ export const TabsView = ({
item={item} item={item}
existingRelations={relations} existingRelations={relations}
triggerItemSelected={linkItem} triggerItemSelected={linkItem}
colorField={item.layer.itemColorField}
></ActionButton> ></ActionButton>
)} )}
</div> </div>

View File

@ -46,7 +46,6 @@ const DialogModal = ({
<dialog <dialog
className={`${className ?? ''} tw-card tw-shadow-xl tw-absolute tw-right-0 tw-top-0 tw-bottom-0 tw-left-0 tw-m-auto tw-transition-opacity tw-duration-300 tw-p-4 tw-max-w-xl tw-bg-base-100`} className={`${className ?? ''} tw-card tw-shadow-xl tw-absolute tw-right-0 tw-top-0 tw-bottom-0 tw-left-0 tw-m-auto tw-transition-opacity tw-duration-300 tw-p-4 tw-max-w-xl tw-bg-base-100`}
ref={ref} ref={ref}
// eslint-disable-next-line react/no-unknown-property
onCancel={onClose} onCancel={onClose}
onClick={(e) => onClick={(e) =>
ref.current && !isClickInsideRectangle(e, ref.current) && closeOnClickOutside && onClose() ref.current && !isClickInsideRectangle(e, ref.current) && closeOnClickOutside && onClose()

View File

@ -2,14 +2,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { StartEndView, TextView } from '#components/Map' import { StartEndView, TextView } from '#components/Map'
import useWindowDimensions from '#components/Map/hooks/useWindowDimension' import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView' import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
import { getValue } from '#utils/GetValue'
import { DateUserInfo } from './DateUserInfo' import { DateUserInfo } from './DateUserInfo'
@ -19,13 +17,11 @@ export const ItemCard = ({
i, i,
loading, loading,
url, url,
parameterField,
deleteCallback, deleteCallback,
}: { }: {
i: Item i: Item
loading: boolean loading: boolean
url: string url: string
parameterField: string
deleteCallback: any deleteCallback: any
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
@ -35,27 +31,23 @@ export const ItemCard = ({
<div <div
className='tw-cursor-pointer tw-card tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-bg-base-100 tw-text-base-content tw-p-4 tw-mb-4 tw-h-fit' className='tw-cursor-pointer tw-card tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-bg-base-100 tw-text-base-content tw-p-4 tw-mb-4 tw-h-fit'
onClick={() => { onClick={() => {
// We could have an onClick callback instead
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
if (windowDimensions.width < 786 && i.position) if (windowDimensions.width < 786 && i.position)
navigate('/' + getValue(i, parameterField) + `${params ? `?${params}` : ''}`) navigate('/' + i.id + `${params ? `?${params}` : ''}`)
else navigate(url + getValue(i, parameterField) + `${params ? `?${params}` : ''}`) else navigate(url + i.id + `${params ? `?${params}` : ''}`)
}} }}
> >
<HeaderView <HeaderView
loading={loading} loading={loading}
item={i} item={i}
api={i.layer?.api} api={i.layer?.api}
itemAvatarField={i.layer?.itemAvatarField}
itemNameField={i.layer?.itemNameField}
itemSubnameField={i.layer?.itemSubnameField}
editCallback={() => navigate('/edit-item/' + i.id)} editCallback={() => navigate('/edit-item/' + i.id)}
deleteCallback={() => deleteCallback(i)} deleteCallback={() => deleteCallback(i)}
></HeaderView> ></HeaderView>
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'> <div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
{i.layer?.itemType.show_start_end && <StartEndView item={i}></StartEndView>} {i.layer?.itemType.show_start_end && <StartEndView item={i}></StartEndView>}
{i.layer?.itemType.show_text && ( {i.layer?.itemType.show_text && <TextView truncate text={i.text} itemId={i.id} />}
<TextView truncate item={i} itemTextField={i.layer.itemTextField} />
)}
</div> </div>
<DateUserInfo item={i}></DateUserInfo> <DateUserInfo item={i}></DateUserInfo>
</div> </div>

View File

@ -8,7 +8,6 @@ import { useNavigate } from 'react-router-dom'
import { useItems } from '#components/Map/hooks/useItems' import { useItems } from '#components/Map/hooks/useItems'
import { useTags } from '#components/Map/hooks/useTags' import { useTags } from '#components/Map/hooks/useTags'
import { getValue } from '#utils/GetValue'
import { MapOverlayPage } from './MapOverlayPage' import { MapOverlayPage } from './MapOverlayPage'
import { TagView } from './TagView' import { TagView } from './TagView'
@ -42,21 +41,16 @@ export const MarketView = () => {
useEffect(() => { useEffect(() => {
setOffers([]) setOffers([])
setNeeds([]) setNeeds([])
items.map((i) => { for (const item of items) {
i.layer?.itemOffersField && item.offers?.forEach((o) => {
getValue(i, i.layer.itemOffersField)?.map((o) => { const tag = tags.find((t) => t.id === o.tags_id)
const tag = tags.find((t) => t.id === o.tags_id) tag && setOffers((current) => [...current, tag])
tag && setOffers((current) => [...current, tag]) })
return null item.needs?.forEach((n) => {
}) const tag = tags.find((t) => t.id === n.tags_id)
i.layer?.itemNeedsField && tag && setNeeds((current) => [...current, tag])
getValue(i, i.layer.itemNeedsField)?.map((n) => { })
const tag = tags.find((t) => t.id === n.tags_id) }
tag && setNeeds((current) => [...current, tag])
return null
})
return null
})
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(offers) console.log(offers)

View File

@ -30,12 +30,10 @@ import type { Item } from '#types/Item'
export const OverlayItemsIndexPage = ({ export const OverlayItemsIndexPage = ({
url, url,
layerName, layerName,
parameterField,
plusButton = true, plusButton = true,
}: { }: {
layerName: string layerName: string
url: string url: string
parameterField: string
plusButton?: boolean plusButton?: boolean
}) => { }) => {
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
@ -165,7 +163,6 @@ export const OverlayItemsIndexPage = ({
i={i} i={i}
loading={loading} loading={loading}
url={url} url={url}
parameterField={parameterField}
deleteCallback={() => deleteItem(i)} deleteCallback={() => deleteItem(i)}
/> />
</div> </div>

View File

@ -1,14 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
export function getValue(obj, path) {
if (!obj || typeof path !== 'string') return undefined
const pathArray = path.split('.') // Use a different variable for the split path
for (let i = 0, len = pathArray.length; i < len; i++) {
if (!obj) return undefined // Check if obj is falsy at each step
// eslint-disable-next-line security/detect-object-injection
obj = obj[pathArray[i]] // Dive one level deeper
}
return obj // Return the final value
}

View File

@ -17,4 +17,6 @@ export interface FormState {
offers: Tag[] offers: Tag[]
needs: Tag[] needs: Tag[]
relations: Item[] relations: Item[]
start: string
end: string
} }

34
src/types/Item.d.ts vendored
View File

@ -1,14 +1,26 @@
import type { ItemsApi } from './ItemsApi' import type { ItemsApi } from './ItemsApi'
import type { ItemType } from './ItemType'
import type { LayerProps } from './LayerProps' import type { LayerProps } from './LayerProps'
import type { Relation } from './Relation' import type { Relation } from './Relation'
import type { UserItem } from './UserItem' import type { UserItem } from './UserItem'
import type { Point } from 'geojson' import type { Point } from 'geojson'
type TagIds = { tags_id: string }[]
interface GalleryItem {
directus_files_id: {
id: number
width: number
height: number
}
}
export interface Item { export interface Item {
id: string id: string
name: string name: string
text: string text?: string
position?: Point data?: string
position?: Point | null
date_created?: string date_created?: string
date_updated?: string | null date_updated?: string | null
start?: string start?: string
@ -24,8 +36,22 @@ export interface Item {
slug?: string slug?: string
user_created?: UserItem user_created?: UserItem
image?: string image?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any group_type?: string
[key: string]: any offers?: TagIds
needs?: TagIds
status?: string
color?: string
markerIcon?: string
avatar?: string
new?: boolean
contact?: string
telephone?: string
next_appointment?: string
type?: ItemType
gallery?: GalleryItem[]
// {
// coordinates: [number, number]
/* constructor( /* constructor(
id: string, id: string,
name: string, name: string,

View File

@ -1,5 +1,15 @@
import type { Key } from 'react'
export interface ItemType { export interface ItemType {
name: string name: string
show_start_end: boolean
show_text: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any profileTemplate: { collection: string | number; id: Key | null | undefined; item: any }[]
offers_and_needs: boolean
icon_as_labels: unknown
relations: boolean
template: string
show_start_end_input: boolean
questlog: boolean
} }

View File

@ -2,7 +2,7 @@ export interface ItemsApi<T> {
getItems(): Promise<T[]> getItems(): Promise<T[]>
getItem?(id: string): Promise<T> getItem?(id: string): Promise<T>
createItem?(item: T): Promise<T> createItem?(item: T): Promise<T>
updateItem?(item: T): Promise<T> updateItem?(item: Partial<T>): Promise<T>
deleteItem?(id: string): Promise<boolean> deleteItem?(id: string): Promise<boolean>
collectionName?: string collectionName?: string
} }

View File

@ -18,17 +18,6 @@ export interface LayerProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
api?: ItemsApi<any> api?: ItemsApi<any>
itemType: ItemType itemType: ItemType
itemNameField?: string
itemSubnameField?: string
itemTextField?: string
itemAvatarField?: string
itemColorField?: string
itemOwnerField?: string
itemTagsField?: string
itemLatitudeField?: string
itemLongitudeField?: string
itemOffersField?: string
itemNeedsField?: string
onlyOnePerOwner?: boolean onlyOnePerOwner?: boolean
customEditLink?: string customEditLink?: string
customEditParameter?: string customEditParameter?: string

View File

@ -14,8 +14,8 @@ export default defineConfig({
exclude: [...configDefaults.exclude], exclude: [...configDefaults.exclude],
thresholds: { thresholds: {
lines: 0, lines: 0,
functions: 67, functions: 66,
branches: 67, branches: 66,
statements: 0, statements: 0,
}, },
}, },