fix(source): select position on index page (#240)

* 3.0.97

* 3.0.98

* 3.0.99

* add select position to item menu on index page

* added tooltips to headermenu

* optimized toasts while updating item position
This commit is contained in:
Anton Tranelis 2025-06-11 13:04:04 +02:00 committed by GitHub
parent a7a50af896
commit 55c39bef88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 80 additions and 25 deletions

View File

@ -137,7 +137,8 @@ export function HeaderView({
editCallback && ( editCallback && (
<li> <li>
<a <a
className='tw:text-base-content! tw:cursor-pointer' className='tw:text-base-content! tw:tooltip tw:tooltip-right tw:cursor-pointer'
data-tip='Edit'
onClick={(e) => onClick={(e) =>
item.layer?.customEditLink item.layer?.customEditLink
? navigate( ? navigate(
@ -155,7 +156,8 @@ export function HeaderView({
setPositionCallback && ( setPositionCallback && (
<li> <li>
<a <a
className='tw:text-base-content! tw:cursor-pointer' className='tw:text-base-content! tw:tooltip tw:tooltip-right tw:cursor-pointer'
data-tip='Set position'
onClick={setPositionCallback} onClick={setPositionCallback}
> >
<SVG src={TargetDotSVG} className='tw:w-5 tw:h-5' /> <SVG src={TargetDotSVG} className='tw:w-5 tw:h-5' />
@ -166,7 +168,11 @@ export function HeaderView({
hasUserPermission(api.collectionName!, 'delete', item) && hasUserPermission(api.collectionName!, 'delete', item) &&
deleteCallback && ( deleteCallback && (
<li> <li>
<a className='tw:cursor-pointer tw:text-error!' onClick={openDeleteModal}> <a
className='tw:text-error! tw:tooltip tw:tooltip-right tw:cursor-pointer'
data-tip='Delete'
onClick={openDeleteModal}
>
{loading ? ( {loading ? (
<span className='tw:loading tw:loading-spinner tw:loading-sm'></span> <span className='tw:loading tw:loading-spinner tw:loading-sm'></span>
) : ( ) : (

View File

@ -1,5 +1,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ import type { Item } from '#types/Item'
export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItemPosition }) => { import type { LayerProps } from '#types/LayerProps'
export const SelectPosition = ({
setSelectNewItemPosition,
selectNewItemPosition,
}: {
setSelectNewItemPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>
selectNewItemPosition?: Item | LayerProps | null
}) => {
return ( return (
<div className='tw:animate-pulseGrow tw:button tw:z-1000 tw:absolute tw:right-5 tw:top-4 tw:drop-shadow-md'> <div className='tw:animate-pulseGrow tw:button tw:z-1000 tw:absolute tw:right-5 tw:top-4 tw:drop-shadow-md'>
<label <label
@ -12,7 +20,14 @@ export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItemP
</label> </label>
<div className='tw:alert tw:bg-base-100 tw:text-base-content'> <div className='tw:alert tw:bg-base-100 tw:text-base-content'>
<div> <div>
{selectNewItemPosition && 'text' in selectNewItemPosition && (
<span className='tw:text-lg'>
Select new position of <b>{selectNewItemPosition.name}</b> on the map!
</span>
)}
{selectNewItemPosition && 'menuIcon' in selectNewItemPosition && (
<span className='tw:text-lg'>Select position on the map!</span> <span className='tw:text-lg'>Select position on the map!</span>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -307,7 +307,10 @@ export function UtopiaMapInner({
<MapEventListener /> <MapEventListener />
<AddButton triggerAction={setSelectNewItemPosition} /> <AddButton triggerAction={setSelectNewItemPosition} />
{selectNewItemPosition != null && ( {selectNewItemPosition != null && (
<SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} /> <SelectPosition
selectNewItemPosition={selectNewItemPosition}
setSelectNewItemPosition={setSelectNewItemPosition}
/>
)} )}
</div> </div>
) )

View File

@ -5,8 +5,7 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/await-thenable */ /* eslint-disable @typescript-eslint/await-thenable */
/* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import { createContext, useContext, useEffect, useState } from 'react' import { createContext, useContext, useEffect, useState } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
@ -87,6 +86,7 @@ function useSelectPositionManager(): {
}, [mapClicked]) }, [mapClicked])
const itemUpdateParent = async (updatedItem: Item) => { const itemUpdateParent = async (updatedItem: Item) => {
const toastId = toast.loading('Adding item to ' + markerClicked?.name)
if ( if (
markerClicked?.layer?.api?.collectionName && markerClicked?.layer?.api?.collectionName &&
hasUserPermission(markerClicked.layer.api.collectionName, 'update', markerClicked) hasUserPermission(markerClicked.layer.api.collectionName, 'update', markerClicked)
@ -99,40 +99,57 @@ function useSelectPositionManager(): {
position: null, position: null,
}) })
success = true success = true
// eslint-disable-next-line no-catch-all/no-catch-all } catch (error: unknown) {
} catch (error) { if (error instanceof Error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument toast.update(toastId, { render: error.message, type: 'error' })
toast.error(error.toString()) } else if (typeof error === 'string') {
toast.update(toastId, { render: error, type: 'error' })
} else {
throw error
}
} }
if (success) { if (success) {
await updateItem({ ...updatedItem, parent: updatedItem.parent, position: undefined }) await updateItem({ ...updatedItem, parent: updatedItem.parent, position: undefined })
await linkItem(updatedItem.id) await linkItem(updatedItem.id)
toast.success('Item position updated') toast.update(toastId, {
render: 'Item position updated',
type: 'success',
isLoading: false,
})
setSelectPosition(null) setSelectPosition(null)
setMarkerClicked(null) setMarkerClicked(null)
} }
} else { } else {
setSelectPosition(null) setSelectPosition(null)
toast.error("you don't have permission to add items to " + markerClicked?.name) toast.update(toastId, {
render: "you don't have permission to add items to " + markerClicked?.name,
type: 'error',
isLoading: false,
})
} }
} }
const itemUpdatePosition = async (updatedItem: Item) => { const itemUpdatePosition = async (updatedItem: Item) => {
let success = false let success = false
const toastId = toast.loading('Updating item position')
try { try {
await updatedItem.layer?.api?.updateItem!({ await updatedItem.layer?.api?.updateItem!({
id: updatedItem.id, id: updatedItem.id,
position: updatedItem.position, position: updatedItem.position,
}) })
success = true success = true
// eslint-disable-next-line no-catch-all/no-catch-all } catch (error: unknown) {
} catch (error) { if (error instanceof Error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument toast.update(toastId, { render: error.message, type: 'error', isLoading: false })
toast.error(error.toString()) } else if (typeof error === 'string') {
toast.update(toastId, { render: error, type: 'error', isLoading: false })
} else {
throw error
}
} }
if (success) { if (success) {
updateItem(updatedItem) updateItem(updatedItem)
toast.success('Item position updated') toast.update(toastId, { render: 'Item position updated', type: 'success', isLoading: false })
} }
} }
@ -145,17 +162,22 @@ function useSelectPositionManager(): {
const updatedItem = { id: markerClicked.id, relations: newRelations } const updatedItem = { id: markerClicked.id, relations: newRelations }
let success = false let success = false
const toastId = toast.loading('Linking item')
try { try {
await markerClicked.layer?.api?.updateItem!(updatedItem) await markerClicked.layer?.api?.updateItem!(updatedItem)
success = true success = true
// eslint-disable-next-line no-catch-all/no-catch-all } catch (error: unknown) {
} catch (error) { if (error instanceof Error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument toast.update(toastId, { render: error.message, type: 'error', isLoading: false })
toast.error(error.toString()) } else if (typeof error === 'string') {
toast.update(toastId, { render: error, type: 'error', isLoading: false })
} else {
throw error
}
} }
if (success) { if (success) {
updateItem({ ...markerClicked, relations: newRelations }) updateItem({ ...markerClicked, relations: newRelations })
toast.success('Item linked') toast.update(toastId, { render: 'Item linked', type: 'success', isLoading: false })
} }
} }
} }

View File

@ -1,5 +1,7 @@
import { useMap } from 'react-leaflet'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useSetSelectPosition } from '#components/Map/hooks/useSelectPosition'
import useWindowDimensions from '#components/Map/hooks/useWindowDimension' import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
import { StartEndView, TextView } from '#components/Map/Subcomponents/ItemPopupComponents' import { StartEndView, TextView } from '#components/Map/Subcomponents/ItemPopupComponents'
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView' import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
@ -21,6 +23,8 @@ export const ItemCard = ({
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
const windowDimensions = useWindowDimensions() const windowDimensions = useWindowDimensions()
const map = useMap()
const setSelectPosition = useSetSelectPosition()
return ( return (
<div <div
@ -38,6 +42,11 @@ export const ItemCard = ({
item={i} item={i}
api={i.layer?.api} api={i.layer?.api}
editCallback={() => navigate('/edit-item/' + i.id)} editCallback={() => navigate('/edit-item/' + i.id)}
setPositionCallback={() => {
map.closePopup()
setSelectPosition(i)
navigate('/')
}}
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'>