Improve typing of items, remove getValue

This commit is contained in:
Maximilian Harz 2025-01-31 23:29:32 +01:00
parent f5b7b9267f
commit 4316387ecb
30 changed files with 166 additions and 279 deletions

View File

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

View File

@ -23,7 +23,6 @@ import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
import { useTags } from '#components/Map/hooks/useTags'
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
import { decodeTag } from '#utils/FormatTags'
import { getValue } from '#utils/GetValue'
import MarkerIconFactory from '#utils/MarkerIconFactory'
import { LocateControl } from './LocateControl'
@ -73,8 +72,6 @@ export const SearchControl = () => {
searchGeo()
setItemsResults(
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 (
value.length > 2 &&
((item.layer?.listed && item.name.toLowerCase().includes(value.toLowerCase())) ||

View File

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

View File

@ -3,7 +3,6 @@
import { Link } from 'react-router-dom'
import { useGetItemTags } from '#components/Map/hooks/useTags'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item'
@ -11,23 +10,21 @@ export const PopupButton = ({
url,
parameterField,
text,
colorField,
item,
}: {
url: string
parameterField?: string
text: string
colorField?: string
item?: Item
}) => {
const params = new URLSearchParams(window.location.search)
const getItemTags = useGetItemTags()
return (
<Link to={`${url}/${parameterField ? getValue(item, parameterField) : ''}?${params}`}>
<Link to={`${url}/${parameterField ? item?.id : ''}?${params}`}>
<button
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'
>

View File

@ -12,41 +12,38 @@ import remarkBreaks from 'remark-breaks'
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
import { useTags } from '#components/Map/hooks/useTags'
import { decodeTag } from '#utils/FormatTags'
import { getValue } from '#utils/GetValue'
import { hashTagRegex } from '#utils/HashTagRegex'
import { fixUrls, mailRegex } from '#utils/ReplaceURLs'
import type { Item } from '#types/Item'
import type { Tag } from '#types/Tag'
export const TextView = ({
item,
itemId,
text,
truncate = false,
itemTextField,
rawText,
}: {
item?: Item
itemId: string
text?: string
truncate?: boolean
itemTextField?: string
rawText?: string
}) => {
const tags = useTags()
const addFilterTag = useAddFilterTag()
let text = ''
let innerText = ''
let replacedText = ''
if (rawText) {
text = replacedText = rawText
} else if (itemTextField && item) {
text = getValue(item, itemTextField)
} else {
text = item?.layer?.itemTextField && item ? getValue(item, item.layer.itemTextField) : ''
innerText = replacedText = rawText
} else if (text) {
innerText = text
}
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) {
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
@ -114,16 +111,16 @@ export const TextView = ({
const CustomHashTagLink = ({
children,
tag,
item,
itemId,
}: {
children: string
tag: Tag
item?: Item
itemId: string
}) => {
return (
<a
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) => {
e.stopPropagation()
addFilterTag(tag)
@ -173,7 +170,7 @@ export const TextView = ({
)
if (tag)
return (
<CustomHashTagLink tag={tag} item={item}>
<CustomHashTagLink tag={tag} itemId={itemId}>
{children}
</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 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 { containsUUID } from '#utils/ContainsUUID'
import { getValue } from '#utils/GetValue'
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
import { useAddVisibleLayer } from './hooks/useFilter'
@ -75,9 +74,10 @@ export function UtopiaMapInner({
setTimeout(() => {
toast(
<>
<TextView rawText={'## Do you like this Map?'} />
<TextView itemId='' rawText={'## Do you like this Map?'} />
<div>
<TextView
itemId=''
rawText={'Support us building free opensource maps and help us grow 🌱☀️'}
/>
<PopupButton url={'https://opencollective.com/utopia-project'} text={'Donate'} />
@ -120,7 +120,6 @@ export function UtopiaMapInner({
}
let title = ''
if (item?.name) title = item.name
else if (item?.layer?.itemNameField) title = getValue(item, item.layer.itemNameField)
document.title = `${document.title.split('-')[0]} - ${title}`
}
},
@ -142,8 +141,6 @@ export function UtopiaMapInner({
})
let title = ''
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
.querySelector('meta[property="og:title"]')

View File

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

View File

@ -5,12 +5,8 @@
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* 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 { getValue } from '#utils/GetValue'
import { hashTagRegex } from '#utils/HashTagRegex'
import type { Item } from '#types/Item'
@ -96,8 +92,7 @@ function useTagsManager(initialTags: Tag[]): {
const getItemTags = useCallback(
(item: Item) => {
const text =
item.layer?.itemTextField && item ? getValue(item, item.layer.itemTextField) : undefined
const text = item.text
const itemTagStrings = text?.match(hashTagRegex)
const itemTags: Tag[] = []
itemTagStrings?.map((tag) => {
@ -108,18 +103,15 @@ function useTagsManager(initialTags: Tag[]): {
}
return null
})
item.layer?.itemOffersField &&
getValue(item, item.layer.itemOffersField)?.map((o) => {
const offer = tags.find((t) => t.id === o.tags_id)
offer && itemTags.push(offer)
return null
})
item.layer?.itemNeedsField &&
getValue(item, item.layer.itemNeedsField)?.map((n) => {
const need = tags.find((t) => t.id === n.tags_id)
need && itemTags.push(need)
return null
})
// Could be refactored as it occurs in multiple places
item.offers?.forEach((o) => {
const offer = tags.find((t) => t.id === o.tags_id)
offer && itemTags.push(offer)
})
item.needs?.forEach((n) => {
const need = tags.find((t) => t.id === n.tags_id)
need && itemTags.push(need)
})
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/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { useEffect, useState } from 'react'
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 { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
import { MapOverlayPage } from '#components/Templates'
import { getValue } from '#utils/GetValue'
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
import { FormHeader } from './Subcomponents/FormHeader'
@ -23,11 +19,12 @@ import { OnepagerForm } from './Templates/OnepagerForm'
import { SimpleForm } from './Templates/SimpleForm'
import { TabsForm } from './Templates/TabsForm'
import type { FormState } from '#types/FormState'
import type { Item } from '#types/Item'
import type { Tag } from '#types/Tag'
export function ProfileForm() {
const [state, setState] = useState({
const [state, setState] = useState<FormState>({
color: '',
id: '',
group_type: 'wuerdekompass',
@ -91,11 +88,9 @@ export function ProfileForm() {
useEffect(() => {
const newColor =
item.layer?.itemColorField && getValue(item, item.layer.itemColorField)
? getValue(item, item.layer.itemColorField)
: getItemTags(item) && getItemTags(item)[0]?.color
? getItemTags(item)[0].color
: item.layer?.markerDefaultColor
(item.color ?? (getItemTags(item) && getItemTags(item)[0]?.color))
? getItemTags(item)[0].color
: item.layer?.markerDefaultColor
const offers = (item.offers ?? []).reduce((acc: Tag[], o) => {
const offer = tags.find((t) => t.id === o.tags_id)
@ -116,7 +111,7 @@ export function ProfileForm() {
}, [])
setState({
color: newColor,
color: newColor ?? '',
id: item?.id ?? '',
group_type: item?.group_type ?? '',
status: item?.status ?? '',
@ -127,7 +122,8 @@ export function ProfileForm() {
telephone: item?.telephone ?? '',
next_appointment: item?.next_appointment ?? '',
image: item?.image ?? '',
marker_icon: item?.marker_icon ?? '',
// Do we actually mean marker_icon here?
marker_icon: item?.markerIcon ?? '',
offers,
needs,
relations,
@ -140,7 +136,7 @@ export function ProfileForm() {
const [template, setTemplate] = useState<string>('')
useEffect(() => {
setTemplate(item.layer?.itemType.template || appState.userType)
setTemplate(item.layer?.itemType.template ?? appState.userType)
}, [appState.userType, item])
return (
@ -198,7 +194,8 @@ export function ProfileForm() {
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
type='submit'
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',
}}
>

View File

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

View File

@ -10,7 +10,6 @@ import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useGetItemTags } from '#components/Map/hooks/useTags'
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
import DialogModal from '#components/Templates/DialogModal'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item'
@ -20,7 +19,6 @@ export function ActionButton({
triggerItemSelected,
existingRelations,
itemType,
colorField,
collection = 'items',
customStyle,
}: {
@ -28,7 +26,6 @@ export function ActionButton({
triggerItemSelected?: any
existingRelations: Item[]
itemType?: string
colorField?: string
collection?: string
customStyle?: string
item: Item
@ -58,7 +55,7 @@ export function ActionButton({
setModalOpen(true)
}}
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: `${(item.color ?? (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color)) ? getItemTags(item)[0].color : item.layer?.markerDefaultColor}`,
color: '#fff',
}}
>
@ -82,7 +79,7 @@ export function ActionButton({
triggerAddButton()
}}
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: `${(item.color ?? (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color)) ? getItemTags(item)[0].color : item.layer?.markerDefaultColor}`,
color: '#fff',
}}
>

View File

@ -1,5 +1,5 @@
/* 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/restrict-plus-operands */
import { useEffect, useState } from 'react'

View File

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

View File

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

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { TextView } from '#components/Map'
import { getValue } from '#utils/GetValue'
import type { Item } from '#types/Item'
@ -17,11 +15,11 @@ export const ProfileTextView = ({
}) => {
return (
<div className='tw-my-10 tw-mt-2 tw-px-6'>
{!(getValue(item, dataField) === '' && hideWhenEmpty) && (
{!(item.data === '' && hideWhenEmpty) && (
<h2 className='tw-text-lg tw-font-semibold'>{heading}</h2>
)}
<div className='tw-mt-2 tw-text-sm'>
<TextView rawText={dataField ? getValue(item, dataField) : getValue(item, 'text')} />
<TextView itemId={item.id} rawText={dataField ? item.data : item.text} />
</div>
</div>
)

View File

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

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { TextView } from '#components/Map'
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} />}
{/* Description Section */}
<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>
{/* Next Appointment Section */}
{item.next_appointment && (
<div className='tw-my-10 tw-px-6'>
<h2 className='tw-text-lg tw-font-semibold'>Nächste Termine</h2>
<div className='tw-mt-2 tw-text-sm'>
<TextView rawText={item.next_appointment} />
<TextView itemId={item.id} rawText={item.next_appointment} />
</div>
</div>
)}

View File

@ -5,7 +5,7 @@ import type { Item } from '#types/Item'
export const SimpleView = ({ item }: { item: Item }) => {
return (
<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>
)
}

View File

@ -197,7 +197,7 @@ export const TabsForm = ({
loading={loading}
/>
<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>
))}
@ -208,7 +208,6 @@ export const TabsForm = ({
item={item}
existingRelations={state.relations}
triggerItemSelected={(id) => linkItem(id, item, updateItem)}
colorField={item.layer.itemColorField}
></ActionButton>
)}
</div>

View File

@ -108,9 +108,9 @@ export const TabsView = ({
<StartEndView item={item}></StartEndView>
</div>
)}
<TextView item={item} />
<TextView text={item.text} itemId={item.id} />
<div className='tw-h-4'></div>
<TextView item={item} itemTextField='contact' />
<TextView text={item.contact} itemId={item.id} />
</div>
{item.layer?.itemType.questlog && (
<>
@ -267,7 +267,7 @@ export const TabsView = ({
loading={loading}
/>
<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>
))}
@ -277,7 +277,6 @@ export const TabsView = ({
item={item}
existingRelations={relations}
triggerItemSelected={linkItem}
colorField={item.layer.itemColorField}
></ActionButton>
)}
</div>

View File

@ -2,14 +2,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useNavigate } from 'react-router-dom'
import { StartEndView, TextView } from '#components/Map'
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
import { getValue } from '#utils/GetValue'
import { DateUserInfo } from './DateUserInfo'
@ -19,13 +17,11 @@ export const ItemCard = ({
i,
loading,
url,
parameterField,
deleteCallback,
}: {
i: Item
loading: boolean
url: string
parameterField: string
deleteCallback: any
}) => {
const navigate = useNavigate()
@ -35,27 +31,23 @@ export const ItemCard = ({
<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'
onClick={() => {
// We could have an onClick callback instead
const params = new URLSearchParams(window.location.search)
if (windowDimensions.width < 786 && i.position)
navigate('/' + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
else navigate(url + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
navigate('/' + i.id + `${params ? `?${params}` : ''}`)
else navigate(url + i.id + `${params ? `?${params}` : ''}`)
}}
>
<HeaderView
loading={loading}
item={i}
api={i.layer?.api}
itemAvatarField={i.layer?.itemAvatarField}
itemNameField={i.layer?.itemNameField}
itemSubnameField={i.layer?.itemSubnameField}
editCallback={() => navigate('/edit-item/' + i.id)}
deleteCallback={() => deleteCallback(i)}
></HeaderView>
<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_text && (
<TextView truncate item={i} itemTextField={i.layer.itemTextField} />
)}
{i.layer?.itemType.show_text && <TextView truncate text={i.text} itemId={i.id} />}
</div>
<DateUserInfo item={i}></DateUserInfo>
</div>

View File

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

View File

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

View File

@ -1,17 +0,0 @@
import type { Item } from '#types/Item'
function getNestedValue(obj: Object, path: string) {
re
}
export function getValue(obj: Item | undefined, path: string): Item | string | undefined {
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]] as Item // Dive one level deeper
}
return obj // Return the final value
}

View File

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

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

@ -1,13 +1,19 @@
import type { ItemsApi } from './ItemsApi'
import type { ItemType } from './ItemType'
import type { LayerProps } from './LayerProps'
import type { Relation } from './Relation'
import type { UserItem } from './UserItem'
import type { Point } from 'geojson'
interface Special_Find_Name {
tags_id: string
}
export interface Item {
id: string
name: string
text: string
data?: string
position?: Point
date_created?: string
date_updated?: string | null
@ -24,7 +30,21 @@ export interface Item {
slug?: string
user_created?: UserItem
image?: string
group_type: string
group_type?: string
offers?: Special_Find_Name[]
needs?: Special_Find_Name[]
status?: string
color?: string
markerIcon?: string
avatar?: string
new?: boolean
contact?: string
telephone?: string
next_appointment?: string
type?: ItemType
// {
// coordinates: [number, number]
/* constructor(
id: string,
name: string,

View File

@ -1,5 +1,15 @@
import type { Key } from 'react'
export interface ItemType {
name: string
show_start_end: boolean
show_text: boolean
// 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

@ -17,18 +17,13 @@ export interface LayerProps {
markerDefaultColor2?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api?: ItemsApi<any>
itemType: ItemType
itemNameField?: string
itemSubnameField?: string
itemTextField?: string
itemAvatarField?: string
itemColorField?: string
itemOwnerField?: string
itemTagsField?: string
itemLatitudeField?: string
itemLongitudeField?: string
itemOffersField?: string
itemNeedsField?: string
itemType: ItemType // How does this relate to Item type defined in Item.d.ts?
// TODO Conditionally type items with .avatar etc.?
hasAvatar?: boolean
hasColor?: boolean
hasTags?: boolean
hasOffers?: boolean
hasNeeds?: boolean
onlyOnePerOwner?: boolean
customEditLink?: string
customEditParameter?: string