utopia-ui/src/Components/Templates/OverlayItemsIndexPage.tsx
Anton Tranelis 9e6bcf1846
fix(source): update tailwind and daisyui (#196)
* removed daisy from config

* removed tw-elements artefact

* removed comments from tailwind config

* removed safelist

* migrated to tailwind4 and daisyui5

* deleted tailwind.config.js which is not eeded anymore

* 3.0.79

* version number

* fixed broken layouts

* more fixing

* more layout fixing

* tested theming

* small fixes

* adapt snapshots to changes

* package.json: add unit test update script

* more ui refactoring & theme controller

* ui improvements

* package-lock.json

* fix linting

* fixed tabs

* fix linting

* fixed typing

---------

Co-authored-by: mahula <lenzmath@posteo.de>
2025-04-25 16:03:42 +02:00

234 lines
8.2 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-toastify'
import { useAuth } from '#components/Auth/useAuth'
import { TextInput, TextAreaInput } from '#components/Input'
import { PopupStartEndInput } from '#components/Map'
import { useFilterTags } from '#components/Map/hooks/useFilter'
import { useAddItem, useItems, useRemoveItem } from '#components/Map/hooks/useItems'
import { useLayers } from '#components/Map/hooks/useLayers'
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
import { Control } from '#components/Map/Subcomponents/Controls/Control'
import { SearchControl } from '#components/Map/Subcomponents/Controls/SearchControl'
import { TagsControl } from '#components/Map/Subcomponents/Controls/TagsControl'
import { PlusButton } from '#components/Profile/Subcomponents/PlusButton'
import { hashTagRegex } from '#utils/HashTagRegex'
import { randomColor } from '#utils/RandomColor'
import { ItemCard } from './ItemCard'
import { MapOverlayPage } from './MapOverlayPage'
import type { Item } from '#types/Item'
/**
* @category Templates
*/
export const OverlayItemsIndexPage = ({
url,
layerName,
parameterField,
plusButton = true,
}: {
layerName: string
url: string
parameterField?: string
plusButton?: boolean
}) => {
const [loading, setLoading] = useState<boolean>(false)
const [addItemPopupType, setAddItemPopupType] = useState<string>('')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const parameterFieldDummy = parameterField
const tabRef = useRef<HTMLFormElement>(null)
function scroll() {
tabRef.current?.scrollIntoView()
}
useEffect(() => {
scroll()
}, [addItemPopupType])
useEffect(() => {
setAddItemPopupType('')
}, [layerName])
const tags = useTags()
const addTag = useAddTag()
const { user } = useAuth()
const items = useItems()
const addItem = useAddItem()
const removeItem = useRemoveItem()
const layers = useLayers()
const filterTags = useFilterTags()
const getItemTags = useGetItemTags()
const layer = layers.find((l) => l.name === layerName)
const submitNewItem = async (evt: React.FormEvent<HTMLFormElement>) => {
evt.preventDefault()
const formItem: Item = {} as Item
Array.from(evt.target as any).forEach((input: HTMLInputElement) => {
if (input.name) {
formItem[input.name] = input.value
}
})
setLoading(true)
if (formItem.text) {
formItem.text
.toLocaleLowerCase()
.match(hashTagRegex)
?.map((tag) => {
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
}
return null
})
}
const uuid = crypto.randomUUID()
let success = false
try {
await layer?.api?.createItem!({ ...formItem, id: uuid })
success = true
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
toast.error(error.toString())
}
if (success) {
toast.success('New item created')
}
addItem({ ...formItem, user_created: user ?? undefined, id: uuid, layer, public_edit: !user })
setLoading(false)
setAddItemPopupType('')
}
const deleteItem = async (item: Item) => {
setLoading(true)
let success = false
try {
await layer?.api?.deleteItem!(item.id)
success = true
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
toast.error(error.toString())
}
if (success) {
toast.success('Item deleted')
}
removeItem(item)
setLoading(false)
}
return (
<>
<MapOverlayPage className='tw:rounded-none tw:overflow-y-auto tw:bg-base-200 tw:p-4!'>
<div className='tw:flex tw:flex-col tw:h-full'>
<div className='tw:flex-none'>
<Control position='topLeft' zIndex='1000' absolute={false}>
<SearchControl />
<TagsControl />
</Control>
</div>
<div className='tw:overflow-scroll fade tw:flex-1'>
<div className='tw:columns-1 tw:md:columns-2 tw:lg:columns-3 tw:2xl:columns-4 tw:gap-6 tw:pt-4'>
{items
.filter((i) => i.layer?.name === layerName)
.filter((item) =>
filterTags.length === 0
? item
: filterTags.some((tag) =>
getItemTags(item).some(
(filterTag) =>
filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase(),
),
),
)
.sort((a, b) => {
// Convert date_created to milliseconds, handle undefined by converting to lowest possible date (0 milliseconds)
const dateA = a.date_updated
? new Date(a.date_updated).getTime()
: a.date_created
? new Date(a.date_created).getTime()
: 0
const dateB = b.date_updated
? new Date(b.date_updated).getTime()
: b.date_created
? new Date(b.date_created).getTime()
: 0
return dateB - dateA // Subtracts milliseconds which are numbers
})
.map((i, k) => (
<div key={k} className='tw:break-inside-avoid tw:mb-6'>
<ItemCard
i={i}
loading={loading}
url={url}
deleteCallback={() => deleteItem(i)}
/>
</div>
))}
{addItemPopupType === 'place' && (
<form ref={tabRef} autoComplete='off' onSubmit={(e) => submitNewItem(e)}>
<div className='tw:cursor-pointer tw:break-inside-avoid card tw:border-[1px] tw:border-base-300 card-body tw:shadow-xl tw:bg-base-100 tw:text-base-content tw:p-6 tw:mb-10'>
<label
className='btn btn-sm tw:rounded-2xl btn-circle btn-ghost tw:hover:bg-transparent tw:absolute tw:right-0 tw:top-0 tw:text-gray-600'
onClick={() => setAddItemPopupType('')}
>
<p className='tw:text-center'></p>
</label>
<TextInput
type='text'
placeholder='Name'
dataField='name'
defaultValue={''}
inputStyle=''
/>
{layer?.itemType.show_start_end_input && <PopupStartEndInput />}
<TextAreaInput
placeholder='Text'
dataField='text'
defaultValue={''}
inputStyle='tw:h-40 tw:mt-5'
/>
<div className='tw:flex tw:justify-center'>
<button
className={
loading
? 'btn btn-disabled tw:mt-5 tw:place-self-center'
: 'btn tw:mt-5 tw:place-self-center'
}
type='submit'
>
{loading ? <span className='loading loading-spinner'></span> : 'Save'}
</button>
</div>
</div>
</form>
)}
</div>
</div>
</div>
</MapOverlayPage>
{plusButton && (
<PlusButton
layer={layer}
triggerAction={() => {
setAddItemPopupType('place')
scroll()
}}
color={'#777'}
collection='items'
/>
)}
</>
)
}