Merge pull request #21 from utopia-os/eslint-standart

fix(other): eslint standard
This commit is contained in:
Ulf Gebhardt 2024-11-02 22:31:25 +01:00 committed by GitHub
commit 3464eadc89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
125 changed files with 5074 additions and 4356 deletions

View File

@ -5,7 +5,7 @@ module.exports = {
es2021: true es2021: true
}, },
extends: [ extends: [
// 'standard', 'standard',
'eslint:recommended', 'eslint:recommended',
// 'plugin:@eslint-community/eslint-comments/recommended', // 'plugin:@eslint-community/eslint-comments/recommended',
// 'plugin:@typescript-eslint/recommended', // 'plugin:@typescript-eslint/recommended',
@ -13,12 +13,12 @@ module.exports = {
// 'plugin:import/typescript', // 'plugin:import/typescript',
// 'plugin:promise/recommended', // 'plugin:promise/recommended',
// 'plugin:security/recommended-legacy', // 'plugin:security/recommended-legacy',
'plugin:react/recommended', 'plugin:react/recommended'
], ],
parserOptions: { parserOptions: {
ecmaVersion: 'latest', ecmaVersion: 'latest',
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
sourceType: 'module', sourceType: 'module'
}, },
plugins: [ plugins: [
// '@typescript-eslint', // '@typescript-eslint',
@ -27,15 +27,15 @@ module.exports = {
// 'security', // 'security',
// 'no-catch-all', // 'no-catch-all',
'react', 'react',
'react-hooks', 'react-hooks'
], ],
settings: { settings: {
// 'import/resolver': { // 'import/resolver': {
// typescript: true, // typescript: true,
// node: true, // node: true,
// }, // },
'react': { react: {
'version': 'detect' version: 'detect'
} }
}, },
rules: { rules: {
@ -139,7 +139,7 @@ module.exports = {
overrides: [ overrides: [
{ {
files: ['*.ts', '*.tsx'], files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser'
// parserOptions: { // parserOptions: {
// tsconfigRootDir: __dirname, // tsconfigRootDir: __dirname,
// project: ['./tsconfig.json', '**/tsconfig.json'], // project: ['./tsconfig.json', '**/tsconfig.json'],
@ -170,7 +170,7 @@ module.exports = {
{ {
files: ['*.json'], files: ['*.json'],
plugins: ['json'], plugins: ['json'],
extends: ['plugin:json/recommended-with-comments'], extends: ['plugin:json/recommended-with-comments']
}, },
// { // {
// files: ['*.{test,spec}.[tj]s'], // files: ['*.{test,spec}.[tj]s'],
@ -206,7 +206,7 @@ module.exports = {
files: ['*.yaml', '*.yml'], files: ['*.yaml', '*.yml'],
parser: 'yaml-eslint-parser', parser: 'yaml-eslint-parser',
plugins: ['yml'], plugins: ['yml'],
extends: ['plugin:yml/prettier'], extends: ['plugin:yml/prettier']
}, }
], ]
} }

1396
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"daisyui": "^4.6.1", "daisyui": "^4.6.1",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-json": "^3.1.0", "eslint-plugin-json": "^3.1.0",
"eslint-plugin-react": "^7.31.8", "eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",

View File

@ -2,7 +2,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {}
} }
} }

View File

@ -16,7 +16,7 @@ export default {
postcss({ postcss({
plugins: [] plugins: []
}), }),
typescript(), typescript()
], ],
external: ['react', 'react-dom', 'leaflet', 'react-leaflet', 'react-toastify', 'react-string-replace', 'react-toastify/dist/ReactToastify.css', 'tw-elements', 'react-router-dom', 'react-leaflet-cluster', '@tanstack/react-query', 'tributejs', 'prop-types', 'leaflet/dist/leaflet.css', '@heroicons/react/20/solid'] external: ['react', 'react-dom', 'leaflet', 'react-leaflet', 'react-toastify', 'react-string-replace', 'react-toastify/dist/ReactToastify.css', 'tw-elements', 'react-router-dom', 'react-leaflet-cluster', '@tanstack/react-query', 'tributejs', 'prop-types', 'leaflet/dist/leaflet.css', '@heroicons/react/20/solid']
} }

View File

@ -2,8 +2,7 @@ import * as React from 'react'
import NavBar from './NavBar' import NavBar from './NavBar'
import { SetAssetsApi } from './SetAssetsApi' import { SetAssetsApi } from './SetAssetsApi'
import { AssetsApi } from '../../types' import { AssetsApi } from '../../types'
import { ContextWrapper } from './ContextWrapper'; import { ContextWrapper } from './ContextWrapper'
export function AppShell ({ appName, children, assetsApi, userType }: { appName: string, children: React.ReactNode, assetsApi: AssetsApi, userType: string }) { export function AppShell ({ appName, children, assetsApi, userType }: { appName: string, children: React.ReactNode, assetsApi: AssetsApi, userType: string }) {
return ( return (

View File

@ -10,24 +10,23 @@ import { PermissionsProvider } from '../Map/hooks/usePermissions'
import { SelectPositionProvider } from '../Map/hooks/useSelectPosition' import { SelectPositionProvider } from '../Map/hooks/useSelectPosition'
import { TagsProvider } from '../Map/hooks/useTags' import { TagsProvider } from '../Map/hooks/useTags'
import { AssetsProvider } from './hooks/useAssets' import { AssetsProvider } from './hooks/useAssets'
import { useContext, createContext } from 'react'; import { useContext, createContext } from 'react'
import { BrowserRouter as Router, useLocation } from 'react-router-dom'; import { BrowserRouter as Router, useLocation } from 'react-router-dom'
// Helper context to determine if the ContextWrapper is already present. // Helper context to determine if the ContextWrapper is already present.
const ContextCheckContext = createContext(false); const ContextCheckContext = createContext(false)
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const ContextWrapper = ({ children }) => { export const ContextWrapper = ({ children }) => {
const isWrapped = useContext(ContextCheckContext); const isWrapped = useContext(ContextCheckContext)
// Check if we are already inside a Router // Check if we are already inside a Router
let location; let location
try { try {
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
location = useLocation(); location = useLocation()
} catch (e) { } catch (e) {
location = null; location = null
} }
// Case 1: Only the Router is missing, but ContextWrapper is already provided // Case 1: Only the Router is missing, but ContextWrapper is already provided
@ -36,7 +35,7 @@ export const ContextWrapper = ({ children }) => {
<Router> <Router>
{children} {children}
</Router> </Router>
); )
} }
// Case 2: Neither Router nor ContextWrapper is present // Case 2: Neither Router nor ContextWrapper is present
@ -49,7 +48,7 @@ export const ContextWrapper = ({ children }) => {
</Wrappers> </Wrappers>
</ContextCheckContext.Provider> </ContextCheckContext.Provider>
</Router> </Router>
); )
} }
// Case 3: Only ContextWrapper is missing // Case 3: Only ContextWrapper is missing
@ -58,16 +57,16 @@ export const ContextWrapper = ({ children }) => {
<ContextCheckContext.Provider value={true}> <ContextCheckContext.Provider value={true}>
<Wrappers>{children}</Wrappers> <Wrappers>{children}</Wrappers>
</ContextCheckContext.Provider> </ContextCheckContext.Provider>
); )
} }
// Case 4: Both Router and ContextWrapper are already present // Case 4: Both Router and ContextWrapper are already present
return children; return children
}; }
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const Wrappers = ({ children }) => { export const Wrappers = ({ children }) => {
const queryClient = new QueryClient(); const queryClient = new QueryClient()
return ( return (
<PermissionsProvider initialPermissions={[]}> <PermissionsProvider initialPermissions={[]}>
@ -103,5 +102,5 @@ export const Wrappers = ({ children }) => {
</LayersProvider> </LayersProvider>
</TagsProvider> </TagsProvider>
</PermissionsProvider> </PermissionsProvider>
); )
}; }

View File

@ -1,23 +1,20 @@
import { useAuth } from "../Auth" import { useAuth } from '../Auth'
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from 'react-router-dom'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import QuestionMarkIcon from '@heroicons/react/24/outline/QuestionMarkCircleIcon' import QuestionMarkIcon from '@heroicons/react/24/outline/QuestionMarkCircleIcon'
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from 'react'
import { useItems } from "../Map/hooks/useItems"; import { useItems } from '../Map/hooks/useItems'
import { Item } from "../../types"; import { Item } from '../../types'
export default function NavBar ({ appName, userType }: { appName: string, userType: string }) { export default function NavBar ({ appName, userType }: { appName: string, userType: string }) {
const { isAuthenticated, user, logout } = useAuth()
const [userProfile, setUserProfile] = useState<Item>({}as Item)
const { isAuthenticated, user, logout } = useAuth(); const items = useItems()
const [userProfile, setUserProfile] = useState<Item>({}as Item);
const items = useItems();
useEffect(() => { useEffect(() => {
const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === userType); const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === userType)
profile ? setUserProfile(profile) : setUserProfile({id: crypto.randomUUID(), name: user?.first_name, text: ""}); profile ? setUserProfile(profile) : setUserProfile({ id: crypto.randomUUID(), name: user?.first_name, text: '' })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, items]) }, [user, items])
@ -25,26 +22,20 @@ export default function NavBar({ appName, userType}: { appName: string, userType
}, [userProfile]) }, [userProfile])
const nameRef = useRef<any>(null); const nameRef = useRef<any>(null)
const [nameWidth, setNameWidth] = useState<number>(0); const [nameWidth, setNameWidth] = useState<number>(0)
const location = useLocation(); const location = useLocation()
const [showNav, setShowNav] = useState<boolean>(false) const [showNav, setShowNav] = useState<boolean>(false)
useEffect(() => { useEffect(() => {
showNav && nameRef && setNameWidth(nameRef.current.scrollWidth) showNav && nameRef && setNameWidth(nameRef.current.scrollWidth)
}, [nameRef, appName, showNav]) }, [nameRef, appName, showNav])
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const embedded = params.get("embedded"); const embedded = params.get('embedded')
embedded!="true" && setShowNav(true) embedded !== 'true' && setShowNav(true)
}, [location]); }, [location])
const onLogout = () => { const onLogout = () => {
toast.promise( toast.promise(
@ -52,21 +43,22 @@ export default function NavBar({ appName, userType}: { appName: string, userType
{ {
success: { success: {
render () { render () {
return `Bye bye` return 'Bye bye'
}, },
// other options // other options
icon: "👋", icon: '👋'
}, },
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, }
}, },
pending: 'logging out ..' pending: 'logging out ..'
}); })
} }
if(showNav) return ( if (showNav) {
return (
<> <>
<div className="tw-navbar tw-bg-base-100 tw-z-[10000] tw-shadow-xl tw-relative"> <div className="tw-navbar tw-bg-base-100 tw-z-[10000] tw-shadow-xl tw-relative">
<button className="tw-btn tw-btn-square tw-btn-ghost" <button className="tw-btn tw-btn-square tw-btn-ghost"
@ -77,20 +69,18 @@ export default function NavBar({ appName, userType}: { appName: string, userType
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="tw-inline-block tw-w-5 tw-h-5 tw-stroke-current"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="tw-inline-block tw-w-5 tw-h-5 tw-stroke-current"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
</button> </button>
<div className="tw-flex-1 tw-mr-2"> <div className="tw-flex-1 tw-mr-2">
<div className={`tw-flex-1 tw-truncate tw-grid tw-grid-flow-col`} style={{maxWidth: nameWidth+60}}> <div className={'tw-flex-1 tw-truncate tw-grid tw-grid-flow-col'} style={{ maxWidth: nameWidth + 60 }}>
<Link className="tw-btn tw-btn-ghost tw-px-2 tw-normal-case tw-text-xl tw-flex-1 tw-truncate" to={"/"}><h1 ref={nameRef} className="tw-truncate">{appName}</h1></Link> <Link className="tw-btn tw-btn-ghost tw-px-2 tw-normal-case tw-text-xl tw-flex-1 tw-truncate" to={'/'}><h1 ref={nameRef} className="tw-truncate">{appName}</h1></Link>
<button className="tw-btn tw-px-2 tw-btn-ghost" onClick={() => window.my_modal_3.showModal()}><QuestionMarkIcon className="tw-h-5 tw-w-5" /></button> <button className="tw-btn tw-px-2 tw-btn-ghost" onClick={() => window.my_modal_3.showModal()}><QuestionMarkIcon className="tw-h-5 tw-w-5" /></button>
</div> </div>
</div> </div>
{isAuthenticated
? <div className="tw-flex-none">
{isAuthenticated ? <Link to={`${userProfile.id && '/item/' + userProfile.id}`} className="tw-flex tw-items-center">
<div className="tw-flex-none">
<Link to={`${userProfile.id && "/item/"+userProfile.id}`} className="tw-flex tw-items-center">
{ userProfile?.image && <div className="tw-avatar"> { userProfile?.image && <div className="tw-avatar">
<div className="tw-w-10 tw-rounded-full"> <div className="tw-w-10 tw-rounded-full">
<img src={"https://api.utopia-lab.org/assets/" + userProfile.image} /> <img src={'https://api.utopia-lab.org/assets/' + userProfile.image} />
</div> </div>
</div>} </div>}
<div className='tw-ml-2 tw-mr-2'>{userProfile.name || user?.first_name}</div> <div className='tw-ml-2 tw-mr-2'>{userProfile.name || user?.first_name}</div>
@ -102,21 +92,20 @@ export default function NavBar({ appName, userType}: { appName: string, userType
</svg> </svg>
</label> </label>
<ul tabIndex={0} className="tw-menu tw-menu-compact tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[10000]"> <ul tabIndex={0} className="tw-menu tw-menu-compact tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[10000]">
<li><Link to={`${userProfile.id && "/edit-item/"+userProfile.id}`}>Profile</Link></li> <li><Link to={`${userProfile.id && '/edit-item/' + userProfile.id}`}>Profile</Link></li>
<li><Link to={"/user-settings"}>Settings</Link></li> <li><Link to={'/user-settings'}>Settings</Link></li>
<li><a onClick={() => { onLogout() }}>Logout</a></li> <li><a onClick={() => { onLogout() }}>Logout</a></li>
</ul> </ul>
</div> </div>
</div> </div>
: : <div>
<div>
<div className="tw-hidden md:tw-flex"> <div className="tw-hidden md:tw-flex">
<Link to={"/login"}><div className="tw-btn tw-btn-ghost tw-mr-2"> <Link to={'/login'}><div className="tw-btn tw-btn-ghost tw-mr-2">
Login Login
</div></Link> </div></Link>
<Link to={"/signup"}><div className="tw-btn tw-btn-ghost tw-mr-2"> <Link to={'/signup'}><div className="tw-btn tw-btn-ghost tw-mr-2">
Sign Up Sign Up
</div></Link> </div></Link>
</div> </div>
@ -126,11 +115,10 @@ export default function NavBar({ appName, userType}: { appName: string, userType
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" /> <path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
</svg> </svg>
</label> </label>
<ul tabIndex={1} className="tw-menu tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[10000]"> <ul tabIndex={1} className="tw-menu tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[10000]">
<li><Link to={"/login"}>Login</Link></li> <li><Link to={'/login'}>Login</Link></li>
<li><Link to={"/signup"}>Sign Up</Link></li> <li><Link to={'/signup'}>Sign Up</Link></li>
</ul> </ul>
</div> </div>
@ -140,5 +128,5 @@ export default function NavBar({ appName, userType}: { appName: string, userType
</> </>
) )
else return (<></>) } else return (<></>)
} }

View File

@ -1,16 +1,15 @@
import { useSetAssetApi } from './hooks/useAssets' import { useSetAssetApi } from './hooks/useAssets'
import { AssetsApi } from '../../types'; import { AssetsApi } from '../../types'
import { useEffect } from 'react'; import { useEffect } from 'react'
export const SetAssetsApi = ({ assetsApi }:{assetsApi: AssetsApi}) => { export const SetAssetsApi = ({ assetsApi }:{assetsApi: AssetsApi}) => {
const setAssetsApi = useSetAssetApi(); const setAssetsApi = useSetAssetApi()
useEffect(() => { useEffect(() => {
setAssetsApi(assetsApi) setAssetsApi(assetsApi)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [assetsApi]) }, [assetsApi])
return ( return (
<></> <></>
) )

View File

@ -1,13 +1,12 @@
import { useRef, useState } from 'react' import { useRef, useState, useEffect } from 'react'
import { useEffect } from 'react' import { NavLink, useLocation } from 'react-router-dom'
import { NavLink, useLocation } from 'react-router-dom';
import { import {
Sidenav, Sidenav,
initTE, initTE
} from "tw-elements"; } from 'tw-elements'
import SidebarSubmenu from './SidebarSubmenu'; import SidebarSubmenu from './SidebarSubmenu'
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon'; import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon'
import * as React from 'react'; import * as React from 'react'
type route = { type route = {
path: string; path: string;
@ -18,48 +17,41 @@ type route = {
blank?: boolean blank?: boolean
} }
export function SideBar ({ routes, bottomRoutes }: { routes: route[], bottomRoutes?: route[] }) { export function SideBar ({ routes, bottomRoutes }: { routes: route[], bottomRoutes?: route[] }) {
// prevent react18 from calling useEffect twice // prevent react18 from calling useEffect twice
const init = useRef(false) const init = useRef(false)
const location = useLocation(); const location = useLocation()
const [instance, setInstance] = useState<any>(null);
const [slim, setSlim] = useState<boolean>(false);
const [instance, setInstance] = useState<any>(null)
const [slim, setSlim] = useState<boolean>(false)
const toggleSlim = () => { const toggleSlim = () => {
setSlim(!slim); setSlim(!slim)
instance.toggleSlim(); instance.toggleSlim()
} }
useEffect(() => { useEffect(() => {
if (!init.current) { if (!init.current) {
initTE({ Sidenav }); initTE({ Sidenav })
const instance = Sidenav.getInstance( const instance = Sidenav.getInstance(
document.getElementById("sidenav") document.getElementById('sidenav')
); )
setInstance(instance); setInstance(instance)
instance.toggleSlim(); instance.toggleSlim()
init.current = true; init.current = true
} }
}, []) }, [])
const [embedded, setEmbedded] = useState<boolean>(true) const [embedded, setEmbedded] = useState<boolean>(true)
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const embedded = params.get("embedded"); const embedded = params.get('embedded')
embedded != "true" && setEmbedded(false) embedded !== 'true' && setEmbedded(false)
}, [location]); }, [location])
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
return ( return (
<nav <nav
@ -73,26 +65,28 @@ export function SideBar({ routes, bottomRoutes }: { routes: route[], bottomRoute
data-te-sidenav-slim-collapsed="true" data-te-sidenav-slim-collapsed="true"
data-te-sidenav-slim-width="56" data-te-sidenav-slim-width="56"
data-te-sidenav-width="180"> data-te-sidenav-width="180">
<div className={`tw-flex tw-flex-col ${embedded ? "tw-h-full" :"tw-h-[calc(100dvh-64px)]"}`}> <div className={`tw-flex tw-flex-col ${embedded ? 'tw-h-full' : 'tw-h-[calc(100dvh-64px)]'}`}>
<ul className="tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0" data-te-sidenav-menu-ref> <ul className="tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0" data-te-sidenav-menu-ref>
{ {
routes.map((route, k) => { routes.map((route, k) => {
return ( return (
<li className="" key={k}> <li className="" key={k}>
{ {
route.submenu ? route.submenu
<SidebarSubmenu {...route} /> : ? <SidebarSubmenu {...route} />
(<NavLink : (<NavLink
end end
target={route.blank ? "_blank" : "_self"} target={route.blank ? '_blank' : '_self'}
to={`${route.path}${params && '?' + params}`} to={`${route.path}${params && '?' + params}`}
className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => { className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => {
if (screen.width < 640 && !slim) instance.toggle(); if (screen.width < 640 && !slim) instance.toggle()
}}> }}>
{route.icon}<span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden" data-te-sidenav-slim="false">{route.name}</span> {route.icon}<span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden" data-te-sidenav-slim="false">{route.name}</span>
{ {
location.pathname.includes(route.path) && route.path.length > 1 || location.pathname === route.path ? (<span className="tw-absolute tw-inset-y-0 tw-left-0 tw-w-1 tw-rounded-tr-md tw-rounded-br-md tw-bg-primary " (location.pathname.includes(route.path) && route.path.length > 1) || location.pathname === route.path
aria-hidden="true"></span>) : null ? (<span className="tw-absolute tw-inset-y-0 tw-left-0 tw-w-1 tw-rounded-tr-md tw-rounded-br-md tw-bg-primary "
aria-hidden="true"></span>)
: null
} }
</NavLink>) </NavLink>)
} }
@ -112,19 +106,21 @@ export function SideBar({ routes, bottomRoutes }: { routes: route[], bottomRoute
return ( return (
<li className="" key={k}> <li className="" key={k}>
{ {
route.submenu ? route.submenu
<SidebarSubmenu {...route} /> : ? <SidebarSubmenu {...route} />
(<NavLink : (<NavLink
end end
target={route.blank ? "_blank" : "_self"} target={route.blank ? '_blank' : '_self'}
to={route.path} to={route.path}
className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => { className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => {
if (screen.width < 640 && !slim) instance.toggle(); if (screen.width < 640 && !slim) instance.toggle()
}}> }}>
{route.icon}<span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden" data-te-sidenav-slim="false">{route.name}</span> {route.icon}<span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden" data-te-sidenav-slim="false">{route.name}</span>
{ {
location.pathname.includes(route.path) && route.path.length > 1 || location.pathname === route.path ? (<span className="tw-absolute tw-inset-y-0 tw-left-0 tw-w-1 tw-rounded-tr-md tw-rounded-br-md tw-bg-primary " (location.pathname.includes(route.path) && route.path.length > 1) || location.pathname === route.path
aria-hidden="true"></span>) : null ? (<span className="tw-absolute tw-inset-y-0 tw-left-0 tw-w-1 tw-rounded-tr-md tw-rounded-br-md tw-bg-primary "
aria-hidden="true"></span>)
: null
} }
</NavLink>) </NavLink>)
} }
@ -135,7 +131,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: route[], bottomRoute
} }
</ul> </ul>
<ChevronRightIcon className={"tw-w-5 tw-h-5 tw-mb-4 tw-mr-4 tw-cursor-pointer tw-float-right tw-delay-400 tw-duration-500 tw-transition-all " + (!slim ? "tw-rotate-180" : '')} onClick={() => toggleSlim()} /> <ChevronRightIcon className={'tw-w-5 tw-h-5 tw-mb-4 tw-mr-4 tw-cursor-pointer tw-float-right tw-delay-400 tw-duration-500 tw-transition-all ' + (!slim ? 'tw-rotate-180' : '')} onClick={() => toggleSlim()} />
</div> </div>
</div> </div>

View File

@ -2,7 +2,6 @@ import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
function SidebarSubmenu ({ submenu, name, icon } : { path: string; function SidebarSubmenu ({ submenu, name, icon } : { path: string;
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
icon: JSX.Element; icon: JSX.Element;
@ -11,7 +10,6 @@ function SidebarSubmenu({submenu, name, icon} : { path: string;
const location = useLocation() const location = useLocation()
const [isExpanded, setIsExpanded] = useState(false) const [isExpanded, setIsExpanded] = useState(false)
/** Open Submenu list if path found in routes, this is for directly loading submenu routes first time */ /** Open Submenu list if path found in routes, this is for directly loading submenu routes first time */
useEffect(() => { useEffect(() => {
if (submenu.filter(m => { return m.path === location.pathname })[0])setIsExpanded(true) if (submenu.filter(m => { return m.path === location.pathname })[0])setIsExpanded(true)
@ -28,8 +26,8 @@ function SidebarSubmenu({submenu, name, icon} : { path: string;
</div> </div>
{/** Submenu list */} {/** Submenu list */}
<div className={` w-full data-[te-collapse-show]:!hidden `+ (isExpanded ? "" : "hidden")} > <div className={' w-full data-[te-collapse-show]:!hidden ' + (isExpanded ? '' : 'hidden')} >
<ul className={`menu menu-compact`} > <ul className={'menu menu-compact'} >
{ {
submenu.map((m, k) => { submenu.map((m, k) => {
return ( return (
@ -37,8 +35,10 @@ function SidebarSubmenu({submenu, name, icon} : { path: string;
<Link to={m.path} className='' > <Link to={m.path} className='' >
{m.icon}<span className="" data-te-sidenav-slim="false">{m.name}</span> {m.icon}<span className="" data-te-sidenav-slim="false">{m.name}</span>
{ {
location.pathname == m.path ? (<span className="absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary " location.pathname === m.path
aria-hidden="true"></span>) : null ? (<span className="absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary "
aria-hidden="true"></span>)
: null
} }
</Link> </Link>
</li> </li>

View File

@ -1,37 +1,36 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react'
import { useItems } from '../Map/hooks/useItems'; import { useItems } from '../Map/hooks/useItems'
export const Sitemap = ({ url }:{url:string}) => { export const Sitemap = ({ url }:{url:string}) => {
const [sitemap, setSitemap] = useState(''); const [sitemap, setSitemap] = useState('')
const items = useItems(); const items = useItems()
useEffect(() => { useEffect(() => {
if (items.length) { if (items.length) {
const generateSitemap = () => { const generateSitemap = () => {
let sitemapXML = `<?xml version="1.0" encoding="UTF-8"?>\n`; let sitemapXML = '<?xml version="1.0" encoding="UTF-8"?>\n'
sitemapXML += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`; sitemapXML += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
items.forEach(item => { items.forEach(item => {
sitemapXML += ` <url>\n`; sitemapXML += ' <url>\n'
sitemapXML += ` <loc>${url}/${item.slug}</loc>\n`; sitemapXML += ` <loc>${url}/${item.slug}</loc>\n`
sitemapXML += ` </url>\n`; sitemapXML += ' </url>\n'
}); })
sitemapXML += `</urlset>`; sitemapXML += '</urlset>'
return sitemapXML; return sitemapXML
}; }
setSitemap(generateSitemap()); setSitemap(generateSitemap())
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [items]); }, [items])
return ( return (
<div> <div>
<h1>Sitemap</h1> <h1>Sitemap</h1>
<textarea value={sitemap} readOnly rows={items.length + 4} cols={80} /> <textarea value={sitemap} readOnly rows={items.length + 4} cols={80} />
</div> </div>
); )
}; }

View File

@ -1,31 +1,27 @@
import { useCallback, useState } from 'react'; import { useCallback, useState, createContext, useContext } from 'react'
import { createContext, useContext } from "react";
import * as React from "react";
import { AssetsApi } from '../../../types';
import * as React from 'react'
import { AssetsApi } from '../../../types'
type UseAssetManagerResult = ReturnType<typeof useAssetsManager>; type UseAssetManagerResult = ReturnType<typeof useAssetsManager>;
const AssetContext = createContext<UseAssetManagerResult>({ const AssetContext = createContext<UseAssetManagerResult>({
api: {} as AssetsApi, api: {} as AssetsApi,
setAssetsApi: () => { } setAssetsApi: () => { }
}); })
function useAssetsManager (): { function useAssetsManager (): {
api: AssetsApi; api: AssetsApi;
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
setAssetsApi: (api: AssetsApi) => void; setAssetsApi: (api: AssetsApi) => void;
} { } {
const [api, setApi] = useState<AssetsApi>({} as AssetsApi); const [api, setApi] = useState<AssetsApi>({} as AssetsApi)
const setAssetsApi = useCallback((api: AssetsApi) => { const setAssetsApi = useCallback((api: AssetsApi) => {
setApi(api); setApi(api)
}, []); }, [])
return { api, setAssetsApi }; return { api, setAssetsApi }
} }
export const AssetsProvider: React.FunctionComponent<{ export const AssetsProvider: React.FunctionComponent<{
@ -34,15 +30,14 @@ export const AssetsProvider: React.FunctionComponent<{
<AssetContext.Provider value={useAssetsManager()}> <AssetContext.Provider value={useAssetsManager()}>
{children} {children}
</AssetContext.Provider> </AssetContext.Provider>
); )
export const useAssetApi = (): AssetsApi => { export const useAssetApi = (): AssetsApi => {
const { api } = useContext(AssetContext); const { api } = useContext(AssetContext)
return api; return api
}; }
export const useSetAssetApi = (): UseAssetManagerResult['setAssetsApi'] => {
export const useSetAssetApi = (): UseAssetManagerResult["setAssetsApi"] => { const { setAssetsApi } = useContext(AssetContext)
const { setAssetsApi } = useContext(AssetContext); return setAssetsApi
return setAssetsApi;
} }

View File

@ -1,4 +1,4 @@
export {AppShell} from "./AppShell" export { AppShell } from './AppShell'
export {SideBar} from "./SideBar" export { SideBar } from './SideBar'
export {Content} from "./Content" export { Content } from './Content'
export {Sitemap} from "./Sitemap" export { Sitemap } from './Sitemap'

View File

@ -5,49 +5,48 @@ import { useAuth } from './useAuth'
import { MapOverlayPage } from '../Templates' import { MapOverlayPage } from '../Templates'
export function LoginPage () { export function LoginPage () {
const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>('')
const [email, setEmail] = useState<string>(""); const { login, loading } = useAuth()
const [password, setPassword] = useState<string>("");
const { login, loading } = useAuth(); const navigate = useNavigate()
const navigate = useNavigate();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const onLogin = async () => { const onLogin = async () => {
await toast.promise( await toast.promise(
login({ email: email, password: password }), login({ email, password }),
{ {
success: { success: {
render ({ data }) { render ({ data }) {
navigate(`/`); navigate('/')
return `Hi ${data?.first_name}` return `Hi ${data?.first_name}`
}, },
// other options // other options
icon: "✌️", icon: '✌️'
}, },
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, },
autoClose: 10000, autoClose: 10000
}, },
pending: 'logging in ...', pending: 'logging in ...'
}); })
} }
useEffect(() => { useEffect(() => {
const keyDownHandler = event => { const keyDownHandler = event => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault()
onLogin(); onLogin()
} }
}; }
document.addEventListener('keydown', keyDownHandler); document.addEventListener('keydown', keyDownHandler)
return () => { return () => {
document.removeEventListener('keydown', keyDownHandler); document.removeEventListener('keydown', keyDownHandler)
}; }
}, [onLogin]); }, [onLogin])
return ( return (
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'> <MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
@ -62,4 +61,3 @@ export function LoginPage() {
</MapOverlayPage> </MapOverlayPage>
) )
} }

View File

@ -5,33 +5,32 @@ import { useAuth } from './useAuth'
import { MapOverlayPage } from '../Templates' import { MapOverlayPage } from '../Templates'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export function RequestPasswordPage({reset_url}) { export function RequestPasswordPage ({ resetUrl }) {
const [email, setEmail] = useState<string>('')
const [email, setEmail] = useState<string>(""); const { requestPasswordReset, loading } = useAuth()
const { requestPasswordReset, loading } = useAuth(); const navigate = useNavigate()
const navigate = useNavigate();
const onReset = async () => { const onReset = async () => {
await toast.promise( await toast.promise(
requestPasswordReset( email, reset_url), requestPasswordReset(email, resetUrl),
{ {
success: { success: {
render () { render () {
navigate(`/`); navigate('/')
return `Check your mailbox` return 'Check your mailbox'
}, },
// other options // other options
icon: "📬", icon: '📬'
}, },
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, }
}, },
pending: 'sending email ...' pending: 'sending email ...'
}); })
} }
return ( return (
@ -44,4 +43,3 @@ export function RequestPasswordPage({reset_url}) {
</MapOverlayPage> </MapOverlayPage>
) )
} }

View File

@ -6,33 +6,32 @@ import { useAuth } from './useAuth'
import { MapOverlayPage } from '../Templates' import { MapOverlayPage } from '../Templates'
export function SetNewPasswordPage () { export function SetNewPasswordPage () {
const [password, setPassword] = useState<string>('')
const [password, setPassword] = useState<string>(""); const { passwordReset, loading } = useAuth()
const { passwordReset, loading } = useAuth(); const navigate = useNavigate()
const navigate = useNavigate();
const onReset = async () => { const onReset = async () => {
const token = window.location.search.split("token=")[1]; const token = window.location.search.split('token=')[1]
console.log(token); console.log(token)
await toast.promise( await toast.promise(
passwordReset(token, password), passwordReset(token, password),
{ {
success: { success: {
render () { render () {
navigate(`/`); navigate('/')
return `New password set` return 'New password set'
}, }
}, },
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, }
}, },
pending: 'setting password ...' pending: 'setting password ...'
}); })
} }
return ( return (
@ -45,4 +44,3 @@ export function SetNewPasswordPage() {
</MapOverlayPage> </MapOverlayPage>
) )
} }

View File

@ -6,52 +6,50 @@ import { useAuth } from './useAuth'
import { MapOverlayPage } from '../Templates' import { MapOverlayPage } from '../Templates'
export function SignupPage () { export function SignupPage () {
const [email, setEmail] = useState<string>('')
const [userName, setUserName] = useState<string>('')
const [email, setEmail] = useState<string>(""); const [password, setPassword] = useState<string>('')
const [userName, setUserName] = useState<string>("");
const [password, setPassword] = useState<string>(""); const { register, loading } = useAuth()
const { register, loading } = useAuth(); const navigate = useNavigate()
const navigate = useNavigate();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const onRegister = async () => { const onRegister = async () => {
await toast.promise( await toast.promise(
register({ email: email, password: password }, userName), register({ email, password }, userName),
{ {
success: { success: {
render ({ data }) { render ({ data }) {
navigate(`/`); navigate('/')
return `Hi ${data?.first_name}` return `Hi ${data?.first_name}`
}, },
// other options // other options
icon: "✌️", icon: '✌️'
}, },
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, },
autoClose: 10000, autoClose: 10000
}, },
pending: 'creating new user ...' pending: 'creating new user ...'
}); })
} }
useEffect(() => { useEffect(() => {
const keyDownHandler = event => { const keyDownHandler = event => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault()
onRegister(); onRegister()
} }
}; }
document.addEventListener('keydown', keyDownHandler); document.addEventListener('keydown', keyDownHandler)
return () => { return () => {
document.removeEventListener('keydown', keyDownHandler); document.removeEventListener('keydown', keyDownHandler)
}; }
}, [onRegister]); }, [onRegister])
return ( return (
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'> <MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
@ -65,4 +63,3 @@ export function SignupPage() {
</MapOverlayPage> </MapOverlayPage>
) )
} }

View File

@ -1,5 +1,5 @@
export {AuthProvider, useAuth} from "./useAuth" export { AuthProvider, useAuth } from './useAuth'
export {LoginPage} from "./LoginPage" export { LoginPage } from './LoginPage'
export { SignupPage } from './SignupPage' export { SignupPage } from './SignupPage'
export { RequestPasswordPage } from './RequestPasswordPage' export { RequestPasswordPage } from './RequestPasswordPage'
export { SetNewPasswordPage } from './SetNewPasswordPage' export { SetNewPasswordPage } from './SetNewPasswordPage'

View File

@ -1,7 +1,6 @@
import { createContext, useState, useContext, useEffect } from "react"; import { createContext, useState, useContext, useEffect } from 'react'
import * as React from "react"; import * as React from 'react'
import { UserApi, UserItem } from "../../types"; import { UserApi, UserItem } from '../../types'
type AuthProviderProps = { type AuthProviderProps = {
userApi: UserApi, userApi: UserApi,
@ -14,8 +13,6 @@ type AuthCredentials = {
otp?: string | undefined; otp?: string | undefined;
} }
type AuthContextProps = { type AuthContextProps = {
isAuthenticated: boolean, isAuthenticated: boolean,
user: UserItem | null; user: UserItem | null;
@ -37,130 +34,123 @@ type AuthContextProps = {
const AuthContext = createContext<AuthContextProps>({ const AuthContext = createContext<AuthContextProps>({
isAuthenticated: false, isAuthenticated: false,
user: null, user: null,
login: () => Promise.reject(), login: () => Promise.reject(Error('Unimplemented')),
register: () => Promise.reject(), register: () => Promise.reject(Error('Unimplemented')),
loading: false, loading: false,
logout: () => Promise.reject(), logout: () => Promise.reject(Error('Unimplemented')),
updateUser: () => Promise.reject(), updateUser: () => Promise.reject(Error('Unimplemented')),
token: "", token: '',
requestPasswordReset: () => Promise.reject(), requestPasswordReset: () => Promise.reject(Error('Unimplemented')),
passwordReset: () => Promise.reject() passwordReset: () => Promise.reject(Error('Unimplemented'))
}); })
export const AuthProvider = ({ userApi, children }: AuthProviderProps) => { export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
const [user, setUser] = useState<UserItem | null>(null); const [user, setUser] = useState<UserItem | null>(null)
const [token, setToken] = useState<string | null>(null); const [token, setToken] = useState<string | null>(null)
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false)
const isAuthenticated = !!user; const isAuthenticated = !!user
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true)
loadUser(); loadUser()
setLoading(false) setLoading(false)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [])
async function loadUser (): Promise<UserItem | undefined> { async function loadUser (): Promise<UserItem | undefined> {
try { try {
const token = await userApi.getToken(); const token = await userApi.getToken()
setToken(token); setToken(token)
if (token) { if (token) {
const me = await userApi.getUser(); const me = await userApi.getUser()
setUser(me as UserItem); setUser(me as UserItem)
setLoading(false); setLoading(false)
return me as UserItem; return me as UserItem
} } else return undefined
else return undefined;
} catch (error) { } catch (error) {
setLoading(false) setLoading(false)
return undefined; return undefined
} }
} }
const login = async (credentials: AuthCredentials): Promise<UserItem | undefined> => { const login = async (credentials: AuthCredentials): Promise<UserItem | undefined> => {
setLoading(true); setLoading(true)
try { try {
const res = await userApi.login(credentials.email, credentials.password); const res = await userApi.login(credentials.email, credentials.password)
setToken(res.access_token); setToken(res.access_token)
return (await loadUser()); return (await loadUser())
} catch (error: any) { } catch (error: any) {
setLoading(false); setLoading(false)
throw error; throw error
} }
} }
const register = async (credentials: AuthCredentials, userName): Promise<UserItem | undefined> => { const register = async (credentials: AuthCredentials, userName): Promise<UserItem | undefined> => {
setLoading(true); setLoading(true)
try { try {
/* const res = */ await userApi.register(credentials.email, credentials.password, userName) /* const res = */ await userApi.register(credentials.email, credentials.password, userName)
return (await login(credentials)); return (await login(credentials))
} catch (error: any) { } catch (error: any) {
setLoading(false); setLoading(false)
throw error; throw error
} }
} }
const logout = async () => { const logout = async () => {
try { try {
await userApi.logout(); await userApi.logout()
setUser(null); setUser(null)
} catch (error: any) { } catch (error: any) {
setLoading(false); setLoading(false)
throw error; throw error
} }
} }
const updateUser = async (user: UserItem) => { const updateUser = async (user: UserItem) => {
setLoading(true); setLoading(true)
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { id, ...userRest } = user; const { id, ...userRest } = user
try { try {
const res = await userApi.updateUser(userRest); const res = await userApi.updateUser(userRest)
setUser(res as any); setUser(res as any)
loadUser(); loadUser()
setLoading(false); setLoading(false)
return res as any; return res as any
} catch (error: any) { } catch (error: any) {
setLoading(false); setLoading(false)
throw error; throw error
} }
} }
const requestPasswordReset = async (email: string, reset_url?: string): Promise<any> => { const requestPasswordReset = async (email: string, resetUrl?: string): Promise<any> => {
setLoading(true); setLoading(true)
try { try {
await userApi.requestPasswordReset(email, reset_url); await userApi.requestPasswordReset(email, resetUrl)
return setLoading(false); return setLoading(false)
} catch (error: any) { } catch (error: any) {
setLoading(false); setLoading(false)
throw error; throw error
} }
} }
const passwordReset = async (token: string, newPassword:string): Promise<any> => {
const passwordReset = async (token: string, new_password:string): Promise<any> => { setLoading(true)
setLoading(true);
try { try {
await userApi.passwordReset(token, new_password); await userApi.passwordReset(token, newPassword)
return setLoading(false); return setLoading(false)
} catch (error: any) { } catch (error: any) {
setLoading(false); setLoading(false)
throw error; throw error
} }
} }
return ( return (
<AuthContext.Provider <AuthContext.Provider
value={{ isAuthenticated, user, login, register, loading, logout, updateUser, token, requestPasswordReset, passwordReset }} value={{ isAuthenticated, user, login, register, loading, logout, updateUser, token, requestPasswordReset, passwordReset }}
> >
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); )
}; }
export const useAuth = () => useContext(AuthContext); export const useAuth = () => useContext(AuthContext)

View File

@ -1,15 +1,12 @@
import * as React from "react" import * as React from 'react'
import { useEffect } from "react" import { useEffect } from 'react'
export function Modal ({ children, showOnStartup }:{children : React.ReactNode, showOnStartup?: boolean}) { export function Modal ({ children, showOnStartup }:{children : React.ReactNode, showOnStartup?: boolean}) {
useEffect(() => { useEffect(() => {
if(showOnStartup) if (showOnStartup) { window.my_modal_3.showModal() }
window.my_modal_3.showModal()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
return ( return (
<> <>
@ -26,4 +23,3 @@ export function Modal({children, showOnStartup}:{children : React.ReactNode, sho
</> </>
) )
} }

View File

@ -1,32 +1,30 @@
import { useQuestsOpen, useSetQuestOpen } from './hooks/useQuests'; import { useQuestsOpen, useSetQuestOpen } from './hooks/useQuests'
import { useAuth } from '../Auth'; import { useAuth } from '../Auth'
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react'
import { useItems } from '../Map/hooks/useItems'; import { useItems } from '../Map/hooks/useItems'
import { Item } from '../../types'; import { Item } from '../../types'
export function Quests () { export function Quests () {
const questsOpen = useQuestsOpen()
const questsOpen = useQuestsOpen(); const setQuestsOpen = useSetQuestOpen()
const setQuestsOpen = useSetQuestOpen(); const { isAuthenticated, user } = useAuth()
const { isAuthenticated, user } = useAuth();
useEffect(() => { useEffect(() => {
setQuestsOpen(false); setQuestsOpen(false)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const [profile, setProfie] = useState<Item>() const [profile, setProfie] = useState<Item>()
const items = useItems(); const items = useItems()
useEffect(() => { useEffect(() => {
setProfie(items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name == "user" && i.user_created?.id != null)) setProfie(items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name === 'user' && i.user_created?.id != null))
}, [items, user]) }, [items, user])
return ( return (
<>{questsOpen ? <>{questsOpen
<div className="tw-card tw-w-48 tw-bg-base-100 tw-shadow-xl tw-absolute tw-bottom-4 tw-left-4 tw-z-[2000]"> ? <div className="tw-card tw-w-48 tw-bg-base-100 tw-shadow-xl tw-absolute tw-bottom-4 tw-left-4 tw-z-[2000]">
<div className="tw-card-body tw-p-4 tw-pt-0"> <div className="tw-card-body tw-p-4 tw-pt-0">
<div className="tw-card-actions tw-justify-end"> <div className="tw-card-actions tw-justify-end">
<label className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-1 tw-top-1" onClick={() => setQuestsOpen(false)}></label> <label className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-1 tw-top-1" onClick={() => setQuestsOpen(false)}></label>
@ -39,17 +37,15 @@ export function Quests() {
</h2> </h2>
<ul className='tw-flex-row'> <ul className='tw-flex-row'>
<li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={isAuthenticated ? isAuthenticated : false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Sign Up</span></label></li> <li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={isAuthenticated || false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Sign Up</span></label></li>
<li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={profile?.text ? true : false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Fill Profile</span></label></li> <li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={!!profile?.text} /><span className='tw-text-sm tw-label-text tw-mx-2'>Fill Profile</span></label></li>
<li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={profile?.image ? true : false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Upload Avatar</span></label></li> <li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={!!profile?.image} /><span className='tw-text-sm tw-label-text tw-mx-2'>Upload Avatar</span></label></li>
</ul> </ul>
{ /** <button className='tw-btn tw-btn-xs tw-btn-neutral tw-w-fit tw-self-center tw-mt-1'>Next &gt;</button> */ { /** <button className='tw-btn tw-btn-xs tw-btn-neutral tw-w-fit tw-self-center tw-mt-1'>Next &gt;</button> */
} </div> } </div>
</div> </div>
: "" : ''
} }
</> </>
) )
} }

View File

@ -1,28 +1,25 @@
import { useCallback, useState } from 'react'; import { useCallback, useState, createContext, useContext } from 'react'
import { createContext, useContext } from "react"; import * as React from 'react'
import * as React from "react";
type UseQuestManagerResult = ReturnType<typeof useQuestsManager>; type UseQuestManagerResult = ReturnType<typeof useQuestsManager>;
const QuestContext = createContext<UseQuestManagerResult>({ const QuestContext = createContext<UseQuestManagerResult>({
open: false, open: false,
setQuestsOpen: () => { } setQuestsOpen: () => { }
}); })
function useQuestsManager (initialOpen: boolean): { function useQuestsManager (initialOpen: boolean): {
open: boolean; open: boolean;
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
setQuestsOpen: (open: boolean) => void; setQuestsOpen: (open: boolean) => void;
} { } {
const [open, setOpen] = useState<boolean>(initialOpen); const [open, setOpen] = useState<boolean>(initialOpen)
const setQuestsOpen = useCallback((questOpen: boolean) => { const setQuestsOpen = useCallback((questOpen: boolean) => {
setOpen(questOpen); setOpen(questOpen)
}, []); }, [])
return { open, setQuestsOpen }; return { open, setQuestsOpen }
} }
export const QuestsProvider: React.FunctionComponent<{ export const QuestsProvider: React.FunctionComponent<{
@ -31,15 +28,14 @@ export const QuestsProvider: React.FunctionComponent<{
<QuestContext.Provider value={useQuestsManager(initialOpen)}> <QuestContext.Provider value={useQuestsManager(initialOpen)}>
{children} {children}
</QuestContext.Provider> </QuestContext.Provider>
); )
export const useQuestsOpen = (): boolean => { export const useQuestsOpen = (): boolean => {
const { open } = useContext(QuestContext); const { open } = useContext(QuestContext)
return open; return open
}; }
export const useSetQuestOpen = (): UseQuestManagerResult['setQuestsOpen'] => {
export const useSetQuestOpen = (): UseQuestManagerResult["setQuestsOpen"] => { const { setQuestsOpen } = useContext(QuestContext)
const { setQuestsOpen } = useContext(QuestContext); return setQuestsOpen
return setQuestsOpen;
} }

View File

@ -1,46 +1,44 @@
import * as React from 'react' import * as React from 'react'
import { useEffect } from 'react'; import { useEffect } from 'react'
import { TagView } from '../Templates/TagView'; import { TagView } from '../Templates/TagView'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFilteredSuggestions, setFocus }: { inputProps: any, suggestions: Array<any>, onSelected: (suggestion) => void, pushFilteredSuggestions?: Array<any>, setFocus?: boolean }) => { export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFilteredSuggestions, setFocus }: { inputProps: any, suggestions: Array<any>, onSelected: (suggestion) => void, pushFilteredSuggestions?: Array<any>, setFocus?: boolean }) => {
const [filteredSuggestions, setFilteredSuggestions] = React.useState<Array<any>>([])
const [filteredSuggestions, setFilteredSuggestions] = React.useState<Array<any>>([]); const [heighlightedSuggestion, setHeighlightedSuggestion] = React.useState<number>(0)
const [heighlightedSuggestion, setHeighlightedSuggestion] = React.useState<number>(0);
useEffect(() => { useEffect(() => {
pushFilteredSuggestions && setFilteredSuggestions(pushFilteredSuggestions) pushFilteredSuggestions && setFilteredSuggestions(pushFilteredSuggestions)
}, [pushFilteredSuggestions]) }, [pushFilteredSuggestions])
useEffect(() => { useEffect(() => {
setFocus && inputRef.current?.focus(); setFocus && inputRef.current?.focus()
}, [setFocus]) }, [setFocus])
const inputRef = React.useRef<HTMLInputElement>(); const inputRef = React.useRef<HTMLInputElement>()
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const getSuggestionValue = suggestion => suggestion.name; const getSuggestionValue = suggestion => suggestion.name
const getSuggestions = value => { const getSuggestions = value => {
const inputValue = value.trim().toLowerCase(); const inputValue = value.trim().toLowerCase()
const inputLength = inputValue.length; const inputLength = inputValue.length
return inputLength === 0 ? [] : suggestions.filter(tag => return inputLength === 0
? []
: suggestions.filter(tag =>
tag.name.toLowerCase().slice(0, inputLength) === inputValue tag.name.toLowerCase().slice(0, inputLength) === inputValue
); )
}; }
const handleChange = (e) => { const handleChange = (e) => {
setFilteredSuggestions(getSuggestions(e.target.value)) setFilteredSuggestions(getSuggestions(e.target.value))
// Call the parent's onChange handler, if it exists // Call the parent's onChange handler, if it exists
if (inputProps.onChange) { if (inputProps.onChange) {
inputProps.onChange(e); inputProps.onChange(e)
}
} }
};
function handleSuggestionClick (suggestion) { function handleSuggestionClick (suggestion) {
onSelected(suggestion) onSelected(suggestion)
@ -50,20 +48,20 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
switch (event.key) { switch (event.key) {
case 'ArrowDown': case 'ArrowDown':
heighlightedSuggestion < filteredSuggestions.length - 1 && setHeighlightedSuggestion(current => current + 1) heighlightedSuggestion < filteredSuggestions.length - 1 && setHeighlightedSuggestion(current => current + 1)
break; break
case 'ArrowUp': case 'ArrowUp':
heighlightedSuggestion > 0 && setHeighlightedSuggestion(current => current - 1) heighlightedSuggestion > 0 && setHeighlightedSuggestion(current => current - 1)
break; break
case 'Enter': case 'Enter':
if (filteredSuggestions.length > 0) { if (filteredSuggestions.length > 0) {
onSelected(filteredSuggestions[heighlightedSuggestion]); onSelected(filteredSuggestions[heighlightedSuggestion])
setHeighlightedSuggestion(0); setHeighlightedSuggestion(0)
} }
filteredSuggestions.length == 0 && inputProps.onKeyDown(event); filteredSuggestions.length === 0 && inputProps.onKeyDown(event)
break; break
default: default:
inputProps.onKeyDown(event); inputProps.onKeyDown(event)
break; break
} }
} }
@ -72,7 +70,7 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
<input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} tabIndex="-1" onKeyDown={handleKeyDown}/> <input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} tabIndex="-1" onKeyDown={handleKeyDown}/>
<ul className={`tw-absolute tw-z-[4000] ${filteredSuggestions.length > 0 && 'tw-bg-base-100 tw-rounded-xl tw-p-2'}`}> <ul className={`tw-absolute tw-z-[4000] ${filteredSuggestions.length > 0 && 'tw-bg-base-100 tw-rounded-xl tw-p-2'}`}>
{filteredSuggestions.map((suggestion, index) => ( {filteredSuggestions.map((suggestion, index) => (
<li key={index} onClick={() => handleSuggestionClick(suggestion)}><TagView heighlight={index == heighlightedSuggestion} tag={suggestion}></TagView></li> <li key={index} onClick={() => handleSuggestionClick(suggestion)}><TagView heighlight={index === heighlightedSuggestion} tag={suggestion}></TagView></li>
))} ))}
</ul> </ul>
</div> </div>

View File

@ -1,5 +1,5 @@
import { useState } from "react" import { useState } from 'react'
import * as React from "react" import * as React from 'react'
interface ComboBoxProps { interface ComboBoxProps {
id?: string; id?: string;
@ -10,15 +10,14 @@ interface ComboBoxProps {
} }
const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => { const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const [selectedValue, setSelectedValue] = useState(value); const [selectedValue, setSelectedValue] = useState(value)
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value; const value = e.target.value
setSelectedValue(value); setSelectedValue(value)
onValueChange(value); onValueChange(value)
}; }
return ( return (
<select <select
@ -27,10 +26,10 @@ const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) =>
onChange={handleChange} onChange={handleChange}
> >
{options.map((o) => {options.map((o) =>
<option value={o.value} key={o.value} selected={o.value == value}>{o.label}</option> <option value={o.value} key={o.value} selected={o.value === value}>{o.label}</option>
)} )}
</select> </select>
); )
} }
export default ComboBoxInput; export default ComboBoxInput

View File

@ -1,7 +1,6 @@
import { useState } from 'react' import { useState } from 'react'
import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon' import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'
import * as React from 'react'; import * as React from 'react'
type SelectBoxProps = { type SelectBoxProps = {
labelTitle?: string; labelTitle?: string;
@ -18,27 +17,24 @@ type SelectBoxProps = {
} }
export function SelectBox (props : SelectBoxProps) { export function SelectBox (props : SelectBoxProps) {
const { labelTitle, labelDescription, defaultValue, containerStyle, placeholder, labelStyle, options, updateFormValue } = props const { labelTitle, labelDescription, defaultValue, containerStyle, placeholder, labelStyle, options, updateFormValue } = props
const [value, setValue] = useState(defaultValue || "") const [value, setValue] = useState(defaultValue || '')
const updateValue = (newValue: string) => { const updateValue = (newValue: string) => {
updateFormValue(newValue) updateFormValue(newValue)
setValue(newValue) setValue(newValue)
} }
return ( return (
<div className={`tw-inline-block ${containerStyle}`}> <div className={`tw-inline-block ${containerStyle}`}>
{labelTitle? {labelTitle
<label className={`tw-label ${labelStyle}`}> ? <label className={`tw-label ${labelStyle}`}>
<div className="tw-label-text">{labelTitle} <div className="tw-label-text">{labelTitle}
{labelDescription && <div className="tw-tooltip tw-tooltip-right" data-tip={labelDescription}><InformationCircleIcon className='tw-w-4 tw-h-4'/></div>} {labelDescription && <div className="tw-tooltip tw-tooltip-right" data-tip={labelDescription}><InformationCircleIcon className='tw-w-4 tw-h-4'/></div>}
</div> </div>
</label> </label>
: ""} : ''}
<select className="tw-select tw-select-bordered tw-w-full" value={value} onChange={(e) => updateValue(e.target.value)}> <select className="tw-select tw-select-bordered tw-w-full" value={value} onChange={(e) => updateValue(e.target.value)}>
<option disabled value="PLACEHOLDER">{placeholder}</option> <option disabled value="PLACEHOLDER">{placeholder}</option>
{ {

View File

@ -1,7 +1,7 @@
import * as React from "react"; import * as React from 'react'
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from 'react'
import Tribute from "tributejs"; import Tribute from 'tributejs'
import { useTags } from "../Map/hooks/useTags"; import { useTags } from '../Map/hooks/useTags'
type TextAreaProps = { type TextAreaProps = {
labelTitle?: string; labelTitle?: string;
@ -20,72 +20,74 @@ interface KeyValue {
} }
export function TextAreaInput ({ labelTitle, dataField, labelStyle, containerStyle, inputStyle, defaultValue, placeholder, updateFormValue }: TextAreaProps) { export function TextAreaInput ({ labelTitle, dataField, labelStyle, containerStyle, inputStyle, defaultValue, placeholder, updateFormValue }: TextAreaProps) {
const ref = useRef<HTMLTextAreaElement>(null); const ref = useRef<HTMLTextAreaElement>(null)
const [inputValue, setInputValue] = useState<string>(defaultValue); const [inputValue, setInputValue] = useState<string>(defaultValue)
// prevent react18 from calling useEffect twice // prevent react18 from calling useEffect twice
const init = useRef(false); const init = useRef(false)
const tags = useTags(); const tags = useTags()
const values: KeyValue[] = []; const values: KeyValue[] = []
tags.forEach(tag => { tags.forEach(tag => {
values.push({ key: tag.name, value: tag.name, color: tag.color }); values.push({ key: tag.name, value: tag.name, color: tag.color })
}); })
const tribute = new Tribute({ const tribute = new Tribute({
containerClass: 'tw-z-3000 tw-bg-base-100 tw-p-2 tw-rounded-lg tw-shadow', containerClass: 'tw-z-3000 tw-bg-base-100 tw-p-2 tw-rounded-lg tw-shadow',
selectClass: 'tw-font-bold', selectClass: 'tw-font-bold',
trigger: "#", trigger: '#',
values: values, values,
menuShowMinLength: 3, menuShowMinLength: 3,
noMatchTemplate: () => { noMatchTemplate: () => {
return "" return ''
}, },
menuItemTemplate: function (item) { menuItemTemplate: function (item) {
return `<span style="color: ${item.original.color}; padding: 5px; border-radius: 3px;">#${item.string}</span>`; return `<span style="color: ${item.original.color}; padding: 5px; border-radius: 3px;">#${item.string}</span>`
} }
}); })
useEffect(() => { useEffect(() => {
if (!init.current) { if (!init.current) {
if (ref.current) { if (ref.current) {
tribute.attach(ref.current); tribute.attach(ref.current)
} }
init.current = true; init.current = true
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref]); }, [ref])
useEffect(() => { useEffect(() => {
setInputValue(defaultValue); setInputValue(defaultValue)
}, [defaultValue]); }, [defaultValue])
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newValue = e.target.value; const newValue = e.target.value
setInputValue(newValue); setInputValue(newValue)
if (updateFormValue) { if (updateFormValue) {
updateFormValue(newValue); updateFormValue(newValue)
}
} }
};
return ( return (
<div className={`tw-form-control tw-w-full ${containerStyle ? containerStyle : ""}`}> <div className={`tw-form-control tw-w-full ${containerStyle || ''}`}>
{labelTitle ? ( {labelTitle
? (
<label className="tw-label"> <label className="tw-label">
<span className={`tw-label-text tw-text-base-content ${labelStyle}`}>{labelTitle}</span> <span className={`tw-label-text tw-text-base-content ${labelStyle}`}>{labelTitle}</span>
</label> </label>
) : null} )
: null}
<textarea <textarea
required required
ref={ref} ref={ref}
value={inputValue} value={inputValue}
name={dataField} name={dataField}
className={`tw-textarea tw-textarea-bordered tw-w-full tw-leading-5 ${inputStyle || ""}`} className={`tw-textarea tw-textarea-bordered tw-w-full tw-leading-5 ${inputStyle || ''}`}
placeholder={placeholder || ""} placeholder={placeholder || ''}
onChange={handleChange} onChange={handleChange}
></textarea> ></textarea>
</div> </div>
); )
} }

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react" import { useEffect, useState } from 'react'
import * as React from "react" import * as React from 'react'
type InputTextProps = { type InputTextProps = {
labelTitle?: string; labelTitle?: string;
@ -15,39 +15,40 @@ type InputTextProps = {
updateFormValue?: (value: string) => void; updateFormValue?: (value: string) => void;
} }
export function TextInput ({ labelTitle, labelStyle, type, dataField, containerStyle, inputStyle, defaultValue, placeholder, autocomplete, updateFormValue }: InputTextProps) { export function TextInput ({ labelTitle, labelStyle, type, dataField, containerStyle, inputStyle, defaultValue, placeholder, autocomplete, updateFormValue }: InputTextProps) {
const [inputValue, setInputValue] = useState<string>(defaultValue || ""); const [inputValue, setInputValue] = useState<string>(defaultValue || '')
useEffect(() => { useEffect(() => {
setInputValue(defaultValue || ""); setInputValue(defaultValue || '')
}, [defaultValue]); }, [defaultValue])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value; const newValue = e.target.value
setInputValue(newValue); setInputValue(newValue)
if (updateFormValue) { if (updateFormValue) {
updateFormValue(newValue); updateFormValue(newValue)
}
} }
};
return ( return (
<div className={`tw-form-control ${containerStyle}`}> <div className={`tw-form-control ${containerStyle}`}>
{labelTitle ? ( {labelTitle
? (
<label className="tw-label"> <label className="tw-label">
<span className={`tw-label-text tw-text-base-content ${labelStyle}`}>{labelTitle}</span> <span className={`tw-label-text tw-text-base-content ${labelStyle}`}>{labelTitle}</span>
</label> </label>
) : null} )
: null}
<input <input
required required
type={type || "text"} type={type || 'text'}
name={dataField} name={dataField}
value={inputValue} value={inputValue}
placeholder={placeholder || ""} placeholder={placeholder || ''}
autoComplete={autocomplete} autoComplete={autocomplete}
onChange={handleChange} onChange={handleChange}
className={`tw-input tw-input-bordered tw-w-full ${inputStyle || ""}`} className={`tw-input tw-input-bordered tw-w-full ${inputStyle || ''}`}
/> />
</div> </div>
); )
} }

View File

@ -1,3 +1,3 @@
export {TextAreaInput} from "./TextAreaInput" export { TextAreaInput } from './TextAreaInput'
export {TextInput} from "./TextInput" export { TextInput } from './TextInput'
export {SelectBox} from "./SelectBox" export { SelectBox } from './SelectBox'

View File

@ -3,30 +3,31 @@ import { Item } from '../../types'
import * as PropTypes from 'prop-types' import * as PropTypes from 'prop-types'
import { useEffect } from 'react' import { useEffect } from 'react'
export const ItemForm = ({ children, item, title, setPopupTitle }: { children?: React.ReactNode, item?: Item, title?: string, setPopupTitle?: React.Dispatch<React.SetStateAction<string>> }) => { export const ItemForm = ({ children, item, title, setPopupTitle }: { children?: React.ReactNode, item?: Item, title?: string, setPopupTitle?: React.Dispatch<React.SetStateAction<string>> }) => {
useEffect(() => { useEffect(() => {
setPopupTitle&& title && setPopupTitle(title); setPopupTitle && title && setPopupTitle(title)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [title]) }, [title])
return ( return (
<div>{ <div>{
children ? children
React.Children.toArray(children).map((child) => ? React.Children.toArray(children).map((child) =>
React.isValidElement<{ item: Item, test: string }>(child) ? React.isValidElement<{ item: Item, test: string }>(child)
React.cloneElement(child, { item: item, test: "test" }) : "" ? React.cloneElement(child, { item, test: 'test' })
) : "" : ''
)
: ''
}</div> }</div>
) )
} }
ItemForm.propTypes = { ItemForm.propTypes = {
children: PropTypes.node, children: PropTypes.node,
__TYPE: PropTypes.string, __TYPE: PropTypes.string
}; }
ItemForm.defaultProps = { ItemForm.defaultProps = {
__TYPE: 'ItemForm', __TYPE: 'ItemForm'
}; }

View File

@ -2,24 +2,25 @@ import * as React from 'react'
import { Item } from '../../types' import { Item } from '../../types'
import * as PropTypes from 'prop-types' import * as PropTypes from 'prop-types'
export const ItemView = ({ children, item }: { children?: React.ReactNode, item?: Item }) => { export const ItemView = ({ children, item }: { children?: React.ReactNode, item?: Item }) => {
return ( return (
<div> <div>
{children ? {children
React.Children.toArray(children).map((child) => ? React.Children.toArray(children).map((child) =>
React.isValidElement<{ item: Item }>(child) ? React.isValidElement<{ item: Item }>(child)
React.cloneElement(child, { item: item }) : "" ? React.cloneElement(child, { item })
) : ""} : ''
)
: ''}
</div> </div>
) )
} }
ItemView.propTypes = { ItemView.propTypes = {
children: PropTypes.node, children: PropTypes.node,
__TYPE: PropTypes.string, __TYPE: PropTypes.string
}; }
ItemView.defaultProps = { ItemView.defaultProps = {
__TYPE: 'ItemView', __TYPE: 'ItemView'
}; }

View File

@ -10,7 +10,7 @@ import { useFilterTags, useIsGroupTypeVisible, useIsLayerVisible, useVisibleGrou
import { useAddTag, useAllTagsLoaded, useGetItemTags, useTags } from './hooks/useTags' import { useAddTag, useAllTagsLoaded, useGetItemTags, useTags } from './hooks/useTags'
import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs' import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs'
import { Popup } from 'leaflet' import { Popup } from 'leaflet'
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom'
import { getValue } from '../../Utils/GetValue' import { getValue } from '../../Utils/GetValue'
import { hashTagRegex } from '../../Utils/HashTagRegex' import { hashTagRegex } from '../../Utils/HashTagRegex'
import { randomColor } from '../../Utils/RandomColor' import { randomColor } from '../../Utils/RandomColor'
@ -27,7 +27,7 @@ export const Layer = ({
markerIcon = 'circle-solid', markerIcon = 'circle-solid',
markerShape = 'circle', markerShape = 'circle',
markerDefaultColor = '#777', markerDefaultColor = '#777',
markerDefaultColor2 = "RGBA(35, 31, 32, 0.2)", markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)',
api, api,
itemType, itemType,
itemNameField = 'name', itemNameField = 'name',
@ -44,105 +44,104 @@ export const Layer = ({
onlyOnePerOwner = false, onlyOnePerOwner = false,
customEditLink, customEditLink,
customEditParameter, customEditParameter,
// eslint-disable-next-line camelcase
public_edit_items, public_edit_items,
listed = true, listed = true,
setItemFormPopup, setItemFormPopup,
itemFormPopup, itemFormPopup,
clusterRef clusterRef
}: LayerProps) => { }: LayerProps) => {
const filterTags = useFilterTags()
const filterTags = useFilterTags(); const items = useItems()
const setItemsApi = useSetItemsApi()
const setItemsData = useSetItemsData()
const getItemTags = useGetItemTags()
const addMarker = useAddMarker()
const addPopup = useAddPopup()
const leafletRefs = useLeafletRefs()
const items = useItems(); const location = useLocation()
const setItemsApi = useSetItemsApi();
const setItemsData = useSetItemsData();
const getItemTags = useGetItemTags();
const addMarker = useAddMarker();
const addPopup = useAddPopup();
const leafletRefs = useLeafletRefs();
const location = useLocation(); const allTagsLoaded = useAllTagsLoaded()
const allItemsLoaded = useAllItemsLoaded()
const allTagsLoaded = useAllTagsLoaded(); const setMarkerClicked = useSetMarkerClicked()
const allItemsLoaded = useAllItemsLoaded(); const selectPosition = useSelectPosition()
const setMarkerClicked = useSetMarkerClicked(); const tags = useTags()
const selectPosition = useSelectPosition(); const addTag = useAddTag()
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
const [tagsReady, setTagsReady] = useState<boolean>(false)
const tags = useTags(); const map = useMap()
const addTag = useAddTag();
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([]);
const [tagsReady, setTagsReady] = useState<boolean>(false);
const isLayerVisible = useIsLayerVisible()
const map = useMap(); const isGroupTypeVisible = useIsGroupTypeVisible()
const isLayerVisible = useIsLayerVisible();
const isGroupTypeVisible = useIsGroupTypeVisible();
const visibleGroupTypes = useVisibleGroupType();
const visibleGroupTypes = useVisibleGroupType()
useEffect(() => { useEffect(() => {
data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, markerDefaultColor2, api, itemType, itemNameField, itemSubnameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, customEditParameter, public_edit_items, listed, setItemFormPopup, itemFormPopup, clusterRef }); // eslint-disable-next-line camelcase
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, markerDefaultColor2, api, itemType, itemNameField, itemSubnameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, customEditParameter, public_edit_items, listed, setItemFormPopup, itemFormPopup, clusterRef }); data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, markerDefaultColor2, api, itemType, itemNameField, itemSubnameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, customEditParameter, public_edit_items, listed, setItemFormPopup, itemFormPopup, clusterRef })
// eslint-disable-next-line camelcase
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, markerDefaultColor2, api, itemType, itemNameField, itemSubnameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, customEditParameter, public_edit_items, listed, setItemFormPopup, itemFormPopup, clusterRef })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, api]) }, [data, api])
useMapEvents({ useMapEvents({
popupopen: (e) => { popupopen: (e) => {
const item = Object.entries(leafletRefs).find(r => r[1].popup == e.popup)?.[1].item; const item = Object.entries(leafletRefs).find(r => r[1].popup === e.popup)?.[1].item
if (item?.layer?.name == name && window.location.pathname.split("/")[1] != item.id) { if (item?.layer?.name === name && window.location.pathname.split('/')[1] !== item.id) {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
if (!location.pathname.includes("/item/")) { if (!location.pathname.includes('/item/')) {
window.history.pushState({}, "", `/${item.id}` + `${params.toString() !== "" ? `?${params}` : ""}`) window.history.pushState({}, '', `/${item.id}` + `${params.toString() !== '' ? `?${params}` : ''}`)
}
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}`
} }
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}`;
} }
},
}) })
const openPopup = () => { const openPopup = () => {
if (window.location.pathname.split("/").length <= 1 || window.location.pathname.split("/")[1] === "") { if (window.location.pathname.split('/').length <= 1 || window.location.pathname.split('/')[1] === '') {
map.closePopup(); map.closePopup()
} } else {
else { if (window.location.pathname.split('/')[1]) {
if (window.location.pathname.split("/")[1]) { const id = window.location.pathname.split('/')[1]
const id = window.location.pathname.split("/")[1] const ref = leafletRefs[id]
const ref = leafletRefs[id];
if (ref?.marker && ref.item.layer?.name === name) { if (ref?.marker && ref.item.layer?.name === name) {
ref.marker && clusterRef.hasLayer(ref.marker) && clusterRef?.zoomToShowLayer(ref.marker, () => { ref.marker && clusterRef.hasLayer(ref.marker) && clusterRef?.zoomToShowLayer(ref.marker, () => {
ref.marker.openPopup(); ref.marker.openPopup()
}); })
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); 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.querySelector('meta[property="og:title"]')?.setAttribute("content", ref.item.name); document.querySelector('meta[property="og:title"]')?.setAttribute('content', ref.item.name)
document.querySelector('meta[property="og:description"]')?.setAttribute("content", ref.item.text); document.querySelector('meta[property="og:description"]')?.setAttribute('content', ref.item.text)
} }
} }
} }
} }
useEffect(() => { useEffect(() => {
openPopup(); openPopup()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [leafletRefs, location]) }, [leafletRefs, location])
useEffect(() => { useEffect(() => {
if (tagsReady) { if (tagsReady) {
const processedTags = {}; const processedTags = {}
newTagsToAdd.map(newtag => { newTagsToAdd.map(newtag => {
if (!processedTags[newtag.name]) { if (!processedTags[newtag.name]) {
processedTags[newtag.name] = true; processedTags[newtag.name] = true
addTag(newtag); addTag(newtag)
} }
return null
}) })
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -151,85 +150,77 @@ export const Layer = ({
return ( return (
<> <>
{items && {items &&
items. items
filter(item => item.layer?.name === name)?. .filter(item => item.layer?.name === name)
filter(item => ?.filter(item =>
filterTags.length == 0 ? item : filterTags.some(tag => getItemTags(item).some(filterTag => filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())))?. filterTags.length === 0 ? item : filterTags.some(tag => getItemTags(item).some(filterTag => filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())))
filter(item => item.layer && isLayerVisible(item.layer)). ?.filter(item => item.layer && isLayerVisible(item.layer))
filter(item => item.group_type && isGroupTypeVisible(item.group_type) || visibleGroupTypes.length == 0). .filter(item => (item.group_type && isGroupTypeVisible(item.group_type)) || visibleGroupTypes.length === 0)
map((item: Item) => { .map((item: Item) => {
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) { if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField)
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField); else item[itemTextField] = ''
else item[itemTextField] = "";
if (item?.tags) { if (item?.tags) {
item[itemTextField] = item[itemTextField] + '\n\n'; item[itemTextField] = item[itemTextField] + '\n\n'
item.tags.map(tag => { item.tags.map(tag => {
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) { return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `) }
return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `)
return item[itemTextField] return item[itemTextField]
})
});
} }
if (allTagsLoaded && allItemsLoaded) { if (allTagsLoaded && allItemsLoaded) {
item[itemTextField].match(hashTagRegex)?.map(tag => { item[itemTextField].match(hashTagRegex)?.map(tag => {
if ((!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) && !newTagsToAdd.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { if ((!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) && !newTagsToAdd.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
const newTag = { id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() }; const newTag = { id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() }
setNewTagsToAdd(current => [...current, newTag]); setNewTagsToAdd(current => [...current, newTag])
} }
}); return null
!tagsReady && setTagsReady(true); })
!tagsReady && setTagsReady(true)
} }
const itemTags = getItemTags(item)
const itemTags = getItemTags(item); const latitude = itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined
const longitude = itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined
const latitude = itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined; let color1 = markerDefaultColor
const longitude = itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined; let color2 = markerDefaultColor2
if (itemColorField && getValue(item, itemColorField) != null) color1 = getValue(item, itemColorField)
let color1 = markerDefaultColor;
let color2 = markerDefaultColor2;
if (itemColorField && getValue(item, itemColorField) != null) color1 = getValue(item, itemColorField);
else if (itemTags && itemTags[0]) { else if (itemTags && itemTags[0]) {
color1 = itemTags[0].color; color1 = itemTags[0].color
} }
if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color; if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color
else if (itemTags && itemTags[1]) { else if (itemTags && itemTags[1]) {
color2 = itemTags[1].color; color2 = itemTags[1].color
} }
return ( return (
<Marker ref={(r) => { <Marker ref={(r) => {
if (!(item.id in leafletRefs && leafletRefs[item.id].marker == r)) if (!(item.id in leafletRefs && leafletRefs[item.id].marker === r)) { r && addMarker(item, r) }
r && addMarker(item, r);
}} }}
eventHandlers={{ eventHandlers={{
click: () => { click: () => {
selectPosition && setMarkerClicked(item) selectPosition && setMarkerClicked(item)
}, }
}} }}
icon={MarkerIconFactory(markerShape, color1, color2, item.markerIcon ? item.markerIcon : markerIcon)} key={item.id} position={[latitude, longitude]}> icon={MarkerIconFactory(markerShape, color1, color2, item.markerIcon ? item.markerIcon : markerIcon)} key={item.id} position={[latitude, longitude]}>
{ {
(children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemView") ? (children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === 'ItemView')
React.Children.toArray(children).map((child) => ? React.Children.toArray(children).map((child) =>
React.isValidElement(child) && child.props.__TYPE === "ItemView" ? React.isValidElement(child) && child.props.__TYPE === 'ItemView'
<ItemViewPopup ref={(r) => { ? <ItemViewPopup ref={(r) => {
if (!(item.id in leafletRefs && leafletRefs[item.id].popup == r)) if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { r && addPopup(item, r as Popup) }
r && addPopup(item, r as Popup);
}} key={item.id + item.name} }} key={item.id + item.name}
item={item} item={item}
setItemFormPopup={setItemFormPopup}> setItemFormPopup={setItemFormPopup}>
{child} {child}
</ItemViewPopup> </ItemViewPopup>
: "" : ''
) )
: : <>
<>
<ItemViewPopup key={item.id + item.name} ref={(r) => { <ItemViewPopup key={item.id + item.name} ref={(r) => {
if (!(item.id in leafletRefs && leafletRefs[item.id].popup == r)) if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { r && addPopup(item, r as Popup) }
r && addPopup(item, r as Popup);
}} }}
item={item} item={item}
setItemFormPopup={setItemFormPopup} /> setItemFormPopup={setItemFormPopup} />
@ -237,22 +228,20 @@ export const Layer = ({
} }
<Tooltip offset={[0, -38]} direction='top'>{item.name ? item.name : getValue(item, itemNameField)}</Tooltip> <Tooltip offset={[0, -38]} direction='top'>{item.name ? item.name : getValue(item, itemNameField)}</Tooltip>
</Marker> </Marker>
); )
} } else return null
else return null;
}) })
} }
{// {children}} {// {children}}
} }
{itemFormPopup && itemFormPopup.layer!.name == name && {itemFormPopup && itemFormPopup.layer!.name === name &&
(children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemForm") ? (children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === 'ItemForm')
React.Children.toArray(children).map((child) => ? React.Children.toArray(children).map((child) =>
React.isValidElement(child) && child.props.__TYPE === "ItemForm" ? React.isValidElement(child) && child.props.__TYPE === 'ItemForm'
<ItemFormPopup key={setItemFormPopup?.name} position={itemFormPopup!.position} layer={itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={itemFormPopup!.item} >{child}</ItemFormPopup> ? <ItemFormPopup key={setItemFormPopup?.name} position={itemFormPopup!.position} layer={itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={itemFormPopup!.item} >{child}</ItemFormPopup>
: "" : ''
) )
: : <>
<>
<ItemFormPopup position={itemFormPopup!.position} layer={itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={itemFormPopup!.item} /> <ItemFormPopup position={itemFormPopup!.position} layer={itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={itemFormPopup!.item} />
</>) </>)
} }

View File

@ -1,19 +1,19 @@
import * as React from 'react' import * as React from 'react'
import { useEffect } from 'react'; import { useEffect } from 'react'
import { ItemsApi, Permission } from '../../types'; import { ItemsApi, Permission } from '../../types'
import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './hooks/usePermissions' import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './hooks/usePermissions'
import { useAuth } from '../Auth'; import { useAuth } from '../Auth'
export function Permissions ({ data, api, adminRole } : {data?: Permission[], api?: ItemsApi<Permission>, adminRole?: string}) { export function Permissions ({ data, api, adminRole } : {data?: Permission[], api?: ItemsApi<Permission>, adminRole?: string}) {
const setPermissionData = useSetPermissionData(); const setPermissionData = useSetPermissionData()
const setPermissionApi = useSetPermissionApi(); const setPermissionApi = useSetPermissionApi()
const setAdminRole = useSetAdminRole(); const setAdminRole = useSetAdminRole()
const {user} = useAuth(); const { user } = useAuth()
useEffect(() => { useEffect(() => {
adminRole && setAdminRole(adminRole); adminRole && setAdminRole(adminRole)
data && setPermissionData(data); data && setPermissionData(data)
api && setPermissionApi(api); api && setPermissionApi(api)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, data, adminRole, user]) }, [api, data, adminRole, user])

View File

@ -1,26 +1,24 @@
import * as React from 'react' import * as React from 'react'
import { useLayers } from '../hooks/useLayers' import { useLayers } from '../hooks/useLayers'
import { useHasUserPermission } from '../hooks/usePermissions'; import { useHasUserPermission } from '../hooks/usePermissions'
export default function AddButton ({ triggerAction }: { triggerAction: React.Dispatch<React.SetStateAction<any>> }) { export default function AddButton ({ triggerAction }: { triggerAction: React.Dispatch<React.SetStateAction<any>> }) {
const layers = useLayers()
const layers = useLayers(); const hasUserPermission = useHasUserPermission()
const hasUserPermission = useHasUserPermission();
const canAddItems = () => { const canAddItems = () => {
let canAdd = false; let canAdd = false
layers.map(layer => { layers.map(layer => {
if (layer.api?.createItem && hasUserPermission(layer.api.collectionName!, "create", undefined, layer) && layer.listed) canAdd = true; if (layer.api?.createItem && hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) && layer.listed) canAdd = true
return null
}) })
return canAdd; return canAdd
} }
return ( return (
<>{ <>{
canAddItems() ? canAddItems()
<div className="tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-500 tw-absolute tw-right-4 tw-bottom-4" > ? <div className="tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-500 tw-absolute tw-right-4 tw-bottom-4" >
<label tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow tw-bg-base-100"> <label tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow tw-bg-base-100">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="3" stroke="currentColor" className="tw-w-5 tw-h-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="3" stroke="currentColor" className="tw-w-5 tw-h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> <path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
@ -28,13 +26,13 @@ export default function AddButton({ triggerAction }: { triggerAction: React.Disp
</label> </label>
<ul tabIndex={0} className="tw-dropdown-content tw-pr-1 tw-list-none"> <ul tabIndex={0} className="tw-dropdown-content tw-pr-1 tw-list-none">
{layers.map((layer) => ( {layers.map((layer) => (
layer.api?.createItem && hasUserPermission(layer.api.collectionName!, "create", undefined, layer) && layer.listed &&( layer.api?.createItem && hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) && layer.listed && (
<li key={layer.name} > <li key={layer.name} >
<a> <a>
<div className="tw-tooltip tw-tooltip-left" data-tip={layer.menuText}> <div className="tw-tooltip tw-tooltip-left" data-tip={layer.menuText}>
<button tabIndex={0} <button tabIndex={0}
className="tw-z-500 tw-border-0 tw-pl-2 tw-p-0 tw-mb-3 tw-w-10 tw-h-10 tw-cursor-pointer tw-rounded-full tw-mouse tw-drop-shadow-md tw-transition tw-ease-in tw-duration-200 focus:tw-outline-none" className="tw-z-500 tw-border-0 tw-pl-2 tw-p-0 tw-mb-3 tw-w-10 tw-h-10 tw-cursor-pointer tw-rounded-full tw-mouse tw-drop-shadow-md tw-transition tw-ease-in tw-duration-200 focus:tw-outline-none"
style={{ backgroundColor: layer.menuColor || "#777"}} style={{ backgroundColor: layer.menuColor || '#777' }}
onClick={() => { triggerAction(layer) }}> onClick={() => { triggerAction(layer) }}>
<img src={layer.menuIcon} className="tw-h-6 tw-w-6 tw-text-white" style={{ filter: 'invert(100%) brightness(200%)' }} /> <img src={layer.menuIcon} className="tw-h-6 tw-w-6 tw-text-white" style={{ filter: 'invert(100%) brightness(200%)' }} />
</button> </button>
@ -45,7 +43,8 @@ export default function AddButton({ triggerAction }: { triggerAction: React.Disp
))} ))}
</ul> </ul>
</div> : "" </div>
: ''
} }
</> </>

View File

@ -1,14 +1,9 @@
import * as L from 'leaflet' import * as L from 'leaflet'
import * as React from 'react' import * as React from 'react'
export const Control = ({ position, children, zIndex, absolute }: { position: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight', children: React.ReactNode, zIndex: string, absolute: boolean }) => {
export const Control = ({ position, children, zIndex, absolute }: { position: "topLeft" | "topRight" | "bottomLeft" | "bottomRight", children: React.ReactNode, zIndex: string, absolute: boolean }) => {
const controlContainerRef = React.createRef<HTMLDivElement>() const controlContainerRef = React.createRef<HTMLDivElement>()
React.useEffect(() => { React.useEffect(() => {
if (controlContainerRef.current !== null) { if (controlContainerRef.current !== null) {
L.DomEvent.disableClickPropagation(controlContainerRef.current) L.DomEvent.disableClickPropagation(controlContainerRef.current)
@ -17,7 +12,7 @@ export const Control = ({ position, children, zIndex, absolute }: { position: "t
}, [controlContainerRef]) }, [controlContainerRef])
return ( return (
<div ref={controlContainerRef} style={{zIndex: zIndex}} className={`${absolute && 'tw-absolute'} tw-z-[999] tw-flex-col ${position === 'topLeft' && "tw-top-4 tw-left-4"} ${position === 'bottomLeft' && "tw-bottom-4 tw-left-4"} ${position === 'topRight' && "tw-bottom-4 tw-right-4"} ${position === 'bottomRight' && "tw-bottom-4 tw-right-4"}`}> <div ref={controlContainerRef} style={{ zIndex }} className={`${absolute && 'tw-absolute'} tw-z-[999] tw-flex-col ${position === 'topLeft' && 'tw-top-4 tw-left-4'} ${position === 'bottomLeft' && 'tw-bottom-4 tw-left-4'} ${position === 'topRight' && 'tw-bottom-4 tw-right-4'} ${position === 'bottomRight' && 'tw-bottom-4 tw-right-4'}`}>
{children} {children}
</div> </div>

View File

@ -1,13 +1,11 @@
import * as React from 'react' import * as React from 'react'
import { useAddVisibleGroupType, useIsGroupTypeVisible, useToggleVisibleGroupType, useVisibleGroupType } from '../../hooks/useFilter'; import { useAddVisibleGroupType, useIsGroupTypeVisible, useToggleVisibleGroupType, useVisibleGroupType } from '../../hooks/useFilter'
import { useEffect } from 'react'; import { useEffect } from 'react'
export function FilterControl () { export function FilterControl () {
const [open, setOpen] = React.useState(false)
const [open, setOpen] = React.useState(false); const groupTypes = [{ text: 'Regional Gruppe', value: 'wuerdekompass' }, { text: 'Themen Gruppe', value: 'themenkompass' }, { text: 'liebevoll.jetzt', value: 'liebevoll.jetzt' }]
const groupTypes = [{ text: "Regional Gruppe", value: "wuerdekompass" }, { text: "Themen Gruppe", value: "themenkompass" }, { text: "liebevoll.jetzt", value: "liebevoll.jetzt" }]
useEffect(() => { useEffect(() => {
groupTypes.map(layer => groupTypes.map(layer =>
@ -16,17 +14,16 @@ export function FilterControl() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const isGroupTypeVisible = useIsGroupTypeVisible()
const isGroupTypeVisible = useIsGroupTypeVisible(); const toggleVisibleGroupType = useToggleVisibleGroupType()
const toggleVisibleGroupType = useToggleVisibleGroupType(); const addVisibleGroupType = useAddVisibleGroupType()
const addVisibleGroupType = useAddVisibleGroupType(); const visibleGroupTypes = useVisibleGroupType()
const visibleGroupTypes = useVisibleGroupType();
return ( return (
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit"> <div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit">
{ {
open ? open
<div className="tw-card-body tw-pr-4 tw-min-w-[8rem] tw-p-2 tw-w-fit tw-transition-all tw-duration-300"> ? <div className="tw-card-body tw-pr-4 tw-min-w-[8rem] tw-p-2 tw-w-fit tw-transition-all tw-duration-300">
<label className="tw-btn tw-btn-sm tw-rounded-2xl tw-btn-circle tw-btn-ghost hover:tw-bg-transparent tw-absolute tw-right-0 tw-top-0 tw-text-gray-600" onClick={() => { <label className="tw-btn tw-btn-sm tw-rounded-2xl tw-btn-circle tw-btn-ghost hover:tw-bg-transparent tw-absolute tw-right-0 tw-top-0 tw-text-gray-600" onClick={() => {
setOpen(false) setOpen(false)
}}> }}>
@ -39,8 +36,7 @@ export function FilterControl() {
} }
</ul> </ul>
</div> </div>
: : <div className="tw-indicator">
<div className="tw-indicator">
{visibleGroupTypes.length < groupTypes.length && <span className="tw-indicator-item tw-badge tw-badge-success tw-h-4 tw-p-2 tw-translate-x-1/3 -tw-translate-y-1/3 tw-border-0"></span>} {visibleGroupTypes.length < groupTypes.length && <span className="tw-indicator-item tw-badge tw-badge-success tw-h-4 tw-p-2 tw-translate-x-1/3 -tw-translate-y-1/3 tw-border-0"></span>}
<div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => { <div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => {
setOpen(true) setOpen(true)

View File

@ -1,29 +1,27 @@
import { useNavigate } from "react-router-dom" import { useNavigate } from 'react-router-dom'
import { useAuth } from "../../../Auth"; import { useAuth } from '../../../Auth'
export const GratitudeControl = () => { export const GratitudeControl = () => {
const navigate = useNavigate(); const navigate = useNavigate()
const {isAuthenticated} = useAuth(); const { isAuthenticated } = useAuth()
if(isAuthenticated) return ( if (isAuthenticated) {
return (
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit"> <div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit">
{ {
<div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => { <div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => {
navigate("/select-user") navigate('/select-user')
}}> }}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2.5} stroke="currentColor" className="size-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2.5} stroke="currentColor" className="size-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg> </svg>
</div> </div>
} }
</div> </div>
) )
else return (<></>); } else return (<></>)
} }

View File

@ -1,23 +1,20 @@
import * as React from 'react' import * as React from 'react'
import { useLayers } from '../../hooks/useLayers'; import { useLayers } from '../../hooks/useLayers'
import { useIsLayerVisible, useToggleVisibleLayer } from '../../hooks/useFilter'; import { useIsLayerVisible, useToggleVisibleLayer } from '../../hooks/useFilter'
export function LayerControl () { export function LayerControl () {
const [open, setOpen] = React.useState(false)
const [open, setOpen] = React.useState(false); const layers = useLayers()
const layers = useLayers(); const isLayerVisible = useIsLayerVisible()
const toggleVisibleLayer = useToggleVisibleLayer()
const isLayerVisible = useIsLayerVisible();
const toggleVisibleLayer = useToggleVisibleLayer();
return ( return (
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit"> <div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit">
{ {
open ? open
<div className="tw-card-body tw-pr-4 tw-min-w-[8rem] tw-p-2 tw-transition-all tw-w-fit tw-duration-300"> ? <div className="tw-card-body tw-pr-4 tw-min-w-[8rem] tw-p-2 tw-transition-all tw-w-fit tw-duration-300">
<label className="tw-btn tw-btn-sm tw-rounded-2xl tw-btn-circle tw-btn-ghost hover:tw-bg-transparent tw-absolute tw-right-0 tw-top-0 tw-text-gray-600" onClick={() => { <label className="tw-btn tw-btn-sm tw-rounded-2xl tw-btn-circle tw-btn-ghost hover:tw-bg-transparent tw-absolute tw-right-0 tw-top-0 tw-text-gray-600" onClick={() => {
setOpen(false) setOpen(false)
}}> }}>
@ -30,8 +27,7 @@ export function LayerControl() {
} }
</ul> </ul>
</div> </div>
: : <div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => {
<div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => {
setOpen(true) setOpen(true)
}}> }}>
<svg version="1.1" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg version="1.1" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
@ -39,7 +35,6 @@ export function LayerControl() {
<path id="svg_2" strokeWidth="2" stroke="currentColor" fill="none" d="m11.247,4.30851l6.2349,3.0877c0.69083,0.34211 0.69083,0.89295 0,1.2351l-6.2349,3.0877c-0.69083,0.34211 -1.8031,0.34212 -2.494,0l-6.2349,-3.0877c-0.69083,-0.34211 -0.69083,-0.89295 0,-1.2351l6.2349,-3.0877c0.69083,-0.34211 1.8031,-0.34211 2.494,0z" /> <path id="svg_2" strokeWidth="2" stroke="currentColor" fill="none" d="m11.247,4.30851l6.2349,3.0877c0.69083,0.34211 0.69083,0.89295 0,1.2351l-6.2349,3.0877c-0.69083,0.34211 -1.8031,0.34212 -2.494,0l-6.2349,-3.0877c-0.69083,-0.34211 -0.69083,-0.89295 0,-1.2351l6.2349,-3.0877c0.69083,-0.34211 1.8031,-0.34211 2.494,0z" />
</svg> </svg>
</div> </div>
} }

View File

@ -7,32 +7,29 @@ import { useEffect, useRef, useState } from 'react'
// Converts leaflet.locatecontrol to a React Component // Converts leaflet.locatecontrol to a React Component
export const LocateControl = () => { export const LocateControl = () => {
const map = useMap()
const map = useMap();
// prevent react18 from calling useEffect twice // prevent react18 from calling useEffect twice
const init = useRef(false) const init = useRef(false)
const [lc, setLc] = useState<any>(null)
const [lc, setLc] = useState<any>(null); const [active, setActive] = useState<boolean>(false)
const [active, setActive] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (!init.current) { if (!init.current) {
// @ts-ignore // @ts-ignore
setLc(L.control.locate().addTo(map)); setLc(L.control.locate().addTo(map))
init.current = true; init.current = true
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
useMapEvents({ useMapEvents({
locationfound: () => { locationfound: () => {
setLoading(false); setLoading(false)
setActive(true); setActive(true)
}, }
}) })
return (<> return (<>
@ -40,22 +37,18 @@ export const LocateControl = () => {
<div className="tw-card-body tw-card tw-p-2 tw-h-10 tw-w-10 " onClick={() => { <div className="tw-card-body tw-card tw-p-2 tw-h-10 tw-w-10 " onClick={() => {
if (active) { if (active) {
lc.stop(); lc.stop()
setActive(false); setActive(false)
} else {
lc.start()
setLoading(true)
} }
else { }}>{loading
lc.start(); ? <span className="tw-loading tw-loading-spinner tw-loading-md tw-mt-1"></span>
setLoading(true); : <svg fill={`${active ? '#fc8702' : 'currentColor'}`} viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" className='tw-mt-1 tw-p-[1px]'>
}
}}>{loading ? <span className="tw-loading tw-loading-spinner tw-loading-md tw-mt-1"></span> :
<svg fill={`${active? "#fc8702" : "currentColor"}`} viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" className='tw-mt-1 tw-p-[1px]'>
<path d="M30 14.75h-2.824c-0.608-5.219-4.707-9.318-9.874-9.921l-0.053-0.005v-2.824c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.824c-5.219 0.608-9.318 4.707-9.921 9.874l-0.005 0.053h-2.824c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.824c0.608 5.219 4.707 9.318 9.874 9.921l0.053 0.005v2.824c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.824c5.219-0.608 9.318-4.707 9.921-9.874l0.005-0.053h2.824c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM17.25 24.624v-2.624c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.624c-3.821-0.57-6.803-3.553-7.368-7.326l-0.006-0.048h2.624c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0h-2.624c0.57-3.821 3.553-6.804 7.326-7.368l0.048-0.006v2.624c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.624c3.821 0.57 6.803 3.553 7.368 7.326l0.006 0.048h-2.624c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.624c-0.571 3.821-3.553 6.803-7.326 7.368l-0.048 0.006z"></path> <path d="M30 14.75h-2.824c-0.608-5.219-4.707-9.318-9.874-9.921l-0.053-0.005v-2.824c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.824c-5.219 0.608-9.318 4.707-9.921 9.874l-0.005 0.053h-2.824c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.824c0.608 5.219 4.707 9.318 9.874 9.921l0.053 0.005v2.824c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.824c5.219-0.608 9.318-4.707 9.921-9.874l0.005-0.053h2.824c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM17.25 24.624v-2.624c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.624c-3.821-0.57-6.803-3.553-7.368-7.326l-0.006-0.048h2.624c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0h-2.624c0.57-3.821 3.553-6.804 7.326-7.368l0.048-0.006v2.624c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.624c3.821 0.57 6.803 3.553 7.368 7.326l0.006 0.048h-2.624c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.624c-0.571 3.821-3.553 6.803-7.326 7.368l-0.048 0.006z"></path>
</svg>} </svg>}
</div> </div>
</div></>) </div></>)
} }

View File

@ -1,16 +1,15 @@
import * as React from 'react' import * as React from 'react'
import { useQuestsOpen, useSetQuestOpen } from '../../../Gaming/hooks/useQuests'; import { useQuestsOpen, useSetQuestOpen } from '../../../Gaming/hooks/useQuests'
export function QuestControl () { export function QuestControl () {
const questsOpen = useQuestsOpen()
const questsOpen = useQuestsOpen(); const setQuestsOpen = useSetQuestOpen()
const setQuestsOpen = useSetQuestOpen();
return ( return (
<> <>
{questsOpen ? "" : {questsOpen
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-my-2 tw-w-10" onClick={e => e.stopPropagation()}> ? ''
: <div className="tw-card tw-bg-base-100 tw-shadow-xl tw-my-2 tw-w-10" onClick={e => e.stopPropagation()}>
<div className="tw-card-body hover:tw-bg-slate-300 tw-rounded-2xl tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => setQuestsOpen(true)}> <div className="tw-card-body hover:tw-bg-slate-300 tw-rounded-2xl tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => setQuestsOpen(true)}>
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="currentColor" viewBox="0 0 448 512"><path d="M192 0c17.7 0 32 14.3 32 32V144H160V32c0-17.7 14.3-32 32-32zM64 64c0-17.7 14.3-32 32-32s32 14.3 32 32v80H64V64zm192 0c0-17.7 14.3-32 32-32s32 14.3 32 32v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V64zm96 64c0-17.7 14.3-32 32-32s32 14.3 32 32v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V128zm-96 88l0-.6c9.4 5.4 20.3 8.6 32 8.6c13.2 0 25.4-4 35.6-10.8c8.7 24.9 32.5 42.8 60.4 42.8c11.7 0 22.6-3.1 32-8.6V256c0 52.3-25.1 98.8-64 128v96c0 17.7-14.3 32-32 32H160c-17.7 0-32-14.3-32-32V401.6c-17.3-7.9-33.2-18.8-46.9-32.5L69.5 357.5C45.5 333.5 32 300.9 32 267V240c0-35.3 28.7-64 64-64h88c22.1 0 40 17.9 40 40s-17.9 40-40 40H128c-8.8 0-16 7.2-16 16s7.2 16 16 16h56c39.8 0 72-32.2 72-72z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="currentColor" viewBox="0 0 448 512"><path d="M192 0c17.7 0 32 14.3 32 32V144H160V32c0-17.7 14.3-32 32-32zM64 64c0-17.7 14.3-32 32-32s32 14.3 32 32v80H64V64zm192 0c0-17.7 14.3-32 32-32s32 14.3 32 32v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V64zm96 64c0-17.7 14.3-32 32-32s32 14.3 32 32v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V128zm-96 88l0-.6c9.4 5.4 20.3 8.6 32 8.6c13.2 0 25.4-4 35.6-10.8c8.7 24.9 32.5 42.8 60.4 42.8c11.7 0 22.6-3.1 32-8.6V256c0 52.3-25.1 98.8-64 128v96c0 17.7-14.3 32-32 32H160c-17.7 0-32-14.3-32-32V401.6c-17.3-7.9-33.2-18.8-46.9-32.5L69.5 357.5C45.5 333.5 32 300.9 32 267V240c0-35.3 28.7-64 64-64h88c22.1 0 40 17.9 40 40s-17.9 40-40 40H128c-8.8 0-16 7.2-16 16s7.2 16 16 16h56c39.8 0 72-32.2 72-72z"/></svg>

View File

@ -1,95 +1,87 @@
import * as React from 'react' import * as React from 'react'
import { useAddFilterTag } from '../../hooks/useFilter' import { useAddFilterTag } from '../../hooks/useFilter'
import useWindowDimensions from '../../hooks/useWindowDimension'; import useWindowDimensions from '../../hooks/useWindowDimension'
import axios from 'axios'; import axios from 'axios'
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react'
import { useMap, useMapEvents } from 'react-leaflet'; import { useMap, useMapEvents } from 'react-leaflet'
import { LatLng, LatLngBounds } from 'leaflet'; import { LatLng, LatLngBounds } from 'leaflet'
import { useDebounce } from '../../hooks/useDebounce'; import { useDebounce } from '../../hooks/useDebounce'
import { useTags } from '../../hooks/useTags'; import { useTags } from '../../hooks/useTags'
import { useItems } from '../../hooks/useItems'; import { useItems } from '../../hooks/useItems'
import { useLeafletRefs } from '../../hooks/useLeafletRefs'; import { useLeafletRefs } from '../../hooks/useLeafletRefs'
import { getValue } from '../../../../Utils/GetValue'; import { getValue } from '../../../../Utils/GetValue'
import { LocateControl } from './LocateControl'; import { LocateControl } from './LocateControl'
import * as L from 'leaflet'; import * as L from 'leaflet'
import MarkerIconFactory from '../../../../Utils/MarkerIconFactory'; import MarkerIconFactory from '../../../../Utils/MarkerIconFactory'
import { decodeTag } from '../../../../Utils/FormatTags'; import { decodeTag } from '../../../../Utils/FormatTags'
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom'
import { Item } from '../../../../types'; import { Item } from '../../../../types'
import { SidebarControl } from './SidebarControl'; import { SidebarControl } from './SidebarControl'
export const SearchControl = () => { export const SearchControl = () => {
const windowDimensions = useWindowDimensions()
const [popupOpen, setPopupOpen] = useState(false)
const windowDimensions = useWindowDimensions(); const [value, setValue] = useState('')
const [popupOpen, setPopupOpen] = useState(false); const [geoResults, setGeoResults] = useState<Array<any>>([])
const [tagsResults, setTagsResults] = useState<Array<any>>([])
const [value, setValue] = useState(''); const [itemsResults, setItemsResults] = useState<Array<Item>>([])
const [geoResults, setGeoResults] = useState<Array<any>>([]); const [hideSuggestions, setHideSuggestions] = useState(true)
const [tagsResults, setTagsResults] = useState<Array<any>>([]);
const [itemsResults, setItemsResults] = useState<Array<Item>>([]);
const [hideSuggestions, setHideSuggestions] = useState(true);
const map = useMap();
const tags = useTags();
const items = useItems();
const leafletRefs = useLeafletRefs();
const addFilterTag = useAddFilterTag();
const map = useMap()
const tags = useTags()
const items = useItems()
const leafletRefs = useLeafletRefs()
const addFilterTag = useAddFilterTag()
useMapEvents({ useMapEvents({
popupopen: () => { popupopen: () => {
setPopupOpen(true); setPopupOpen(true)
}, },
popupclose: () => { popupclose: () => {
setPopupOpen(false); setPopupOpen(false)
} }
}) })
const navigate = useNavigate(); const navigate = useNavigate()
useDebounce(() => { useDebounce(() => {
const searchGeo = async () => { const searchGeo = async () => {
try { try {
const { data } = await axios.get( const { data } = await axios.get(
`https://photon.komoot.io/api/?q=${value}&limit=5` `https://photon.komoot.io/api/?q=${value}&limit=5`
); )
setGeoResults(data.features); setGeoResults(data.features)
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
}; }
searchGeo(); searchGeo()
setItemsResults(items.filter(item => { setItemsResults(items.filter(item => {
if (item.layer?.itemNameField) item.name = getValue(item, item.layer.itemNameField) if (item.layer?.itemNameField) item.name = getValue(item, item.layer.itemNameField)
if (item.layer?.itemTextField) item.text = getValue(item, item.layer.itemTextField) if (item.layer?.itemTextField) item.text = getValue(item, item.layer.itemTextField)
return value.length > 2 && ((item.layer?.listed && item.name?.toLowerCase().includes(value.toLowerCase()) || item.text?.toLowerCase().includes(value.toLowerCase()))) return value.length > 2 && (((item.layer?.listed && item.name?.toLowerCase().includes(value.toLowerCase())) || item.text?.toLowerCase().includes(value.toLowerCase())))
})) }))
let phrase = value; let phrase = value
if (value.startsWith("#")) phrase = value.substring(1); if (value.startsWith('#')) phrase = value.substring(1)
setTagsResults(tags.filter(tag => tag.name?.toLowerCase().includes(phrase.toLowerCase()))) setTagsResults(tags.filter(tag => tag.name?.toLowerCase().includes(phrase.toLowerCase())))
}, 500, [value])
}, 500, [value]);
const hide = async () => { const hide = async () => {
setTimeout(() => { setTimeout(() => {
setHideSuggestions(true); setHideSuggestions(true)
}, 200); }, 200)
} }
const searchInput = useRef<HTMLInputElement>(null); const searchInput = useRef<HTMLInputElement>(null)
const [embedded, setEmbedded] = useState<boolean>(true) const [embedded, setEmbedded] = useState<boolean>(true)
const location = useLocation()
const location = useLocation();
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const embedded = params.get("embedded"); const embedded = params.get('embedded')
embedded != "true" && setEmbedded(false) embedded !== 'true' && setEmbedded(false)
}, [location]); }, [location])
return (<> return (<>
{!(windowDimensions.height < 500 && popupOpen && hideSuggestions) && {!(windowDimensions.height < 500 && popupOpen && hideSuggestions) &&
@ -101,16 +93,17 @@ export const SearchControl = () => {
ref={searchInput} ref={searchInput}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
onFocus={() => { onFocus={() => {
setHideSuggestions(false); setHideSuggestions(false)
if (windowDimensions.width < 500) map.closePopup(); if (windowDimensions.width < 500) map.closePopup()
}} }}
onBlur={() => hide()} /> onBlur={() => hide()} />
{value.length > 0 && <button className="tw-btn tw-btn-sm tw-btn-circle tw-absolute tw-right-2 tw-top-2" onClick={() => setValue("")}></button>} {value.length > 0 && <button className="tw-btn tw-btn-sm tw-btn-circle tw-absolute tw-right-2 tw-top-2" onClick={() => setValue('')}></button>}
</div> </div>
<LocateControl /> <LocateControl />
</div> </div>
{hideSuggestions || Array.from(geoResults).length == 0 && itemsResults.length == 0 && tagsResults.length == 0 && !isGeoCoordinate(value) || value.length == 0 ? "" : {hideSuggestions || (Array.from(geoResults).length === 0 && itemsResults.length === 0 && tagsResults.length === 0 && !isGeoCoordinate(value)) || value.length === 0
<div className='tw-card tw-card-body tw-bg-base-100 tw-p-4 tw-mt-2 tw-shadow-xl tw-overflow-y-auto tw-max-h-[calc(100dvh-152px)] tw-absolute tw-z-3000'> ? ''
: <div className='tw-card tw-card-body tw-bg-base-100 tw-p-4 tw-mt-2 tw-shadow-xl tw-overflow-y-auto tw-max-h-[calc(100dvh-152px)] tw-absolute tw-z-3000'>
{tagsResults.length > 0 && {tagsResults.length > 0 &&
<div className='tw-flex tw-flex-wrap'> <div className='tw-flex tw-flex-wrap'>
{tagsResults.slice(0, 3).map(tag => ( {tagsResults.slice(0, 3).map(tag => (
@ -126,14 +119,12 @@ export const SearchControl = () => {
{itemsResults.length > 0 && tagsResults.length > 0 && <hr className='tw-opacity-50'></hr>} {itemsResults.length > 0 && tagsResults.length > 0 && <hr className='tw-opacity-50'></hr>}
{itemsResults.slice(0, 5).map(item => ( {itemsResults.slice(0, 5).map(item => (
<div key={item.id} className='tw-cursor-pointer hover:tw-font-bold' onClick={() => { <div key={item.id} className='tw-cursor-pointer hover:tw-font-bold' onClick={() => {
const marker = Object.entries(leafletRefs).find(r => r[1].item == item)?.[1].marker; const marker = Object.entries(leafletRefs).find(r => r[1].item === item)?.[1].marker
if (marker) { if (marker) {
navigate(`/${item.id}?${new URLSearchParams(window.location.search)}`) navigate(`/${item.id}?${new URLSearchParams(window.location.search)}`)
} else {
navigate('item/' + item.id + '?' + new URLSearchParams(window.location.search))
} }
else {
navigate("item/" + item.id + "?" + new URLSearchParams(window.location.search))
}
} }
}><div className='tw-flex tw-flex-row'> }><div className='tw-flex tw-flex-row'>
<img src={item.layer?.menuIcon} className="tw-text-current tw-w-5 tw-mr-2 tw-mt-0" /> <img src={item.layer?.menuIcon} className="tw-text-current tw-w-5 tw-mr-2 tw-mt-0" />
@ -147,11 +138,11 @@ export const SearchControl = () => {
{Array.from(geoResults).length > 0 && (itemsResults.length > 0 || tagsResults.length > 0) && <hr className='tw-opacity-50'></hr>} {Array.from(geoResults).length > 0 && (itemsResults.length > 0 || tagsResults.length > 0) && <hr className='tw-opacity-50'></hr>}
{Array.from(geoResults).map((geo) => ( {Array.from(geoResults).map((geo) => (
<div className='tw-flex tw-flex-row hover:tw-font-bold tw-cursor-pointer' key={Math.random()} onClick={() => { <div className='tw-flex tw-flex-row hover:tw-font-bold tw-cursor-pointer' key={Math.random()} onClick={() => {
searchInput.current?.blur(); searchInput.current?.blur()
L.marker(new LatLng(geo.geometry.coordinates[1], geo.geometry.coordinates[0]), { icon: MarkerIconFactory("circle", "#777", "RGBA(35, 31, 32, 0.2)", "point") }).addTo(map).bindPopup(`<h3 class="tw-text-base tw-font-bold">${geo?.properties.name ? geo?.properties.name : value}<h3>${capitalizeFirstLetter(geo?.properties?.osm_value)}`).openPopup().addEventListener("popupclose", (e) => { console.log(e.target.remove()) }); L.marker(new LatLng(geo.geometry.coordinates[1], geo.geometry.coordinates[0]), { icon: MarkerIconFactory('circle', '#777', 'RGBA(35, 31, 32, 0.2)', 'point') }).addTo(map).bindPopup(`<h3 class="tw-text-base tw-font-bold">${geo?.properties.name ? geo?.properties.name : value}<h3>${capitalizeFirstLetter(geo?.properties?.osm_value)}`).openPopup().addEventListener('popupclose', (e) => { console.log(e.target.remove()) })
if (geo.properties.extent) map.fitBounds(new LatLngBounds(new LatLng(geo.properties.extent[1], geo.properties.extent[0]), new LatLng(geo.properties.extent[3], geo.properties.extent[2]))); if (geo.properties.extent) map.fitBounds(new LatLngBounds(new LatLng(geo.properties.extent[1], geo.properties.extent[0]), new LatLng(geo.properties.extent[3], geo.properties.extent[2])))
else map.setView(new LatLng(geo.geometry.coordinates[1], geo.geometry.coordinates[0]), 15, { duration: 1 }); else map.setView(new LatLng(geo.geometry.coordinates[1], geo.geometry.coordinates[0]), 15, { duration: 1 })
hide(); hide()
}}> }}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="tw-text-current tw-mr-2 tw-mt-0 tw-w-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="tw-text-current tw-mr-2 tw-mt-0 tw-w-4">
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> <path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
@ -159,14 +150,14 @@ export const SearchControl = () => {
<div> <div>
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties.name ? geo?.properties.name : value}</div> <div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties.name ? geo?.properties.name : value}</div>
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties?.city && `${capitalizeFirstLetter(geo?.properties?.city)}, `} {geo?.properties?.osm_value && geo?.properties?.osm_value !== "yes" && geo?.properties?.osm_value !== "primary" && geo?.properties?.osm_value !== "path" && geo?.properties?.osm_value !== "secondary" && geo?.properties?.osm_value !== "residential" && geo?.properties?.osm_value !== "unclassified" && `${capitalizeFirstLetter(geo?.properties?.osm_value)}, `} {geo.properties.state && `${geo.properties.state}, `} {geo.properties.country && geo.properties.country}</div> <div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties?.city && `${capitalizeFirstLetter(geo?.properties?.city)}, `} {geo?.properties?.osm_value && geo?.properties?.osm_value !== 'yes' && geo?.properties?.osm_value !== 'primary' && geo?.properties?.osm_value !== 'path' && geo?.properties?.osm_value !== 'secondary' && geo?.properties?.osm_value !== 'residential' && geo?.properties?.osm_value !== 'unclassified' && `${capitalizeFirstLetter(geo?.properties?.osm_value)}, `} {geo.properties.state && `${geo.properties.state}, `} {geo.properties.country && geo.properties.country}</div>
</div> </div>
</div> </div>
))} ))}
{isGeoCoordinate(value) && {isGeoCoordinate(value) &&
<div className='tw-flex tw-flex-row hover:tw-font-bold tw-cursor-pointer' onClick={() => { <div className='tw-flex tw-flex-row hover:tw-font-bold tw-cursor-pointer' onClick={() => {
L.marker(new LatLng(extractCoordinates(value)![0], extractCoordinates(value)![1]), { icon: MarkerIconFactory("circle", "#777", "RGBA(35, 31, 32, 0.2)", "point") }).addTo(map).bindPopup(`<h3 class="tw-text-base tw-font-bold">${extractCoordinates(value)![0]}, ${extractCoordinates(value)![1]}</h3>`).openPopup().addEventListener("popupclose", (e) => { console.log(e.target.remove()) }); L.marker(new LatLng(extractCoordinates(value)![0], extractCoordinates(value)![1]), { icon: MarkerIconFactory('circle', '#777', 'RGBA(35, 31, 32, 0.2)', 'point') }).addTo(map).bindPopup(`<h3 class="tw-text-base tw-font-bold">${extractCoordinates(value)![0]}, ${extractCoordinates(value)![1]}</h3>`).openPopup().addEventListener('popupclose', (e) => { console.log(e.target.remove()) })
map.setView(new LatLng(extractCoordinates(value)![0], extractCoordinates(value)![1]), 15, { duration: 1 }) map.setView(new LatLng(extractCoordinates(value)![0], extractCoordinates(value)![1]), 15, { duration: 1 })
}}> }}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="tw-text-current tw-mr-2 tw-mt-0 tw-w-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="tw-text-current tw-mr-2 tw-mt-0 tw-w-4">
@ -175,7 +166,7 @@ export const SearchControl = () => {
<div> <div>
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{value}</div> <div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{value}</div>
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{"Coordiante"}</div> <div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{'Coordiante'}</div>
</div> </div>
</div> </div>
} }
@ -188,22 +179,22 @@ export const SearchControl = () => {
} }
function isGeoCoordinate (input) { function isGeoCoordinate (input) {
const geokoordinatenRegex = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/; const geokoordinatenRegex = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/
return geokoordinatenRegex.test(input); return geokoordinatenRegex.test(input)
} }
function extractCoordinates (input): number[] | null { function extractCoordinates (input): number[] | null {
const result = input.split(",") const result = input.split(',')
if (result) { if (result) {
const latitude = parseFloat(result[0]); const latitude = parseFloat(result[0])
const longitude = parseFloat(result[1]); const longitude = parseFloat(result[1])
if (!isNaN(latitude) && !isNaN(longitude)) { if (!isNaN(latitude) && !isNaN(longitude)) {
return [latitude, longitude]; return [latitude, longitude]
} }
} }
return null; // Invalid input or error return null // Invalid input or error
} }
function capitalizeFirstLetter (string) { function capitalizeFirstLetter (string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1)
} }

View File

@ -1,10 +1,5 @@
// Converts leaflet.locatecontrol to a React Component // Converts leaflet.locatecontrol to a React Component
export const SidebarControl = () => { export const SidebarControl = () => {
return (<> return (<>
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-items-center tw-justify-center hover:tw-bg-slate-300 hover:tw-cursor-pointer tw-transition-all tw-duration-300 tw-mr-2 tw-h-12 tw-w-12 "> <div className="tw-card tw-bg-base-100 tw-shadow-xl tw-items-center tw-justify-center hover:tw-bg-slate-300 hover:tw-cursor-pointer tw-transition-all tw-duration-300 tw-mr-2 tw-h-12 tw-w-12 ">
@ -19,7 +14,4 @@ export const SidebarControl = () => {
</div> </div>
</div></>) </div></>)
} }

View File

@ -1,11 +1,10 @@
import * as React from 'react' import * as React from 'react'
import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter'; import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter'
import { decodeTag } from '../../../../Utils/FormatTags'; import { decodeTag } from '../../../../Utils/FormatTags'
export const TagsControl = () => { export const TagsControl = () => {
const filterTags = useFilterTags()
const filterTags = useFilterTags(); const removeFilterTag = useRemoveFilterTag()
const removeFilterTag = useRemoveFilterTag();
return ( return (
<div className='tw-flex tw-flex-wrap tw-mt-4 tw-w-[calc(100vw-2rem)] tw-max-w-xs'> <div className='tw-flex tw-flex-wrap tw-mt-4 tw-w-[calc(100vw-2rem)] tw-max-w-xs'>
@ -20,5 +19,3 @@ export const TagsControl = () => {
} }
</div>) </div>)
} }

View File

@ -22,100 +22,94 @@ export interface ItemFormPopupProps {
} }
export function ItemFormPopup (props: ItemFormPopupProps) { export function ItemFormPopup (props: ItemFormPopupProps) {
const [spinner, setSpinner] = useState(false)
const [spinner, setSpinner] = useState(false);
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const [popupTitle, setPopupTitle] = useState<string>(""); const [popupTitle, setPopupTitle] = useState<string>('')
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null)
const map = useMap(); const map = useMap()
const addItem = useAddItem(); const addItem = useAddItem()
const updateItem = useUpdateItem(); const updateItem = useUpdateItem()
const items = useItems(); const items = useItems()
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const removeItem = useRemoveItem(); const removeItem = useRemoveItem()
const tags = useTags()
const addTag = useAddTag()
const tags = useTags(); const resetFilterTags = useResetFilterTags()
const addTag = useAddTag();
const resetFilterTags = useResetFilterTags();
const { user } = useAuth();
const { user } = useAuth()
const handleSubmit = async (evt: any) => { const handleSubmit = async (evt: any) => {
const formItem: Item = {} as Item; const formItem: Item = {} as Item
Array.from(evt.target).forEach((input: HTMLInputElement) => { Array.from(evt.target).forEach((input: HTMLInputElement) => {
if (input.name) { if (input.name) {
formItem[input.name] = input.value; formItem[input.name] = input.value
} }
}); })
formItem['position'] = new Geometry(props.position.lng, props.position.lat); formItem.position = new Geometry(props.position.lng, props.position.lat)
evt.preventDefault(); evt.preventDefault()
setSpinner(true); setSpinner(true)
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => { formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() }) addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
} }
}); return null
})
if (props.item) { if (props.item) {
let success = false; let success = false
try { try {
await props.layer.api?.updateItem!({...formItem, id: props.item.id}); await props.layer.api?.updateItem!({ ...formItem, id: props.item.id })
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
console.log(props.item); console.log(props.item)
updateItem({...props.item, ...formItem}); updateItem({ ...props.item, ...formItem })
toast.success("Item updated"); toast.success('Item updated')
} }
setSpinner(false); setSpinner(false)
map.closePopup(); map.closePopup()
} } else {
else { const item = items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name === props.layer.itemType.name)
const item = items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name === props.layer.itemType.name);
const uuid = crypto.randomUUID(); const uuid = crypto.randomUUID()
let success = false; let success = false
try { try {
props.layer.onlyOnePerOwner && item && await props.layer.api?.updateItem!({ ...formItem, id: item?.id }); props.layer.onlyOnePerOwner && item && await props.layer.api?.updateItem!({ ...formItem, id: item?.id });
(!props.layer.onlyOnePerOwner || !item) && await props.layer.api?.createItem!({...formItem, id: uuid, name: formItem.name ? formItem.name : user?.first_name }); (!props.layer.onlyOnePerOwner || !item) && await props.layer.api?.createItem!({ ...formItem, id: uuid, name: formItem.name ? formItem.name : user?.first_name })
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
props.layer.onlyOnePerOwner && item && updateItem({ ...item, ...formItem }); props.layer.onlyOnePerOwner && item && updateItem({ ...item, ...formItem });
(!props.layer.onlyOnePerOwner || !item) && addItem({...formItem, name: formItem.name ? formItem.name : user?.first_name , user_created: user, type: props.layer.itemType, id: uuid, layer: props.layer, public_edit: !user ? true : false}); (!props.layer.onlyOnePerOwner || !item) && addItem({ ...formItem, name: formItem.name ? formItem.name : user?.first_name, user_created: user, type: props.layer.itemType, id: uuid, layer: props.layer, public_edit: !user })
toast.success("New item created"); toast.success('New item created')
resetFilterTags(); resetFilterTags()
} }
setSpinner(false); setSpinner(false)
map.closePopup(); map.closePopup()
} }
props.setItemFormPopup!(null); props.setItemFormPopup!(null)
} }
const resetPopup = () => { const resetPopup = () => {
if (formRef.current) { if (formRef.current) {
formRef.current.reset(); formRef.current.reset()
} }
} }
useEffect(() => { useEffect(() => {
resetPopup(); resetPopup()
}, [props.position]) }, [props.position])
return ( return (
@ -124,27 +118,27 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
remove: () => { remove: () => {
setTimeout(function () { setTimeout(function () {
resetPopup() resetPopup()
}, 100); }, 100)
} }
}} }}
position={props.position}> position={props.position}>
<form ref={formRef} onReset={resetPopup} autoComplete='off' onSubmit={e => handleSubmit(e)}> <form ref={formRef} onReset={resetPopup} autoComplete='off' onSubmit={e => handleSubmit(e)}>
{props.item ? <div className='tw-h-3'></div> {props.item
: ? <div className='tw-h-3'></div>
<div className='tw-flex tw-justify-center'><b className="tw-text-xl tw-text-center tw-font-bold">{ props.layer.menuText}</b></div> : <div className='tw-flex tw-justify-center'><b className="tw-text-xl tw-text-center tw-font-bold">{ props.layer.menuText}</b></div>
} }
{props.children ? {props.children
React.Children.toArray(props.children).map((child) => ? React.Children.toArray(props.children).map((child) =>
React.isValidElement<{ item: Item, test: string, setPopupTitle: React.Dispatch<React.SetStateAction<string>> }>(child) ? React.isValidElement<{ item: Item, test: string, setPopupTitle: React.Dispatch<React.SetStateAction<string>> }>(child)
React.cloneElement(child, { item: props.item, key: props.position.toString(), setPopupTitle: setPopupTitle }) : "" ? React.cloneElement(child, { item: props.item, key: props.position.toString(), setPopupTitle })
: ''
) )
: : <>
<> <TextInput type="text" placeholder="Name" dataField="name" defaultValue={props.item ? props.item.name : ''} inputStyle='' />
<TextInput type="text" placeholder="Name" dataField="name" defaultValue={props.item ? props.item.name : ""} inputStyle='' /> <TextAreaInput key={props.position.toString()} placeholder="Text" dataField="text" defaultValue={props.item ? props.item.text : ''} inputStyle='tw-h-40 tw-mt-5' />
<TextAreaInput key={props.position.toString()} placeholder="Text" dataField="text" defaultValue={props.item ? props.item.text : ""} inputStyle='tw-h-40 tw-mt-5' />
</> </>
} }
@ -155,6 +149,3 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
</LeafletPopup> </LeafletPopup>
) )
} }

View File

@ -1,10 +1,10 @@
import * as React from "react" import * as React from 'react'
import { Item, ItemsApi } from "../../../../types"; import { Item, ItemsApi } from '../../../../types'
import { useHasUserPermission } from "../../hooks/usePermissions"; import { useHasUserPermission } from '../../hooks/usePermissions'
import { getValue } from "../../../../Utils/GetValue"; import { getValue } from '../../../../Utils/GetValue'
import { useAssetApi } from '../../../AppShell/hooks/useAssets' import { useAssetApi } from '../../../AppShell/hooks/useAssets'
import DialogModal from "../../../Templates/DialogModal"; import DialogModal from '../../../Templates/DialogModal'
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom'
export function HeaderView ({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemSubnameField, itemAvatarField, loading, hideMenu = false, big = false, truncateSubname = true, hideSubname = false, showAddress = false }: { export function HeaderView ({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemSubnameField, itemAvatarField, loading, hideMenu = false, big = false, truncateSubname = true, hideSubname = false, showAddress = false }: {
item: Item, item: Item,
@ -22,63 +22,58 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
truncateSubname?: boolean, truncateSubname?: boolean,
showAddress?: boolean showAddress?: boolean
}) { }) {
const [modalOpen, setModalOpen] = React.useState<boolean>(false)
const hasUserPermission = useHasUserPermission()
const navigate = useNavigate()
const assetsApi = useAssetApi()
const [modalOpen, setModalOpen] = React.useState<boolean>(false); const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}` : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && 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 hasUserPermission = useHasUserPermission(); const [address] = React.useState<string>('')
const navigate = useNavigate();
const assetsApi = useAssetApi();
const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) + `${big ? "?width=160&heigth=160" : "?width=80&heigth=80"}` : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && 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, /* setAdress*/] = React.useState<string>("");
const params = new URLSearchParams(window.location.search);
const params = new URLSearchParams(window.location.search)
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => { const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
setModalOpen(true); setModalOpen(true)
event.stopPropagation(); event.stopPropagation()
} }
return ( return (
<> <>
<div className='tw-flex tw-flex-row'> <div className='tw-flex tw-flex-row'>
<div className={`tw-grow tw-max-w-[calc(100%-60px)] }`}> <div className={'tw-grow tw-max-w-[calc(100%-60px)] }'}>
<div className="flex items-center"> <div className="flex items-center">
{avatar && ( {avatar && (
<div className="tw-avatar"> <div className="tw-avatar">
<div className={`${big ? "tw-w-20" : "tw-w-10"} tw-inline tw-items-center tw-justify-center overflow-hidden`}> <div className={`${big ? 'tw-w-20' : 'tw-w-10'} tw-inline tw-items-center tw-justify-center overflow-hidden`}>
<img <img
className={`tw-w-full tw-h-full tw-object-cover tw-rounded-full`} className={'tw-w-full tw-h-full tw-object-cover tw-rounded-full'}
src={avatar} src={avatar}
alt={item.name + " logo"} alt={item.name + ' logo'}
/> />
</div> </div>
</div> </div>
)} )}
<div className={`${avatar ? "tw-ml-2" : ""} tw-overflow-hidden`}> <div className={`${avatar ? 'tw-ml-2' : ''} tw-overflow-hidden`}>
<div className={`${big ? "xl:tw-text-3xl tw-text-2xl" : "tw-text-xl"} tw-font-semibold tw-truncate`}> <div className={`${big ? 'xl:tw-text-3xl tw-text-2xl' : 'tw-text-xl'} tw-font-semibold tw-truncate`}>
{title} {title}
</div> </div>
{showAddress && address && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && "tw-truncate"}`}> {showAddress && address && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
{address} {address}
</div>} </div>}
{subtitle && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && "tw-truncate"}`}> {subtitle && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
{subtitle} {subtitle}
</div>} </div>}
</div> </div>
</div> </div>
</div> </div>
<div onClick={(e) => e.stopPropagation()} className={`${big ? "tw-mt-5" : "tw-mt-1"}`}> <div onClick={(e) => e.stopPropagation()} className={`${big ? 'tw-mt-5' : 'tw-mt-1'}`}>
{(api?.deleteItem || item.layer?.api?.updateItem) {(api?.deleteItem || item.layer?.api?.updateItem) &&
&& (hasUserPermission(api?.collectionName!, "delete", item) || hasUserPermission(api?.collectionName!, "update", item)) (hasUserPermission(api?.collectionName!, 'delete', item) || hasUserPermission(api?.collectionName!, 'update', item)) &&
&& !hideMenu && !hideMenu &&
<div className="tw-dropdown tw-dropdown-bottom"> <div className="tw-dropdown tw-dropdown-bottom">
<label tabIndex={0} className="tw-bg-base-100 tw-btn tw-m-1 tw-leading-3 tw-border-none tw-min-h-0 tw-h-6"> <label tabIndex={0} className="tw-bg-base-100 tw-btn tw-m-1 tw-leading-3 tw-border-none tw-min-h-0 tw-h-6">
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
@ -86,25 +81,25 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
</svg> </svg>
</label> </label>
<ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000"> <ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000">
{((api?.updateItem && hasUserPermission(api.collectionName!, "update", item))) && editCallback && <li> {((api?.updateItem && hasUserPermission(api.collectionName!, 'update', item))) && editCallback && <li>
<a className="!tw-text-base-content tw-cursor-pointer" onClick={(e) => item.layer?.customEditLink ? navigate(`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && "?"+params}` : ""} `) : editCallback(e)}> <a className="!tw-text-base-content tw-cursor-pointer" onClick={(e) => item.layer?.customEditLink ? navigate(`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && '?' + params}` : ''} `) : editCallback(e)}>
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" /> <path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg> </svg>
</a> </a>
</li>} </li>}
{((api?.updateItem && hasUserPermission(api.collectionName!, "update", item))) && setPositionCallback && <li> {((api?.updateItem && hasUserPermission(api.collectionName!, 'update', item))) && setPositionCallback && <li>
<a className="!tw-text-base-content tw-cursor-pointer" onClick={setPositionCallback}> <a className="!tw-text-base-content tw-cursor-pointer" onClick={setPositionCallback}>
<svg stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" className="tw-w-5 tw-h-5" xmlns="http://www.w3.org/2000/svg"> <svg stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" className="tw-w-5 tw-h-5" xmlns="http://www.w3.org/2000/svg">
<path d="M256 0c17.7 0 32 14.3 32 32V42.4c93.7 13.9 167.7 88 181.6 181.6H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H469.6c-13.9 93.7-88 167.7-181.6 181.6V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V469.6C130.3 455.7 56.3 381.7 42.4 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H42.4C56.3 130.3 130.3 56.3 224 42.4V32c0-17.7 14.3-32 32-32zM107.4 288c12.5 58.3 58.4 104.1 116.6 116.6V384c0-17.7 14.3-32 32-32s32 14.3 32 32v20.6c58.3-12.5 104.1-58.4 116.6-116.6H384c-17.7 0-32-14.3-32-32s14.3-32 32-32h20.6C392.1 165.7 346.3 119.9 288 107.4V128c0 17.7-14.3 32-32 32s-32-14.3-32-32V107.4C165.7 119.9 119.9 165.7 107.4 224H128c17.7 0 32 14.3 32 32s-14.3 32-32 32H107.4zM256 224a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"></path> <path d="M256 0c17.7 0 32 14.3 32 32V42.4c93.7 13.9 167.7 88 181.6 181.6H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H469.6c-13.9 93.7-88 167.7-181.6 181.6V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V469.6C130.3 455.7 56.3 381.7 42.4 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H42.4C56.3 130.3 130.3 56.3 224 42.4V32c0-17.7 14.3-32 32-32zM107.4 288c12.5 58.3 58.4 104.1 116.6 116.6V384c0-17.7 14.3-32 32-32s32 14.3 32 32v20.6c58.3-12.5 104.1-58.4 116.6-116.6H384c-17.7 0-32-14.3-32-32s14.3-32 32-32h20.6C392.1 165.7 346.3 119.9 288 107.4V128c0 17.7-14.3 32-32 32s-32-14.3-32-32V107.4C165.7 119.9 119.9 165.7 107.4 224H128c17.7 0 32 14.3 32 32s-14.3 32-32 32H107.4zM256 224a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"></path>
</svg> </svg>
</a> </a>
</li>} </li>}
{api?.deleteItem && hasUserPermission(api.collectionName!, "delete", item) && deleteCallback && <li> {api?.deleteItem && hasUserPermission(api.collectionName!, 'delete', item) && deleteCallback && <li>
<a className='tw-cursor-pointer !tw-text-error' onClick={openDeleteModal}> <a className='tw-cursor-pointer !tw-text-error' onClick={openDeleteModal}>
{loading ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span> {loading
: ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor"> : <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" /> <path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>} </svg>}
</a> </a>
@ -119,7 +114,7 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
<span>Do you want to delete <b>{item.name}</b>?</span> <span>Do you want to delete <b>{item.name}</b>?</span>
<div className="tw-grid"> <div className="tw-grid">
<div className="tw-flex tw-justify-between" > <div className="tw-flex tw-justify-between" >
<label className="tw-btn tw-mt-4 tw-btn-error" onClick={(e) => { deleteCallback(e); setModalOpen(false); }}>Yes</label> <label className="tw-btn tw-mt-4 tw-btn-error" onClick={(e) => { deleteCallback(e); setModalOpen(false) }}>Yes</label>
<label className="tw-btn tw-mt-4" onClick={() => setModalOpen(false)}>No</label> <label className="tw-btn tw-mt-4" onClick={() => setModalOpen(false)}>No</label>
</div> </div>
</div> </div>

View File

@ -1,19 +1,14 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { getValue } from '../../../../Utils/GetValue' import { getValue } from '../../../../Utils/GetValue'
import { Item } from '../../../../types' import { Item } from '../../../../types'
import { useGetItemTags } from '../../hooks/useTags'; import { useGetItemTags } from '../../hooks/useTags'
export const PopupButton = ({ url, parameterField, text, colorField, item } : {url: string, parameterField?: string, text: string, colorField?: string, item? : Item}) => { 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 params = new URLSearchParams(window.location.search); const getItemTags = useGetItemTags()
const getItemTags = useGetItemTags();
return ( return (
<Link to={`${url}/${parameterField? getValue(item,parameterField):``}?${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)}`}} className="tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1">{text}</button></Link> <Link to={`${url}/${parameterField ? getValue(item, parameterField) : ''}?${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)}` }} className="tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1">{text}</button></Link>
) )
} }

View File

@ -6,7 +6,6 @@ export const PopupCheckboxInput = ({ dataField, label, item }:
label: string, label: string,
item?: Item item?: Item
}) => { }) => {
return ( return (
<label htmlFor={item?.id} className="tw-label tw-justify-normal tw-pt-1 tw-pb-1"><input id={item?.id} type="checkbox" name={dataField} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={item?.public_edit} /><span className='tw-text-sm tw-label-text tw-mx-2 tw-cursor-pointer'>{label}</span></label> <label htmlFor={item?.id} className="tw-label tw-justify-normal tw-pt-1 tw-pb-1"><input id={item?.id} type="checkbox" name={dataField} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={item?.public_edit} /><span className='tw-text-sm tw-label-text tw-mx-2 tw-cursor-pointer'>{label}</span></label>
) )

View File

@ -14,8 +14,8 @@ type StartEndInputProps = {
export const PopupStartEndInput = ({ item, showLabels = true, updateStartValue, updateEndValue }:StartEndInputProps) => { export const PopupStartEndInput = ({ item, showLabels = true, updateStartValue, updateEndValue }:StartEndInputProps) => {
return ( return (
<div className='tw-grid tw-grid-cols-2 tw-gap-2 tw-mb-5'> <div className='tw-grid tw-grid-cols-2 tw-gap-2 tw-mb-5'>
<TextInput type='date' placeholder='start' dataField='start' inputStyle='tw-text-sm tw-px-2' labelTitle={showLabels ? "start" :""} defaultValue={item && item.start? item.start.substring(0, 10) : ""} autocomplete='one-time-code' updateFormValue={updateStartValue}></TextInput> <TextInput type='date' placeholder='start' dataField='start' inputStyle='tw-text-sm tw-px-2' labelTitle={showLabels ? 'start' : ''} defaultValue={item && item.start ? item.start.substring(0, 10) : ''} autocomplete='one-time-code' updateFormValue={updateStartValue}></TextInput>
<TextInput type='date' placeholder='end' dataField='end' inputStyle='tw-text-sm tw-px-2' labelTitle={showLabels ? "end" :""} defaultValue={item && item.end ? item.end.substring(0, 10) : ""} autocomplete='one-time-code' updateFormValue={updateEndValue}></TextInput> <TextInput type='date' placeholder='end' dataField='end' inputStyle='tw-text-sm tw-px-2' labelTitle={showLabels ? 'end' : ''} defaultValue={item && item.end ? item.end.substring(0, 10) : ''} autocomplete='one-time-code' updateFormValue={updateEndValue}></TextInput>
</div> </div>
) )
} }

View File

@ -9,8 +9,7 @@ export const PopupTextAreaInput = ({ dataField, placeholder, style, item }:
style?: string, style?: string,
item?: Item item?: Item
}) => { }) => {
return ( return (
<TextAreaInput defaultValue={item?.text ? item.text : ""} dataField={dataField} placeholder={placeholder} inputStyle={style}></TextAreaInput> <TextAreaInput defaultValue={item?.text ? item.text : ''} dataField={dataField} placeholder={placeholder} inputStyle={style}></TextAreaInput>
) )
} }

View File

@ -9,8 +9,7 @@ export const PopupTextInput = ({ dataField, placeholder, style, item }:
style?: string, style?: string,
item?: Item item?: Item
}) => { }) => {
return ( return (
<TextInput defaultValue={item?.name ? item.name : ""} dataField={dataField} placeholder={placeholder} inputStyle={style} type='text' containerStyle={'tw-mt-4 tw-mb-4'}></TextInput> <TextInput defaultValue={item?.name ? item.name : ''} dataField={dataField} placeholder={placeholder} inputStyle={style} type='text' containerStyle={'tw-mt-4 tw-mb-4'}></TextInput>
) )
} }

View File

@ -8,7 +8,7 @@ export const StartEndView = ({item} : {item?:Item}) => {
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-4 tw-w-4 tw-mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-4 tw-w-4 tw-mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg> </svg>
<time className='tw-align-middle' dateTime={item && item.start? item.start.substring(0, 10) : ""}>{item && item.start? new Date(item.start).toLocaleDateString(): ""}</time> <time className='tw-align-middle' dateTime={item && item.start ? item.start.substring(0, 10) : ''}>{item && item.start ? new Date(item.start).toLocaleDateString() : ''}</time>
</div> </div>
<div className="tw-basis-1/5 tw-place-content-center"> <div className="tw-basis-1/5 tw-place-content-center">
<span>-</span> <span>-</span>
@ -17,9 +17,8 @@ export const StartEndView = ({item} : {item?:Item}) => {
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-4 tw-w-4 tw-mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-4 tw-w-4 tw-mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg> </svg>
<time className='tw-align-middle' dateTime={item && item.end? item.end.substring(0, 10) : ""}>{item && item.end ? new Date(item.end).toLocaleDateString() : ""}</time> <time className='tw-align-middle' dateTime={item && item.end ? item.end.substring(0, 10) : ''}>{item && item.end ? new Date(item.end).toLocaleDateString() : ''}</time>
</div> </div>
</div> </div>
) )
} }

View File

@ -1,95 +1,95 @@
import { Item } from '../../../../types'; import { Item } from '../../../../types'
import { useTags } from '../../hooks/useTags'; import { useTags } from '../../hooks/useTags'
import { useAddFilterTag } from '../../hooks/useFilter'; import { useAddFilterTag } from '../../hooks/useFilter'
import { hashTagRegex } from '../../../../Utils/HashTagRegex'; import { hashTagRegex } from '../../../../Utils/HashTagRegex'
import { fixUrls, mailRegex } from '../../../../Utils/ReplaceURLs'; import { fixUrls, mailRegex } from '../../../../Utils/ReplaceURLs'
import Markdown from 'react-markdown'; import Markdown from 'react-markdown'
import { getValue } from '../../../../Utils/GetValue'; import { getValue } from '../../../../Utils/GetValue'
import remarkBreaks from 'remark-breaks'; import remarkBreaks from 'remark-breaks'
import { decodeTag } from '../../../../Utils/FormatTags'; import { decodeTag } from '../../../../Utils/FormatTags'
import { memo } from 'react'; import { memo } from 'react'
export const TextView = ({ item, truncate = false, itemTextField, rawText }: { item?: Item, truncate?: boolean, itemTextField?: string, rawText?: string }) => { export const TextView = ({ item, truncate = false, itemTextField, rawText }: { item?: Item, truncate?: boolean, itemTextField?: string, rawText?: string }) => {
const tags = useTags(); const tags = useTags()
const addFilterTag = useAddFilterTag(); const addFilterTag = useAddFilterTag()
let text = ""; let text = ''
let replacedText = ""; let replacedText = ''
if (rawText) if (rawText) { text = replacedText = rawText } else if (itemTextField && item) { text = getValue(item, itemTextField) } else { text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : '' }
text = replacedText = rawText;
else if (itemTextField && item)
text = getValue(item, itemTextField);
else
text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : "";
if (item && text && truncate) text = truncateText(removeMarkdownKeepLinksAndParagraphs(text), 100); if (item && text && truncate) text = truncateText(removeMarkdownKeepLinksAndParagraphs(text), 100)
if (item && text) replacedText = fixUrls(text)
item && text ? replacedText = fixUrls(text) : "";
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
replacedText ? replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s\)]+(?!\))/g, (url) => { if (replacedText) {
let shortUrl = url; replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
let shortUrl = url
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
if (url.match('^https:\/\/')) { if (url.match('^https:\/\/')) {
shortUrl = url.split('https://')[1]; shortUrl = url.split('https://')[1]
} }
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
if (url.match('^http:\/\/')) { if (url.match('^http:\/\/')) {
shortUrl = url.split('http://')[1]; shortUrl = url.split('http://')[1]
}
return `[${shortUrl}](${url})`
})
} }
return `[${shortUrl}](${url})`;
}) : "";
replacedText ? replacedText = replacedText.replace(mailRegex, (url) => { if (replacedText) {
return `[${url}](mailto:${url})`; replacedText = replacedText.replace(mailRegex, (url) => {
}) : ""; return `[${url}](mailto:${url})`
})
}
replacedText ? replacedText = replacedText.replace(hashTagRegex, (match) => { if (replacedText) {
return `[${match}](${match})`; replacedText = replacedText.replace(hashTagRegex, (match) => {
}) : ""; return `[${match}](${match})`
})
}
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomH1 = ({ children }) => ( const CustomH1 = ({ children }) => (
<h1 className="tw-text-xl tw-font-bold">{children}</h1> <h1 className="tw-text-xl tw-font-bold">{children}</h1>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomH2 = ({ children }) => ( const CustomH2 = ({ children }) => (
<h2 className="tw-text-lg tw-font-bold">{children}</h2> <h2 className="tw-text-lg tw-font-bold">{children}</h2>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomH3 = ({ children }) => ( const CustomH3 = ({ children }) => (
<h3 className="tw-text-base tw-font-bold">{children}</h3> <h3 className="tw-text-base tw-font-bold">{children}</h3>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomH4 = ({ children }) => ( const CustomH4 = ({ children }) => (
<h4 className="tw-text-base tw-font-bold">{children}</h4> <h4 className="tw-text-base tw-font-bold">{children}</h4>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomH5 = ({ children }) => ( const CustomH5 = ({ children }) => (
<h5 className="tw-text-sm tw-font-bold">{children}</h5> <h5 className="tw-text-sm tw-font-bold">{children}</h5>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomH6 = ({ children }) => ( const CustomH6 = ({ children }) => (
<h6 className="tw-text-sm tw-font-bold">{children}</h6> <h6 className="tw-text-sm tw-font-bold">{children}</h6>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomParagraph = ({ children }) => ( const CustomParagraph = ({ children }) => (
<p className="!tw-my-2">{children}</p> <p className="!tw-my-2">{children}</p>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomUnorderdList = ({ children }) => ( const CustomUnorderdList = ({ children }) => (
<ul className="tw-list-disc tw-list-inside">{children}</ul> <ul className="tw-list-disc tw-list-inside">{children}</ul>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomOrderdList = ({ children }) => ( const CustomOrderdList = ({ children }) => (
<ol className="tw-list-decimal tw-list-inside">{children}</ol> <ol className="tw-list-decimal tw-list-inside">{children}</ol>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomHorizontalRow = ({ children }) => ( const CustomHorizontalRow = ({ children }) => (
<hr className="tw-border-current">{children}</hr> <hr className="tw-border-current">{children}</hr>
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomImage = ({ alt, src, title }) => ( const CustomImage = ({ alt, src, title }) => (
<img <img
@ -98,14 +98,14 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
alt={alt} alt={alt}
title={title} title={title}
/> />
); )
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const CustomExternalLink = ({ href, children }) => ( const CustomExternalLink = ({ href, children }) => (
<a className='tw-font-bold tw-underline' <a className='tw-font-bold tw-underline'
href={href} href={href}
target='_blank' rel="noreferrer" target='_blank' rel="noreferrer"
> {children}</a> > {children}</a>
); )
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
const CustomHashTagLink = ({ children, tag, item }) => { const CustomHashTagLink = ({ children, tag, item }) => {
return ( return (
@ -113,11 +113,11 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
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 + item!.id : item.id}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation()
addFilterTag(tag!); addFilterTag(tag!)
}}>{decodeTag(children)}</a> }}>{decodeTag(children)}</a>
) )
}; }
/* eslint-enable react/prop-types */ /* eslint-enable react/prop-types */
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
@ -128,40 +128,39 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
allow="fullscreen; picture-in-picture" allow="fullscreen; picture-in-picture"
allowFullScreen allowFullScreen
/> />
)); ))
return ( return (
<Markdown className={`tw-text-map tw-leading-map tw-text-sm`} remarkPlugins={[remarkBreaks]} components={{ <Markdown className={'tw-text-map tw-leading-map tw-text-sm'} remarkPlugins={[remarkBreaks]} components={{
p: CustomParagraph, p: CustomParagraph,
a: ({ href, children }) => { a: ({ href, children }) => {
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v='); const isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v=')
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const isRumbleVideo = href?.startsWith('https://rumble.com/embed/'); const isRumbleVideo = href?.startsWith('https://rumble.com/embed/')
if (isYouTubeVideo) { if (isYouTubeVideo) {
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const videoId = href?.split('v=')[1].split('&')[0]; const videoId = href?.split('v=')[1].split('&')[0]
const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`; const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`
return ( return (
<MemoizedVideoEmbed url={youtubeEmbedUrl}></MemoizedVideoEmbed> <MemoizedVideoEmbed url={youtubeEmbedUrl}></MemoizedVideoEmbed>
); )
} }
if (isRumbleVideo) { if (isRumbleVideo) {
return ( return (
<MemoizedVideoEmbed url={href!}></MemoizedVideoEmbed> <MemoizedVideoEmbed url={href!}></MemoizedVideoEmbed>
); )
} }
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
if (href?.startsWith("#")) { if (href?.startsWith('#')) {
const tag = tags.find(t => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase()); const tag = tags.find(t => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase())
return <CustomHashTagLink tag={tag} item={item}>{children}</CustomHashTagLink>; return <CustomHashTagLink tag={tag} item={item}>{children}</CustomHashTagLink>
} else { } else {
return ( return (
<CustomExternalLink href={href}>{children}</CustomExternalLink> <CustomExternalLink href={href}>{children}</CustomExternalLink>
); )
} }
}, },
ul: CustomUnorderdList, ul: CustomUnorderdList,
@ -173,12 +172,12 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
h3: CustomH3, h3: CustomH3,
h4: CustomH4, h4: CustomH4,
h5: CustomH5, h5: CustomH5,
h6: CustomH6, h6: CustomH6
}}> }}>
{replacedText} {replacedText}
</Markdown> </Markdown>
); )
}; }
function removeMarkdownKeepLinksAndParagraphs (text) { function removeMarkdownKeepLinksAndParagraphs (text) {
// Remove Markdown syntax using regular expressions but keep links and paragraphs // Remove Markdown syntax using regular expressions but keep links and paragraphs
@ -189,29 +188,29 @@ function removeMarkdownKeepLinksAndParagraphs(text) {
.replace(/(#+)\s+(.*)/g, '$2') // Remove headers .replace(/(#+)\s+(.*)/g, '$2') // Remove headers
.replace(/>\s+(.*)/g, '$1') // Remove blockquotes .replace(/>\s+(.*)/g, '$1') // Remove blockquotes
.replace(/^\s*\n/gm, '\n') // Preserve empty lines .replace(/^\s*\n/gm, '\n') // Preserve empty lines
.replace(/(\r\n|\n|\r)/gm, '\n'); // Preserve line breaks .replace(/(\r\n|\n|\r)/gm, '\n') // Preserve line breaks
} }
function truncateText (text, limit) { function truncateText (text, limit) {
if (text.length <= limit) { if (text.length <= limit) {
return text; return text
} }
let truncated = ""; let truncated = ''
let length = 0; let length = 0
// Split the text by paragraphs // Split the text by paragraphs
const paragraphs = text.split('\n'); const paragraphs = text.split('\n')
for (const paragraph of paragraphs) { for (const paragraph of paragraphs) {
if (length + paragraph.length > limit) { if (length + paragraph.length > limit) {
truncated += paragraph.slice(0, limit - length) + '...'; truncated += paragraph.slice(0, limit - length) + '...'
break; break
} else { } else {
truncated += paragraph + '\n'; truncated += paragraph + '\n'
length += paragraph.length; length += paragraph.length
} }
} }
return truncated.trim(); return truncated.trim()
} }

View File

@ -12,85 +12,76 @@ import { useRemoveItem, useUpdateItem } from '../hooks/useItems'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useSetSelectPosition } from '../hooks/useSelectPosition' import { useSetSelectPosition } from '../hooks/useSelectPosition'
export interface ItemViewPopupProps { export interface ItemViewPopupProps {
item: Item, item: Item,
children?: React.ReactNode; children?: React.ReactNode;
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>> setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
} }
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: any) => { export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: any) => {
const map = useMap(); const map = useMap()
const [loading, setLoading] = React.useState<boolean>(false); const [loading, setLoading] = React.useState<boolean>(false)
const removeItem = useRemoveItem(); const removeItem = useRemoveItem()
const updadateItem = useUpdateItem(); const updadateItem = useUpdateItem()
const navigate = useNavigate(); const navigate = useNavigate()
const setSelectPosition = useSetSelectPosition(); const setSelectPosition = useSetSelectPosition()
const [infoExpanded, setInfoExpanded] = useState<boolean>(false)
const [infoExpanded, setInfoExpanded] = useState<boolean>(false);
const handleEdit = (event: React.MouseEvent<HTMLElement>) => { const handleEdit = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation(); event.stopPropagation()
map.closePopup(); map.closePopup()
props.setItemFormPopup && props.setItemFormPopup({ position: new LatLng(props.item.position?.coordinates[1]!, props.item.position?.coordinates[0]!), layer: props.item.layer!, item: props.item, setItemFormPopup: props.setItemFormPopup }) props.setItemFormPopup && props.setItemFormPopup({ position: new LatLng(props.item.position?.coordinates[1]!, props.item.position?.coordinates[0]!), layer: props.item.layer!, item: props.item, setItemFormPopup: props.setItemFormPopup })
} }
const handleDelete = async (event: React.MouseEvent<HTMLElement>) => { const handleDelete = async (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation(); event.stopPropagation()
setLoading(true); setLoading(true)
let success = false; let success = false
try { try {
!props.item.layer?.onlyOnePerOwner && await props.item.layer?.api?.deleteItem!(props.item.id); !props.item.layer?.onlyOnePerOwner && await props.item.layer?.api?.deleteItem!(props.item.id)
props.item.layer?.onlyOnePerOwner && await props.item.layer.api?.updateItem!({ id: props.item.id, position: null }) props.item.layer?.onlyOnePerOwner && await props.item.layer.api?.updateItem!({ id: props.item.id, position: null })
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
!props.item.layer?.onlyOnePerOwner && removeItem(props.item); !props.item.layer?.onlyOnePerOwner && removeItem(props.item)
props.item.layer?.onlyOnePerOwner && updadateItem({...props.item, position: undefined}); props.item.layer?.onlyOnePerOwner && updadateItem({ ...props.item, position: undefined })
toast.success("Item deleted"); toast.success('Item deleted')
} }
setLoading(false); setLoading(false)
map.closePopup(); map.closePopup()
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
window.history.pushState({}, "", "/" + `${params ? `?${params}` : ""}`); window.history.pushState({}, '', '/' + `${params ? `?${params}` : ''}`)
navigate("/"); navigate('/')
} }
return ( return (
<LeafletPopup ref={ref} maxHeight={377} minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}> <LeafletPopup ref={ref} maxHeight={377} minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}>
<div className='tw-bg-base-100 tw-text-base-content'> <div className='tw-bg-base-100 tw-text-base-content'>
<HeaderView api={props.item.layer?.api} item={props.item} editCallback={handleEdit} deleteCallback={handleDelete} setPositionCallback={()=>{map.closePopup();setSelectPosition(props.item); navigate("/")}} loading={loading}/> <HeaderView api={props.item.layer?.api} item={props.item} editCallback={handleEdit} deleteCallback={handleDelete} setPositionCallback={() => { map.closePopup(); setSelectPosition(props.item); navigate('/') }} 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'>
{props.children ? {props.children
React.Children.toArray(props.children).map((child) => ? React.Children.toArray(props.children).map((child) =>
React.isValidElement<{ item: Item, test: string }>(child) ? React.isValidElement<{ item: Item, test: string }>(child)
React.cloneElement(child, { item: props.item }) : "" ? React.cloneElement(child, { item: props.item })
: ''
) )
: : <TextView item={props.item} />
<TextView item={props.item} />
} }
</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'>
{ {
infoExpanded ? infoExpanded
<p className={`tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500`} >{`${props.item.date_updated && props.item.date_updated != props.item.date_created ? "updated" : "posted" } ${props.item && props.item.user_created && props.item.user_created.first_name ? `by ${props.item.user_created.first_name}` : ""} ${props.item.date_updated ? timeAgo(props.item.date_updated) : timeAgo(props.item.date_created!)}`}</p> ? <p className={'tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'} >{`${props.item.date_updated && props.item.date_updated !== props.item.date_created ? 'updated' : 'posted'} ${props.item && props.item.user_created && props.item.user_created.first_name ? `by ${props.item.user_created.first_name}` : ''} ${props.item.date_updated ? timeAgo(props.item.date_updated) : timeAgo(props.item.date_created!)}`}</p>
: : <p className="!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500" onClick={() => setInfoExpanded(true)}></p>
<p className="!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500" onClick={() => setInfoExpanded(true)}></p>
} }
<div className='tw-grow'></div> <div className='tw-grow'></div>
{ //* * <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="tw-place-self-end tw-w-4 tw-h-4 tw-mb-1 tw-cursor-pointer"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" /></svg> */ { //* * <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="tw-place-self-end tw-w-4 tw-h-4 tw-mb-1 tw-cursor-pointer"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" /></svg> */
@ -100,4 +91,3 @@ export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: a
</LeafletPopup> </LeafletPopup>
) )
}) })

View File

@ -1,4 +1,3 @@
export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItemPosition }) => { export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItemPosition }) => {
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">

View File

@ -1,43 +1,42 @@
import * as React from 'react' import * as React from 'react'
import { useEffect } from 'react'; import { useEffect } from 'react'
import { ItemsApi, Tag } from '../../types'; import { ItemsApi, Tag } from '../../types'
import { useSetTagData, useSetTagApi, useTags } from './hooks/useTags' import { useSetTagData, useSetTagApi, useTags } from './hooks/useTags'
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom'
import { useAddFilterTag, useFilterTags, useResetFilterTags } from './hooks/useFilter'; import { useAddFilterTag, useFilterTags, useResetFilterTags } from './hooks/useFilter'
export function Tags ({ data, api } : {data?: Tag[], api?: ItemsApi<Tag>}) { export function Tags ({ data, api } : {data?: Tag[], api?: ItemsApi<Tag>}) {
const setTagData = useSetTagData(); const setTagData = useSetTagData()
const setTagApi = useSetTagApi(); const setTagApi = useSetTagApi()
useEffect(() => { useEffect(() => {
data && setTagData(data); data && setTagData(data)
api && setTagApi(api); api && setTagApi(api)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, data]) }, [api, data])
const location = useLocation()
const location = useLocation(); const addFilterTag = useAddFilterTag()
const addFilterTag = useAddFilterTag(); const resetFilterTags = useResetFilterTags()
const resetFilterTags = useResetFilterTags(); const tags = useTags()
const tags = useTags();
const filterTags = useFilterTags() const filterTags = useFilterTags()
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const urlTags = params.get("tags") const urlTags = params.get('tags')
const decodedTags = urlTags ? decodeURIComponent(urlTags) : ""; const decodedTags = urlTags ? decodeURIComponent(urlTags) : ''
const decodedTagsArray = decodedTags.split(";"); const decodedTagsArray = decodedTags.split(';')
if(decodedTagsArray?.some(ut => !filterTags.find(ft => ut.toLocaleLowerCase() === ft.name.toLocaleLowerCase()))||filterTags?.some(ft => !decodedTagsArray?.find(ut => ut.toLocaleLowerCase() === ft.name.toLocaleLowerCase()))) if (decodedTagsArray?.some(ut => !filterTags.find(ft => ut.toLocaleLowerCase() === ft.name.toLocaleLowerCase())) || filterTags?.some(ft => !decodedTagsArray?.find(ut => ut.toLocaleLowerCase() === ft.name.toLocaleLowerCase()))) {
{resetFilterTags() resetFilterTags()
decodedTagsArray?.map(urlTag => { decodedTagsArray?.map(urlTag => {
const tag = tags.find(t => t.name.toLocaleLowerCase() === urlTag.toLocaleLowerCase()) const tag = tags.find(t => t.name.toLocaleLowerCase() === urlTag.toLocaleLowerCase())
tag && addFilterTag(tag) tag && addFilterTag(tag)
});} return null
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [location, tags]); }, [location, tags])
return ( return (
<></> <></>

View File

@ -1,15 +1,14 @@
import { UtopiaMapProps } from "../../types"; import { UtopiaMapProps } from '../../types'
import { ContextWrapper } from "../AppShell/ContextWrapper"; import { ContextWrapper } from '../AppShell/ContextWrapper'
import { UtopiaMapInner } from "./UtopiaMapInner"; import { UtopiaMapInner } from './UtopiaMapInner'
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css'
function UtopiaMap (props: UtopiaMapProps) { function UtopiaMap (props: UtopiaMapProps) {
return ( return (
<ContextWrapper> <ContextWrapper>
<UtopiaMapInner {...props} /> <UtopiaMapInner {...props} />
</ContextWrapper> </ContextWrapper>
); )
} }
export { UtopiaMap }; export { UtopiaMap }

View File

@ -1,35 +1,34 @@
import { TileLayer, MapContainer, useMapEvents, GeoJSON } from "react-leaflet"; import { TileLayer, MapContainer, useMapEvents, GeoJSON } from 'react-leaflet'
import "leaflet/dist/leaflet.css"; import 'leaflet/dist/leaflet.css'
import * as React from "react"; import * as React from 'react'
import { UtopiaMapProps } from "../../types"; import { UtopiaMapProps } from '../../types'
import "./UtopiaMap.css"; import './UtopiaMap.css'
import { LatLng } from "leaflet"; import { LatLng } from 'leaflet'
import MarkerClusterGroup from 'react-leaflet-cluster'; import MarkerClusterGroup from 'react-leaflet-cluster'
import AddButton from "./Subcomponents/AddButton"; import AddButton from './Subcomponents/AddButton'
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from 'react'
import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup"; import { ItemFormPopupProps } from './Subcomponents/ItemFormPopup'
import { SearchControl } from "./Subcomponents/Controls/SearchControl"; import { SearchControl } from './Subcomponents/Controls/SearchControl'
import { Control } from "./Subcomponents/Controls/Control"; import { Control } from './Subcomponents/Controls/Control'
import { Outlet } from "react-router-dom"; import { Outlet } from 'react-router-dom'
import { TagsControl } from "./Subcomponents/Controls/TagsControl"; import { TagsControl } from './Subcomponents/Controls/TagsControl'
import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from "./hooks/useSelectPosition"; import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from './hooks/useSelectPosition'
import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef"; import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
import { Feature, Geometry as GeoJSONGeometry } from 'geojson'; import { Feature, Geometry as GeoJSONGeometry } from 'geojson'
import { FilterControl } from "./Subcomponents/Controls/FilterControl"; import { FilterControl } from './Subcomponents/Controls/FilterControl'
import { LayerControl } from "./Subcomponents/Controls/LayerControl"; import { LayerControl } from './Subcomponents/Controls/LayerControl'
import { useLayers } from "./hooks/useLayers"; import { useLayers } from './hooks/useLayers'
import { useAddVisibleLayer } from "./hooks/useFilter"; import { useAddVisibleLayer } from './hooks/useFilter'
import { GratitudeControl } from "./Subcomponents/Controls/GratitudeControl"; import { GratitudeControl } from './Subcomponents/Controls/GratitudeControl'
import { SelectPosition } from "./Subcomponents/SelectPosition"; import { SelectPosition } from './Subcomponents/SelectPosition'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { TextView } from "./Subcomponents/ItemPopupComponents/TextView"; import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
const mapDivRef = React.createRef()
const mapDivRef = React.createRef();
export function UtopiaMapInner ({ export function UtopiaMapInner ({
height = "500px", height = '500px',
width = "100%", width = '100%',
center = [50.6, 9.5], center = [50.6, 9.5],
zoom = 10, zoom = 10,
children, children,
@ -39,69 +38,67 @@ export function UtopiaMapInner({
showLayerControl = true, showLayerControl = true,
infoText infoText
}: UtopiaMapProps) { }: UtopiaMapProps) {
// Hooks that rely on contexts, called after ContextWrapper is provided // Hooks that rely on contexts, called after ContextWrapper is provided
const selectNewItemPosition = useSelectPosition(); const selectNewItemPosition = useSelectPosition()
const setSelectNewItemPosition = useSetSelectPosition(); const setSelectNewItemPosition = useSetSelectPosition()
const setClusterRef = useSetClusterRef(); const setClusterRef = useSetClusterRef()
const clusterRef = useClusterRef(); const clusterRef = useClusterRef()
const setMapClicked = useSetMapClicked(); const setMapClicked = useSetMapClicked()
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null); const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null)
const layers = useLayers(); const layers = useLayers()
const addVisibleLayer = useAddVisibleLayer(); const addVisibleLayer = useAddVisibleLayer()
useEffect(() => { useEffect(() => {
layers.forEach(layer => addVisibleLayer(layer)); layers.forEach(layer => addVisibleLayer(layer))
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [layers]); }, [layers])
const init = useRef(false) const init = useRef(false)
useEffect(() => { useEffect(() => {
if (!init.current) { if (!init.current) {
infoText && setTimeout(() => { infoText && setTimeout(() => {
toast(<TextView rawText={infoText}/>, { autoClose: false}); toast(<TextView rawText={infoText}/>, { autoClose: false })
}, 4000); }, 4000)
init.current=true; init.current = true
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
function MapEventListener () { function MapEventListener () {
useMapEvents({ useMapEvents({
click: (e) => { click: (e) => {
resetMetaTags(); resetMetaTags()
console.log(e.latlng.lat + ',' + e.latlng.lng); console.log(e.latlng.lat + ',' + e.latlng.lng)
if (selectNewItemPosition) { if (selectNewItemPosition) {
setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }); setMapClicked({ position: e.latlng, setItemFormPopup })
} }
}, },
moveend: () => { } moveend: () => { }
}); })
return null; return null
} }
const resetMetaTags = () => { const resetMetaTags = () => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
if (!window.location.pathname.includes("/item/")) { if (!window.location.pathname.includes('/item/')) {
window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`); window.history.pushState({}, '', '/' + `${params.toString() !== '' ? `?${params}` : ''}`)
}
document.title = document.title.split('-')[0]
document.querySelector('meta[property="og:title"]')?.setAttribute('content', document.title)
document.querySelector('meta[property="og:description"]')?.setAttribute('content', `${document.querySelector('meta[name="description"]')?.getAttribute('content')}`)
} }
document.title = document.title.split("-")[0];
document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title);
document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`);
};
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const onEachFeature = (feature: Feature<GeoJSONGeometry, any>, layer: L.Layer) => { const onEachFeature = (feature: Feature<GeoJSONGeometry, any>, layer: L.Layer) => {
if (feature.properties) { if (feature.properties) {
layer.bindPopup(feature.properties.name); layer.bindPopup(feature.properties.name)
}
} }
};
return ( return (
<div className={`tw-h-full ${selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined}`}> <div className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}>
<MapContainer ref={mapDivRef} style={{ height: height, width: width }} center={new LatLng(center[0], center[1])} zoom={zoom} zoomControl={false} maxZoom={19}> <MapContainer ref={mapDivRef} style={{ height, width }} center={new LatLng(center[0], center[1])} zoom={zoom} zoomControl={false} maxZoom={19}>
<Outlet /> <Outlet />
<Control position="topLeft" zIndex="1000" absolute> <Control position="topLeft" zIndex="1000" absolute>
<SearchControl /> <SearchControl />
@ -121,7 +118,7 @@ export function UtopiaMapInner({
{ {
React.Children.toArray(children).map((child) => React.Children.toArray(children).map((child) =>
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child) React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child)
? React.cloneElement(child, { setItemFormPopup: setItemFormPopup, itemFormPopup: itemFormPopup, clusterRef: clusterRef }) ? React.cloneElement(child, { setItemFormPopup, itemFormPopup, clusterRef })
: child : child
) )
} }
@ -133,10 +130,10 @@ export function UtopiaMapInner({
eventHandlers={{ eventHandlers={{
click: (e) => { click: (e) => {
if (selectNewItemPosition) { if (selectNewItemPosition) {
e.layer!.closePopup(); e.layer!.closePopup()
setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup }); setMapClicked({ position: e.latlng, setItemFormPopup })
}
} }
},
}} }}
/> />
)} )}
@ -145,5 +142,5 @@ export function UtopiaMapInner({
<AddButton triggerAction={setSelectNewItemPosition} /> <AddButton triggerAction={setSelectNewItemPosition} />
{selectNewItemPosition != null && <SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />} {selectNewItemPosition != null && <SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />}
</div> </div>
); )
} }

View File

@ -1,41 +1,36 @@
import * as React from 'react' import * as React from 'react'
import { createContext, useContext, useState } from "react"; import { createContext, useContext, useState } from 'react'
type UseClusterRefManagerResult = ReturnType<typeof useClusterRefManager>; type UseClusterRefManagerResult = ReturnType<typeof useClusterRefManager>;
const ClusterRefContext = createContext<UseClusterRefManagerResult>({ const ClusterRefContext = createContext<UseClusterRefManagerResult>({
clusterRef: {} as React.MutableRefObject<undefined>, clusterRef: {} as React.MutableRefObject<undefined>,
setClusterRef: () => { }, setClusterRef: () => { }
}); })
function useClusterRefManager (): { function useClusterRefManager (): {
clusterRef: any clusterRef: any
setClusterRef: React.Dispatch<React.SetStateAction<React.MutableRefObject<undefined>>>; setClusterRef: React.Dispatch<React.SetStateAction<React.MutableRefObject<undefined>>>;
} { } {
const [clusterRef, setClusterRef] = useState<React.MutableRefObject<undefined>>({} as React.MutableRefObject<undefined>); const [clusterRef, setClusterRef] = useState<React.MutableRefObject<undefined>>({} as React.MutableRefObject<undefined>)
return { clusterRef, setClusterRef };
return { clusterRef, setClusterRef }
} }
export const ClusterRefProvider: React.FunctionComponent<{ export const ClusterRefProvider: React.FunctionComponent<{
children?: React.ReactNode children?: React.ReactNode
}> = ({ children }) => ( }> = ({ children }) => (
<ClusterRefContext.Provider value={useClusterRefManager()}> <ClusterRefContext.Provider value={useClusterRefManager()}>
{children} {children}
</ClusterRefContext.Provider> </ClusterRefContext.Provider>
); )
export const useClusterRef = (): any => { export const useClusterRef = (): any => {
const { clusterRef } = useContext(ClusterRefContext); const { clusterRef } = useContext(ClusterRefContext)
return clusterRef; return clusterRef
};
export const useSetClusterRef = (): UseClusterRefManagerResult["setClusterRef"] => {
const { setClusterRef } = useContext(ClusterRefContext);
return setClusterRef;
} }
export const useSetClusterRef = (): UseClusterRefManagerResult['setClusterRef'] => {
const { setClusterRef } = useContext(ClusterRefContext)
return setClusterRef
}

View File

@ -1,10 +1,10 @@
import { useEffect } from 'react'; import { useEffect } from 'react'
import { useTimeout } from './useTimeout'; import { useTimeout } from './useTimeout'
export const useDebounce = (callback, delay, deps) => { export const useDebounce = (callback, delay, deps) => {
const { reset, clear } = useTimeout(callback, delay); const { reset, clear } = useTimeout(callback, delay)
useEffect(reset, [...deps, reset]); useEffect(reset, [...deps, reset])
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(clear, []); useEffect(clear, [])
} }

View File

@ -1,28 +1,28 @@
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext } from 'react'
import * as React from "react"; import * as React from 'react'
import { LayerProps, Tag } from "../../../types"; import { LayerProps, Tag } from '../../../types'
import { useLayers } from "./useLayers"; import { useLayers } from './useLayers'
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom'
import useWindowDimensions from "./useWindowDimension"; import useWindowDimensions from './useWindowDimension'
type ActionType = type ActionType =
| { type: "ADD_TAG"; tag: Tag } | { type: 'ADD_TAG'; tag: Tag }
| { type: "REMOVE_TAG"; name: string } | { type: 'REMOVE_TAG'; name: string }
| { type: "RESET_TAGS" } | { type: 'RESET_TAGS' }
| { type: "TOGGLE_LAYER"; layer: LayerProps } | { type: 'TOGGLE_LAYER'; layer: LayerProps }
| { type: "ADD_LAYER"; layer: LayerProps } | { type: 'ADD_LAYER'; layer: LayerProps }
| { type: "RESET_LAYERS" } | { type: 'RESET_LAYERS' }
| { type: "TOGGLE_GROUP_TYPE"; groupType: string } | { type: 'TOGGLE_GROUP_TYPE'; groupType: string }
| { type: "ADD_GROUP_TYPE"; groupType: string } | { type: 'ADD_GROUP_TYPE'; groupType: string }
| { type: "RESET_GROUP_TYPE" } | { type: 'RESET_GROUP_TYPE' }
; ;
type UseFilterManagerResult = ReturnType<typeof useFilterManager>; type UseFilterManagerResult = ReturnType<typeof useFilterManager>;
const FilterContext = createContext<UseFilterManagerResult>({ const FilterContext = createContext<UseFilterManagerResult>({
filterTags: [], filterTags: [],
searchPhrase: "", searchPhrase: '',
visibleLayers: [], visibleLayers: [],
visibleGroupTypes: [], visibleGroupTypes: [],
addFilterTag: () => { }, addFilterTag: () => { },
@ -37,7 +37,7 @@ const FilterContext = createContext<UseFilterManagerResult>({
addVisibleGroupType: () => { }, addVisibleGroupType: () => { },
toggleVisibleGroupType: () => { }, toggleVisibleGroupType: () => { },
isGroupTypeVisible: () => true isGroupTypeVisible: () => true
}); })
function useFilterManager (initialTags: Tag[]): { function useFilterManager (initialTags: Tag[]): {
filterTags: Tag[]; filterTags: Tag[];
@ -67,182 +67,174 @@ function useFilterManager(initialTags: Tag[]): {
} { } {
const [filterTags, dispatchTags] = useReducer((state: Tag[], action: ActionType) => { const [filterTags, dispatchTags] = useReducer((state: Tag[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD_TAG": case 'ADD_TAG':
const exist = state.find((tag) => const exist = state.find((tag) =>
tag.id === action.tag.id ? true : false tag.id === action.tag.id
); )
if (!exist) return [ if (!exist) {
return [
...state, ...state,
action.tag, action.tag
]; ]
else return state; } else return state
case "REMOVE_TAG": case 'REMOVE_TAG':
return state.filter(({ name }) => name !== action.name); return state.filter(({ name }) => name !== action.name)
case "RESET_TAGS": case 'RESET_TAGS':
return initialTags; return initialTags
default: default:
throw new Error(); throw new Error()
} }
}, initialTags); }, initialTags)
const initialLayers = useLayers() const initialLayers = useLayers()
const navigate = useNavigate() const navigate = useNavigate()
const windowDimensions = useWindowDimensions(); const windowDimensions = useWindowDimensions()
const [visibleLayers, dispatchLayers] = useReducer((state: LayerProps[], action: ActionType) => { const [visibleLayers, dispatchLayers] = useReducer((state: LayerProps[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD_LAYER": case 'ADD_LAYER':
const exist1 = state.find((layer) => const exist1 = state.find((layer) =>
layer.name === action.layer.name ? true : false layer.name === action.layer.name
); )
if (!exist1) return [ if (!exist1) {
return [
...state, ...state,
action.layer, action.layer
]; ]
else return state; } else return state
case "TOGGLE_LAYER": case 'TOGGLE_LAYER':
const exist2 = state.some((layer) => const exist2 = state.some((layer) =>
layer.name === action.layer.name); layer.name === action.layer.name)
if(exist2) return state.filter(({name}) => name != action.layer.name); if (exist2) return state.filter(({ name }) => name !== action.layer.name)
else return [... state, action.layer]; else return [...state, action.layer]
case "RESET_LAYERS": case 'RESET_LAYERS':
return initialLayers; return initialLayers
default: default:
throw new Error(); throw new Error()
} }
}, initialLayers); }, initialLayers)
const [visibleGroupTypes, dispatchGroupTypes] = useReducer((state: string[], action: ActionType) => { const [visibleGroupTypes, dispatchGroupTypes] = useReducer((state: string[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD_GROUP_TYPE": case 'ADD_GROUP_TYPE':
const exist1 = state.find((groupType) => const exist1 = state.find((groupType) =>
groupType === action.groupType ? true : false groupType === action.groupType
); )
if (!exist1) return [ if (!exist1) {
return [
...state, ...state,
action.groupType, action.groupType
]; ]
else return state; } else return state
case "TOGGLE_GROUP_TYPE": case 'TOGGLE_GROUP_TYPE':
const exist2 = state.some((groupType) => const exist2 = state.some((groupType) =>
groupType === action.groupType); groupType === action.groupType)
if(exist2) return state.filter((groupType) => groupType != action.groupType); if (exist2) return state.filter((groupType) => groupType !== action.groupType)
else return [... state, action.groupType]; else return [...state, action.groupType]
case "RESET_GROUP_TYPE": case 'RESET_GROUP_TYPE':
return []; return []
default: default:
throw new Error(); throw new Error()
} }
},[]); }, [])
const [searchPhrase, searchPhraseSet] = React.useState<string>(""); const [searchPhrase, searchPhraseSet] = React.useState<string>('')
const addFilterTag = useCallback((tag: Tag) => { const addFilterTag = useCallback((tag: Tag) => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const urlTags = params.get("tags") const urlTags = params.get('tags')
const decodedTags = urlTags ? decodeURIComponent(urlTags) : ""; const decodedTags = urlTags ? decodeURIComponent(urlTags) : ''
if(!decodedTags?.includes(tag.name))
params.set("tags", `${urlTags ? urlTags : ""}${urlTags? ';' : ''}${tag.name}`)
if(windowDimensions.width < 786 && location.pathname.split("/").length > 2) navigate("/" + `${params ? `?${params}` : ""}`);
else navigate(location.pathname + `${params ? `?${params}` : ""}`);
if (!decodedTags?.includes(tag.name)) { params.set('tags', `${urlTags || ''}${urlTags ? ';' : ''}${tag.name}`) }
if (windowDimensions.width < 786 && location.pathname.split('/').length > 2) navigate('/' + `${params ? `?${params}` : ''}`)
else navigate(location.pathname + `${params ? `?${params}` : ''}`)
dispatchTags({ dispatchTags({
type: "ADD_TAG", type: 'ADD_TAG',
tag, tag
}); })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [])
const removeFilterTag = useCallback((name: string) => { const removeFilterTag = useCallback((name: string) => {
const params = new URLSearchParams(window.location.search)
const params = new URLSearchParams(window.location.search); const urlTags = params.get('tags')
const urlTags = params.get("tags"); let newUrlTags = ''
let newUrlTags = ""; const tags = urlTags?.split(';')
const tags = urlTags?.split(";"); if (tags?.length === 0 && urlTags?.length && urlTags?.length > 0) tags[0] = urlTags
if(tags?.length==0 && urlTags?.length && urlTags?.length > 0) tags[0]=urlTags;
tags?.map(urlTag => { tags?.map(urlTag => {
if(!(urlTag.toLocaleLowerCase() === name.toLocaleLowerCase())) if (!(urlTag.toLocaleLowerCase() === name.toLocaleLowerCase())) { newUrlTags = newUrlTags + `${newUrlTags === '' ? urlTag : `;${urlTag}`}` }
newUrlTags = newUrlTags + `${newUrlTags===""? urlTag : `;${urlTag}`}` return null
}); })
if(newUrlTags !== "") { if (newUrlTags !== '') {
params.set("tags", `${newUrlTags}`) params.set('tags', `${newUrlTags}`)
navigate(location.pathname + `${params ? `?${params}` : ""}`); navigate(location.pathname + `${params ? `?${params}` : ''}`)
} } else {
else { params.delete('tags')
params.delete("tags"); navigate(location.pathname + `${params ? `?${params}` : ''}`)
navigate(location.pathname + `${params ? `?${params}` : ""}`);
} }
dispatchTags({ dispatchTags({
type: "REMOVE_TAG", type: 'REMOVE_TAG',
name, name
}); })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [])
const resetFilterTags = useCallback(() => { const resetFilterTags = useCallback(() => {
dispatchTags({ dispatchTags({
type: "RESET_TAGS", type: 'RESET_TAGS'
}); })
}, []); }, [])
const addVisibleLayer = (layer: LayerProps) => { const addVisibleLayer = (layer: LayerProps) => {
dispatchLayers({ dispatchLayers({
type: "ADD_LAYER", type: 'ADD_LAYER',
layer, layer
}); })
}
};
const toggleVisibleLayer = (layer: LayerProps) => { const toggleVisibleLayer = (layer: LayerProps) => {
dispatchLayers({ dispatchLayers({
type: "TOGGLE_LAYER", type: 'TOGGLE_LAYER',
layer, layer
}); })
}
};
const resetVisibleLayers = useCallback(() => { const resetVisibleLayers = useCallback(() => {
dispatchLayers({ dispatchLayers({
type: "RESET_LAYERS", type: 'RESET_LAYERS'
}); })
}, []); }, [])
const isLayerVisible = useCallback((layer: LayerProps) => { const isLayerVisible = useCallback((layer: LayerProps) => {
return visibleLayers.some(l => l.name === layer.name) return visibleLayers.some(l => l.name === layer.name)
}, [visibleLayers]); }, [visibleLayers])
const addVisibleGroupType = (groupType: string) => { const addVisibleGroupType = (groupType: string) => {
dispatchGroupTypes({ dispatchGroupTypes({
type: "ADD_GROUP_TYPE", type: 'ADD_GROUP_TYPE',
groupType, groupType
}); })
}
};
const toggleVisibleGroupType = (groupType: string) => { const toggleVisibleGroupType = (groupType: string) => {
dispatchGroupTypes({ dispatchGroupTypes({
type: "TOGGLE_GROUP_TYPE", type: 'TOGGLE_GROUP_TYPE',
groupType, groupType
}); })
}
};
const isGroupTypeVisible = useCallback((groupType: string) => { const isGroupTypeVisible = useCallback((groupType: string) => {
return visibleGroupTypes.some(gt => gt === groupType) return visibleGroupTypes.some(gt => gt === groupType)
}, [visibleGroupTypes]); }, [visibleGroupTypes])
const setSearchPhrase = useCallback((phrase: string) => { const setSearchPhrase = useCallback((phrase: string) => {
searchPhraseSet(phrase) searchPhraseSet(phrase)
}, []); }, [])
return { filterTags, addFilterTag, removeFilterTag, resetFilterTags, setSearchPhrase, searchPhrase, visibleLayers, toggleVisibleLayer, resetVisibleLayers, isLayerVisible, addVisibleLayer, visibleGroupTypes, addVisibleGroupType, toggleVisibleGroupType, isGroupTypeVisible }; return { filterTags, addFilterTag, removeFilterTag, resetFilterTags, setSearchPhrase, searchPhrase, visibleLayers, toggleVisibleLayer, resetVisibleLayers, isLayerVisible, addVisibleLayer, visibleGroupTypes, addVisibleGroupType, toggleVisibleGroupType, isGroupTypeVisible }
} }
export const FilterProvider: React.FunctionComponent<{ export const FilterProvider: React.FunctionComponent<{
@ -251,82 +243,79 @@ export const FilterProvider: React.FunctionComponent<{
<FilterContext.Provider value={useFilterManager(initialTags)}> <FilterContext.Provider value={useFilterManager(initialTags)}>
{children} {children}
</FilterContext.Provider> </FilterContext.Provider>
); )
export const useFilterTags = (): Tag[] => { export const useFilterTags = (): Tag[] => {
const { filterTags } = useContext(FilterContext); const { filterTags } = useContext(FilterContext)
return filterTags; return filterTags
}; }
export const useAddFilterTag = (): UseFilterManagerResult["addFilterTag"] => { export const useAddFilterTag = (): UseFilterManagerResult['addFilterTag'] => {
const { addFilterTag } = useContext(FilterContext); const { addFilterTag } = useContext(FilterContext)
return addFilterTag; return addFilterTag
}; }
export const useRemoveFilterTag = (): UseFilterManagerResult["removeFilterTag"] => { export const useRemoveFilterTag = (): UseFilterManagerResult['removeFilterTag'] => {
const { removeFilterTag } = useContext(FilterContext); const { removeFilterTag } = useContext(FilterContext)
return removeFilterTag; return removeFilterTag
}; }
export const useResetFilterTags = (): UseFilterManagerResult["resetFilterTags"] => { export const useResetFilterTags = (): UseFilterManagerResult['resetFilterTags'] => {
const { resetFilterTags } = useContext(FilterContext); const { resetFilterTags } = useContext(FilterContext)
return resetFilterTags; return resetFilterTags
}; }
export const useSearchPhrase = (): UseFilterManagerResult["searchPhrase"] => { export const useSearchPhrase = (): UseFilterManagerResult['searchPhrase'] => {
const { searchPhrase } = useContext(FilterContext); const { searchPhrase } = useContext(FilterContext)
return searchPhrase; return searchPhrase
}; }
export const useSetSearchPhrase = (): UseFilterManagerResult["setSearchPhrase"] => { export const useSetSearchPhrase = (): UseFilterManagerResult['setSearchPhrase'] => {
const { setSearchPhrase } = useContext(FilterContext); const { setSearchPhrase } = useContext(FilterContext)
return setSearchPhrase; return setSearchPhrase
}; }
export const useVisibleLayer = (): UseFilterManagerResult["visibleLayers"] => { export const useVisibleLayer = (): UseFilterManagerResult['visibleLayers'] => {
const { visibleLayers } = useContext(FilterContext); const { visibleLayers } = useContext(FilterContext)
return visibleLayers; return visibleLayers
}; }
export const useAddVisibleLayer = (): UseFilterManagerResult["addVisibleLayer"] => { export const useAddVisibleLayer = (): UseFilterManagerResult['addVisibleLayer'] => {
const { addVisibleLayer } = useContext(FilterContext); const { addVisibleLayer } = useContext(FilterContext)
return addVisibleLayer; return addVisibleLayer
}; }
export const useToggleVisibleLayer = (): UseFilterManagerResult['toggleVisibleLayer'] => {
const { toggleVisibleLayer } = useContext(FilterContext)
return toggleVisibleLayer
}
export const useToggleVisibleLayer = (): UseFilterManagerResult["toggleVisibleLayer"] => { export const useResetVisibleLayers = (): UseFilterManagerResult['resetVisibleLayers'] => {
const { toggleVisibleLayer } = useContext(FilterContext); const { resetVisibleLayers } = useContext(FilterContext)
return toggleVisibleLayer; return resetVisibleLayers
}; }
export const useResetVisibleLayers = (): UseFilterManagerResult["resetVisibleLayers"] => { export const useIsLayerVisible = (): UseFilterManagerResult['isLayerVisible'] => {
const { resetVisibleLayers } = useContext(FilterContext); const { isLayerVisible } = useContext(FilterContext)
return resetVisibleLayers; return isLayerVisible
}; }
export const useIsLayerVisible = (): UseFilterManagerResult["isLayerVisible"] => { export const useAddVisibleGroupType = (): UseFilterManagerResult['addVisibleGroupType'] => {
const { isLayerVisible } = useContext(FilterContext); const { addVisibleGroupType } = useContext(FilterContext)
return isLayerVisible; return addVisibleGroupType
}; }
export const useAddVisibleGroupType = (): UseFilterManagerResult["addVisibleGroupType"] => { export const useToggleVisibleGroupType = (): UseFilterManagerResult['toggleVisibleGroupType'] => {
const { addVisibleGroupType } = useContext(FilterContext); const { toggleVisibleGroupType } = useContext(FilterContext)
return addVisibleGroupType; return toggleVisibleGroupType
}; }
export const useIsGroupTypeVisible = (): UseFilterManagerResult['isGroupTypeVisible'] => {
export const useToggleVisibleGroupType = (): UseFilterManagerResult["toggleVisibleGroupType"] => { const { isGroupTypeVisible } = useContext(FilterContext)
const { toggleVisibleGroupType } = useContext(FilterContext);
return toggleVisibleGroupType;
};
export const useIsGroupTypeVisible = (): UseFilterManagerResult["isGroupTypeVisible"] => {
const { isGroupTypeVisible } = useContext(FilterContext);
return isGroupTypeVisible return isGroupTypeVisible
}; }
export const useVisibleGroupType = (): UseFilterManagerResult["visibleGroupTypes"] => { export const useVisibleGroupType = (): UseFilterManagerResult['visibleGroupTypes'] => {
const { visibleGroupTypes } = useContext(FilterContext); const { visibleGroupTypes } = useContext(FilterContext)
return visibleGroupTypes; return visibleGroupTypes
}; }

View File

@ -1,18 +1,16 @@
import { useCallback, useReducer, createContext, useContext, useState } from "react"; import { useCallback, useReducer, createContext, useContext, useState } from 'react'
import * as React from "react"; import * as React from 'react'
import { Item, LayerProps } from "../../../types"; import { Item, LayerProps } from '../../../types'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import { useAddLayer } from "./useLayers"; import { useAddLayer } from './useLayers'
type ActionType = type ActionType =
| { type: "ADD"; item: Item } | { type: 'ADD'; item: Item }
| { type: "UPDATE"; item: Item } | { type: 'UPDATE'; item: Item }
| { type: "REMOVE"; item: Item } | { type: 'REMOVE'; item: Item }
| { type: "RESET"; layer: LayerProps } | { type: 'RESET'; layer: LayerProps }
; ;
type UseItemManagerResult = ReturnType<typeof useItemsManager>; type UseItemManagerResult = ReturnType<typeof useItemsManager>;
const ItemContext = createContext<UseItemManagerResult>({ const ItemContext = createContext<UseItemManagerResult>({
@ -24,7 +22,7 @@ const ItemContext = createContext<UseItemManagerResult>({
setItemsApi: () => { }, setItemsApi: () => { },
setItemsData: () => { }, setItemsData: () => { },
allItemsLoaded: false allItemsLoaded: false
}); })
function useItemsManager (initialItems: Item[]): { function useItemsManager (initialItems: Item[]): {
items: Item[]; items: Item[];
@ -43,44 +41,41 @@ function useItemsManager(initialItems: Item[]): {
allItemsLoaded: boolean; allItemsLoaded: boolean;
} { } {
const addLayer = useAddLayer()
const addLayer = useAddLayer(); const [allItemsLoaded, setallItemsLoaded] = useState<boolean>(false)
const [allItemsLoaded, setallItemsLoaded] = useState<boolean>(false);
const [items, dispatch] = useReducer((state: Item[], action: ActionType) => { const [items, dispatch] = useReducer((state: Item[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD": case 'ADD':
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const exist = state.find((item) => const exist = state.find((item) =>
item.id === action.item.id ? true : false item.id === action.item.id
); )
if (!exist) return [ if (!exist) {
return [
...state, ...state,
action.item, action.item
]; ]
else return state; } else return state
case "UPDATE": case 'UPDATE':
return state.map((item) => { return state.map((item) => {
if (item.id === action.item.id) { if (item.id === action.item.id) {
return action.item return action.item
} }
return item return item
}); })
case "REMOVE": case 'REMOVE':
return state.filter(item => item !== action.item); return state.filter(item => item !== action.item)
case "RESET": case 'RESET':
return state.filter(item => item.layer?.name !== action.layer.name); return state.filter(item => item.layer?.name !== action.layer.name)
default: default:
throw new Error(); throw new Error()
} }
}, initialItems); }, initialItems)
const setItemsApi = useCallback(async (layer: LayerProps) => { const setItemsApi = useCallback(async (layer: LayerProps) => {
addLayer(layer); addLayer(layer)
const result = await toast.promise( const result = await toast.promise(
layer.api!.getItems(), layer.api!.getItems(),
{ {
@ -89,60 +84,59 @@ function useItemsManager(initialItems: Item[]): {
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
},
},
} }
); }
}
)
if (result) { if (result) {
result.map(item => { result.map(item => {
dispatch({ type: "ADD", item: { ...item, layer: layer } }); dispatch({ type: 'ADD', item: { ...item, layer } })
return null
}) })
setallItemsLoaded(true); setallItemsLoaded(true)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const setItemsData = useCallback((layer: LayerProps) => { const setItemsData = useCallback((layer: LayerProps) => {
addLayer(layer); addLayer(layer)
layer.data?.map(item => { layer.data?.map(item => {
dispatch({ type: "ADD", item: { ...item, layer: layer } }); dispatch({ type: 'ADD', item: { ...item, layer } })
return null
}) })
setallItemsLoaded(true); setallItemsLoaded(true)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [])
const addItem = useCallback(async (item: Item) => { const addItem = useCallback(async (item: Item) => {
dispatch({ dispatch({
type: "ADD", type: 'ADD',
item, item
}); })
}, []); }, [])
const updateItem = useCallback(async (item: Item) => { const updateItem = useCallback(async (item: Item) => {
dispatch({ dispatch({
type: "UPDATE", type: 'UPDATE',
item, item
}); })
}, []); }, [])
const removeItem = useCallback((item: Item) => { const removeItem = useCallback((item: Item) => {
dispatch({ dispatch({
type: "REMOVE", type: 'REMOVE',
item, item
}); })
}, []); }, [])
const resetItems = useCallback((layer: LayerProps) => { const resetItems = useCallback((layer: LayerProps) => {
dispatch({ dispatch({
type: "RESET", type: 'RESET',
layer layer
}); })
}, []); }, [])
return { items, updateItem, addItem, removeItem, resetItems, setItemsApi, setItemsData, allItemsLoaded }
return { items, updateItem, addItem, removeItem, resetItems, setItemsApi, setItemsData, allItemsLoaded };
} }
export const ItemsProvider: React.FunctionComponent<{ export const ItemsProvider: React.FunctionComponent<{
@ -151,44 +145,44 @@ export const ItemsProvider: React.FunctionComponent<{
<ItemContext.Provider value={useItemsManager(initialItems)}> <ItemContext.Provider value={useItemsManager(initialItems)}>
{children} {children}
</ItemContext.Provider> </ItemContext.Provider>
); )
export const useItems = (): Item[] => { export const useItems = (): Item[] => {
const { items } = useContext(ItemContext); const { items } = useContext(ItemContext)
return items; return items
}; }
export const useAddItem = (): UseItemManagerResult["addItem"] => { export const useAddItem = (): UseItemManagerResult['addItem'] => {
const { addItem } = useContext(ItemContext); const { addItem } = useContext(ItemContext)
return addItem; return addItem
}; }
export const useUpdateItem = (): UseItemManagerResult["updateItem"] => { export const useUpdateItem = (): UseItemManagerResult['updateItem'] => {
const { updateItem } = useContext(ItemContext); const { updateItem } = useContext(ItemContext)
return updateItem; return updateItem
}; }
export const useRemoveItem = (): UseItemManagerResult["removeItem"] => { export const useRemoveItem = (): UseItemManagerResult['removeItem'] => {
const { removeItem } = useContext(ItemContext); const { removeItem } = useContext(ItemContext)
return removeItem; return removeItem
}; }
export const useResetItems = (): UseItemManagerResult["resetItems"] => { export const useResetItems = (): UseItemManagerResult['resetItems'] => {
const { resetItems } = useContext(ItemContext); const { resetItems } = useContext(ItemContext)
return resetItems; return resetItems
}; }
export const useSetItemsApi = (): UseItemManagerResult["setItemsApi"] => { export const useSetItemsApi = (): UseItemManagerResult['setItemsApi'] => {
const { setItemsApi } = useContext(ItemContext); const { setItemsApi } = useContext(ItemContext)
return setItemsApi; return setItemsApi
}; }
export const useSetItemsData = (): UseItemManagerResult["setItemsData"] => { export const useSetItemsData = (): UseItemManagerResult['setItemsData'] => {
const { setItemsData } = useContext(ItemContext); const { setItemsData } = useContext(ItemContext)
return setItemsData; return setItemsData
}; }
export const useAllItemsLoaded = (): UseItemManagerResult["allItemsLoaded"] => { export const useAllItemsLoaded = (): UseItemManagerResult['allItemsLoaded'] => {
const { allItemsLoaded } = useContext(ItemContext); const { allItemsLoaded } = useContext(ItemContext)
return allItemsLoaded; return allItemsLoaded
} }

View File

@ -1,16 +1,16 @@
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext } from 'react'
import * as React from "react"; import * as React from 'react'
import { LayerProps } from "../../../types"; import { LayerProps } from '../../../types'
type ActionType = type ActionType =
| { type: "ADD LAYER"; layer: LayerProps } | { type: 'ADD LAYER'; layer: LayerProps }
type UseItemManagerResult = ReturnType<typeof useLayerManager>; type UseItemManagerResult = ReturnType<typeof useLayerManager>;
const LayerContext = createContext<UseItemManagerResult>({ const LayerContext = createContext<UseItemManagerResult>({
layers: [], layers: [],
addLayer: () => { }, addLayer: () => { }
}); })
function useLayerManager (initialLayers: LayerProps[]): { function useLayerManager (initialLayers: LayerProps[]): {
layers: LayerProps[]; layers: LayerProps[];
@ -19,29 +19,30 @@ function useLayerManager(initialLayers: LayerProps[]): {
} { } {
const [layers, dispatch] = useReducer((state: LayerProps[], action: ActionType) => { const [layers, dispatch] = useReducer((state: LayerProps[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD LAYER": case 'ADD LAYER':
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const exist = state.find((layer) => const exist = state.find((layer) =>
layer.name === action.layer.name ? true : false layer.name === action.layer.name
); )
if (!exist) return [ if (!exist) {
return [
...state, ...state,
action.layer, action.layer
]; ]
else return state; } else return state
default: default:
throw new Error(); throw new Error()
} }
}, initialLayers); }, initialLayers)
const addLayer = useCallback((layer: LayerProps) => { const addLayer = useCallback((layer: LayerProps) => {
dispatch({ dispatch({
type: "ADD LAYER", type: 'ADD LAYER',
layer layer
}); })
}, []); }, [])
return { layers, addLayer }; return { layers, addLayer }
} }
export const LayersProvider: React.FunctionComponent<{ export const LayersProvider: React.FunctionComponent<{
@ -50,14 +51,14 @@ export const LayersProvider: React.FunctionComponent<{
<LayerContext.Provider value={useLayerManager(initialLayers)}> <LayerContext.Provider value={useLayerManager(initialLayers)}>
{children} {children}
</LayerContext.Provider> </LayerContext.Provider>
); )
export const useLayers = (): LayerProps[] => { export const useLayers = (): LayerProps[] => {
const { layers } = useContext(LayerContext); const { layers } = useContext(LayerContext)
return layers; return layers
}; }
export const useAddLayer = (): UseItemManagerResult["addLayer"] => { export const useAddLayer = (): UseItemManagerResult['addLayer'] => {
const { addLayer } = useContext(LayerContext); const { addLayer } = useContext(LayerContext)
return addLayer; return addLayer
}; }

View File

@ -1,7 +1,7 @@
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext } from 'react'
import * as React from "react"; import * as React from 'react'
import { Item } from "../../../types"; import { Item } from '../../../types'
import { Marker, Popup } from "leaflet"; import { Marker, Popup } from 'leaflet'
type LeafletRef = { type LeafletRef = {
item: Item, item: Item,
@ -10,18 +10,17 @@ type LeafletRef = {
} }
type ActionType = type ActionType =
| { type: "ADD_MARKER"; item: Item, marker: Marker } | { type: 'ADD_MARKER'; item: Item, marker: Marker }
| { type: "ADD_POPUP"; item: Item, popup: Popup } | { type: 'ADD_POPUP'; item: Item, popup: Popup }
; ;
type UseLeafletRefsManagerResult = ReturnType<typeof useLeafletRefsManager>; type UseLeafletRefsManagerResult = ReturnType<typeof useLeafletRefsManager>;
const LeafletRefsContext = createContext<UseLeafletRefsManagerResult>({ const LeafletRefsContext = createContext<UseLeafletRefsManagerResult>({
leafletRefs: {}, leafletRefs: {},
addMarker: () => { }, addMarker: () => { },
addPopup: () => { }, addPopup: () => { }
}); })
function useLeafletRefsManager (initialLeafletRefs: {}): { function useLeafletRefsManager (initialLeafletRefs: {}): {
leafletRefs: Record<string, LeafletRef>; leafletRefs: Record<string, LeafletRef>;
@ -30,46 +29,40 @@ function useLeafletRefsManager(initialLeafletRefs: {}): {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
addPopup: (item: Item, popup: Popup) => void; addPopup: (item: Item, popup: Popup) => void;
} { } {
const [leafletRefs, dispatch] = useReducer((state: Record<string, LeafletRef>, action: ActionType) => { const [leafletRefs, dispatch] = useReducer((state: Record<string, LeafletRef>, action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD_MARKER": case 'ADD_MARKER':
return { return {
...state, ...state,
[action.item.id]: { ...state[action.item.id], marker: action.marker, item: action.item }} [action.item.id]: { ...state[action.item.id], marker: action.marker, item: action.item }
case "ADD_POPUP":
return {
...state,
[action.item.id]: { ...state[action.item.id], popup: action.popup, item: action.item }}
default:
throw new Error();
} }
}, initialLeafletRefs); case 'ADD_POPUP':
return {
...state,
[action.item.id]: { ...state[action.item.id], popup: action.popup, item: action.item }
}
default:
throw new Error()
}
}, initialLeafletRefs)
const addMarker = useCallback((item: Item, marker: Marker) => { const addMarker = useCallback((item: Item, marker: Marker) => {
dispatch({ dispatch({
type: "ADD_MARKER", type: 'ADD_MARKER',
item, item,
marker marker
}); })
}, []); }, [])
const addPopup = useCallback((item: Item, popup: Popup) => { const addPopup = useCallback((item: Item, popup: Popup) => {
dispatch({ dispatch({
type: "ADD_POPUP", type: 'ADD_POPUP',
item, item,
popup popup
}); })
}, []); }, [])
return { leafletRefs, addMarker, addPopup }
return { leafletRefs, addMarker, addPopup };
} }
export const LeafletRefsProvider: React.FunctionComponent<{ export const LeafletRefsProvider: React.FunctionComponent<{
@ -78,20 +71,19 @@ export const LeafletRefsProvider: React.FunctionComponent<{
<LeafletRefsContext.Provider value={useLeafletRefsManager(initialLeafletRefs)}> <LeafletRefsContext.Provider value={useLeafletRefsManager(initialLeafletRefs)}>
{children} {children}
</LeafletRefsContext.Provider> </LeafletRefsContext.Provider>
); )
export const useLeafletRefs = (): Record<string, LeafletRef> => { export const useLeafletRefs = (): Record<string, LeafletRef> => {
const { leafletRefs } = useContext(LeafletRefsContext); const { leafletRefs } = useContext(LeafletRefsContext)
return leafletRefs; return leafletRefs
}; }
export const useAddMarker = (): UseLeafletRefsManagerResult["addMarker"] => { export const useAddMarker = (): UseLeafletRefsManagerResult['addMarker'] => {
const { addMarker } = useContext(LeafletRefsContext); const { addMarker } = useContext(LeafletRefsContext)
return addMarker; return addMarker
}; }
export const useAddPopup = (): UseLeafletRefsManagerResult["addPopup"] => {
const { addPopup } = useContext(LeafletRefsContext);
return addPopup;
};
export const useAddPopup = (): UseLeafletRefsManagerResult['addPopup'] => {
const { addPopup } = useContext(LeafletRefsContext)
return addPopup
}

View File

@ -1,11 +1,11 @@
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext } from 'react'
import * as React from "react"; import * as React from 'react'
import { Item, ItemsApi, LayerProps, Permission, PermissionAction } from "../../../types"; import { Item, ItemsApi, LayerProps, Permission, PermissionAction } from '../../../types'
import { useAuth } from "../../Auth"; import { useAuth } from '../../Auth'
type ActionType = type ActionType =
| { type: "ADD"; permission: Permission } | { type: 'ADD'; permission: Permission }
| { type: "REMOVE"; id: string }; | { type: 'REMOVE'; id: string };
type UsePermissionManagerResult = ReturnType<typeof usePermissionsManager>; type UsePermissionManagerResult = ReturnType<typeof usePermissionsManager>;
@ -15,7 +15,7 @@ const PermissionContext = createContext<UsePermissionManagerResult>({
setPermissionData: () => { }, setPermissionData: () => { },
setAdminRole: () => { }, setAdminRole: () => { },
hasUserPermission: () => true hasUserPermission: () => true
}); })
function usePermissionsManager (initialPermissions: Permission[]): { function usePermissionsManager (initialPermissions: Permission[]): {
permissions: Permission[]; permissions: Permission[];
@ -30,42 +30,44 @@ function usePermissionsManager(initialPermissions: Permission[]): {
} { } {
const [permissions, dispatch] = useReducer((state: Permission[], action: ActionType) => { const [permissions, dispatch] = useReducer((state: Permission[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD": case 'ADD':
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const exist = state.find((permission) => const exist = state.find((permission) =>
permission.id === action.permission.id ? true : false permission.id === action.permission.id
); )
if (!exist) return [ if (!exist) {
return [
...state, ...state,
action.permission, action.permission
]; ]
else return state; } else return state
case "REMOVE": case 'REMOVE':
return state.filter(({ id }) => id !== action.id); return state.filter(({ id }) => id !== action.id)
default: default:
throw new Error(); throw new Error()
} }
}, initialPermissions); }, initialPermissions)
const [adminRole, setAdminRole] = React.useState<string | null>(null);
const { user } = useAuth();
const [adminRole, setAdminRole] = React.useState<string | null>(null)
const { user } = useAuth()
const setPermissionApi = useCallback(async (api: ItemsApi<Permission>) => { const setPermissionApi = useCallback(async (api: ItemsApi<Permission>) => {
const result = await api.getItems(); const result = await api.getItems()
if (result) { if (result) {
result.map(permission => { result.map(permission => {
dispatch({ type: "ADD", permission }) dispatch({ type: 'ADD', permission })
return null
}) })
} }
}, []) }, [])
const setPermissionData = useCallback((data: Permission[]) => { const setPermissionData = useCallback((data: Permission[]) => {
data.map(permission => { data.map(permission => {
dispatch({ type: "ADD", permission }) dispatch({ type: 'ADD', permission })
return null
}) })
}, []); }, [])
const hasUserPermission = useCallback( const hasUserPermission = useCallback(
( (
@ -74,60 +76,53 @@ function usePermissionsManager(initialPermissions: Permission[]): {
item?: Item, item?: Item,
layer?: LayerProps layer?: LayerProps
) => { ) => {
const evaluateCondition = (condition: any) => { const evaluateCondition = (condition: any) => {
if (condition.user_created?._eq === "$CURRENT_USER") { if (condition.user_created?._eq === '$CURRENT_USER') {
return item?.user_created?.id === user?.id; return item?.user_created?.id === user?.id
} }
if (condition.public_edit?._eq === true) { if (condition.public_edit?._eq === true) {
return item?.public_edit === true; return item?.public_edit === true
}
return false
} }
return false;
};
const evaluatePermissions = (permissionConditions: any) => { const evaluatePermissions = (permissionConditions: any) => {
if (!permissionConditions || !permissionConditions._and) { if (!permissionConditions || !permissionConditions._and) {
return true; return true
} }
return permissionConditions._and.every((andCondition: any) => return permissionConditions._and.every((andCondition: any) =>
andCondition._or andCondition._or
? andCondition._or.some((orCondition: any) => evaluateCondition(orCondition)) ? andCondition._or.some((orCondition: any) => evaluateCondition(orCondition))
: evaluateCondition(andCondition) : evaluateCondition(andCondition)
); )
}; }
if (collectionName === "items" && action === "create" && layer?.public_edit_items) return true; if (collectionName === 'items' && action === 'create' && layer?.public_edit_items) return true
// Bedingung für leere Berechtigungen nur, wenn NICHT item und create // Bedingung für leere Berechtigungen nur, wenn NICHT item und create
if (permissions.length === 0) return true; if (permissions.length === 0) return true
else if (user && user.role.id === adminRole) return true; else if (user && user.role.id === adminRole) return true
else { else {
return permissions.some(p => return permissions.some(p =>
p.action === action && p.action === action &&
p.collection === collectionName && p.collection === collectionName &&
( (
(p.policy?.name === user?.role.name && (p.policy?.name === user?.role.name &&
( (
!item || evaluatePermissions(p.permissions) !item || evaluatePermissions(p.permissions)
)) || )) ||
(p.policy?.name === "$t:public_label" && (p.policy?.name === '$t:public_label' &&
( (
(layer?.public_edit_items || item?.layer?.public_edit_items) && (layer?.public_edit_items || item?.layer?.public_edit_items) &&
(!item || evaluatePermissions(p.permissions)) (!item || evaluatePermissions(p.permissions))
)) ))
) )
)
);
} }
}, },
[permissions, user, adminRole] [permissions, user, adminRole]
); )
return { permissions, setPermissionApi, setPermissionData, setAdminRole, hasUserPermission }
return { permissions, setPermissionApi, setPermissionData, setAdminRole, hasUserPermission };
} }
export const PermissionsProvider: React.FunctionComponent<{ export const PermissionsProvider: React.FunctionComponent<{
@ -136,30 +131,29 @@ export const PermissionsProvider: React.FunctionComponent<{
<PermissionContext.Provider value={usePermissionsManager(initialPermissions)}> <PermissionContext.Provider value={usePermissionsManager(initialPermissions)}>
{children} {children}
</PermissionContext.Provider> </PermissionContext.Provider>
); )
export const usePermissions = (): Permission[] => { export const usePermissions = (): Permission[] => {
const { permissions } = useContext(PermissionContext); const { permissions } = useContext(PermissionContext)
return permissions; return permissions
};
export const useSetPermissionApi = (): UsePermissionManagerResult["setPermissionApi"] => {
const { setPermissionApi } = useContext(PermissionContext);
return setPermissionApi;
} }
export const useSetPermissionData = (): UsePermissionManagerResult["setPermissionData"] => { export const useSetPermissionApi = (): UsePermissionManagerResult['setPermissionApi'] => {
const { setPermissionData } = useContext(PermissionContext); const { setPermissionApi } = useContext(PermissionContext)
return setPermissionData; return setPermissionApi
} }
export const useHasUserPermission = (): UsePermissionManagerResult["hasUserPermission"] => { export const useSetPermissionData = (): UsePermissionManagerResult['setPermissionData'] => {
const { hasUserPermission } = useContext(PermissionContext); const { setPermissionData } = useContext(PermissionContext)
return hasUserPermission; return setPermissionData
} }
export const useSetAdminRole = (): UsePermissionManagerResult["setAdminRole"] => { export const useHasUserPermission = (): UsePermissionManagerResult['hasUserPermission'] => {
const { setAdminRole } = useContext(PermissionContext); const { hasUserPermission } = useContext(PermissionContext)
return setAdminRole; return hasUserPermission
}
export const useSetAdminRole = (): UsePermissionManagerResult['setAdminRole'] => {
const { setAdminRole } = useContext(PermissionContext)
return setAdminRole
} }

View File

@ -1,11 +1,11 @@
import * as React from 'react' import * as React from 'react'
import { createContext, useContext, useEffect, useState } from "react"; import { createContext, useContext, useEffect, useState } from 'react'
import { Geometry, Item, LayerProps } from '../../../types'; import { Geometry, Item, LayerProps } from '../../../types'
import { useUpdateItem } from "./useItems"; import { useUpdateItem } from './useItems'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import { useHasUserPermission } from "./usePermissions"; import { useHasUserPermission } from './usePermissions'
import { LatLng } from "leaflet"; import { LatLng } from 'leaflet'
import { ItemFormPopupProps } from "../Subcomponents/ItemFormPopup"; import { ItemFormPopupProps } from '../Subcomponents/ItemFormPopup'
type PolygonClickedProps = { type PolygonClickedProps = {
position: LatLng position: LatLng
@ -18,8 +18,8 @@ const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
selectPosition: null, selectPosition: null,
setSelectPosition: () => { }, setSelectPosition: () => { },
setMarkerClicked: () => { }, setMarkerClicked: () => { },
setMapClicked: () => {}, setMapClicked: () => {}
}); })
function useSelectPositionManager (): { function useSelectPositionManager (): {
selectPosition: Item | LayerProps | null; selectPosition: Item | LayerProps | null;
@ -27,13 +27,11 @@ function useSelectPositionManager(): {
setMarkerClicked: React.Dispatch<React.SetStateAction<Item>>; setMarkerClicked: React.Dispatch<React.SetStateAction<Item>>;
setMapClicked: React.Dispatch<React.SetStateAction<PolygonClickedProps | undefined>>; setMapClicked: React.Dispatch<React.SetStateAction<PolygonClickedProps | undefined>>;
} { } {
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null); const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null)
const [markerClicked, setMarkerClicked] = useState<Item | null>(); const [markerClicked, setMarkerClicked] = useState<Item | null>()
const [mapClicked, setMapClicked] = useState<PolygonClickedProps>(); const [mapClicked, setMapClicked] = useState<PolygonClickedProps>()
const updateItem = useUpdateItem(); const updateItem = useUpdateItem()
const hasUserPermission = useHasUserPermission(); const hasUserPermission = useHasUserPermission()
useEffect(() => { useEffect(() => {
if (selectPosition && markerClicked && 'text' in selectPosition && markerClicked.id !== selectPosition.id) { if (selectPosition && markerClicked && 'text' in selectPosition && markerClicked.id !== selectPosition.id) {
@ -49,77 +47,74 @@ function useSelectPositionManager(): {
setSelectPosition(null) setSelectPosition(null)
} }
if ('text' in selectPosition) { if ('text' in selectPosition) {
const position = mapClicked?.position.lng && new Geometry(mapClicked?.position.lng, mapClicked?.position.lat); const position = mapClicked?.position.lng && new Geometry(mapClicked?.position.lng, mapClicked?.position.lat)
position && itemUpdatePosition({ ...selectPosition as Item, position: position }) position && itemUpdatePosition({ ...selectPosition as Item, position })
setSelectPosition(null); setSelectPosition(null)
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [mapClicked]) }, [mapClicked])
const itemUpdateParent = async (updatedItem: Item) => { const itemUpdateParent = async (updatedItem: Item) => {
if (markerClicked?.layer?.api?.collectionName && hasUserPermission(markerClicked?.layer?.api?.collectionName, "update", markerClicked)) { if (markerClicked?.layer?.api?.collectionName && hasUserPermission(markerClicked?.layer?.api?.collectionName, 'update', markerClicked)) {
let success = false; let success = false
try { try {
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, parent: updatedItem.parent, position: null }) await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, parent: updatedItem.parent, position: null })
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
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.success('Item position updated')
setSelectPosition(null); setSelectPosition(null)
setMarkerClicked(null); setMarkerClicked(null)
} }
} else {
setSelectPosition(null)
toast.error("you don't have permission to add items to " + markerClicked?.name)
} }
else {
setSelectPosition(null);
toast.error("you don't have permission to add items to " + markerClicked?.name);
}
} }
const itemUpdatePosition = async (updatedItem: Item) => { const itemUpdatePosition = async (updatedItem: Item) => {
let success = false; let success = false
try { try {
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, position: updatedItem.position }) await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, position: updatedItem.position })
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
updateItem(updatedItem) updateItem(updatedItem)
toast.success("Item position updated"); toast.success('Item position updated')
} }
} }
const linkItem = async (id: string) => { const linkItem = async (id: string) => {
if (markerClicked) { if (markerClicked) {
const new_relations = markerClicked.relations || []; const newRelations = markerClicked.relations || []
if (!new_relations.some(r => r.related_items_id == id)) { if (!newRelations.some(r => r.related_items_id === id)) {
new_relations?.push({ items_id: markerClicked.id, related_items_id: id }) newRelations?.push({ items_id: markerClicked.id, related_items_id: id })
const updatedItem = { id: markerClicked.id, relations: new_relations } const updatedItem = { id: markerClicked.id, relations: newRelations }
let success = false; let success = false
try { try {
await markerClicked?.layer?.api?.updateItem!(updatedItem) await markerClicked?.layer?.api?.updateItem!(updatedItem)
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
updateItem({ ...markerClicked, relations: new_relations }) updateItem({ ...markerClicked, relations: newRelations })
toast.success("Item linked"); toast.success('Item linked')
} }
} }
} }
} }
return { selectPosition, setSelectPosition, setMarkerClicked, setMapClicked }; return { selectPosition, setSelectPosition, setMarkerClicked, setMapClicked }
} }
export const SelectPositionProvider: React.FunctionComponent<{ export const SelectPositionProvider: React.FunctionComponent<{
@ -128,24 +123,24 @@ export const SelectPositionProvider: React.FunctionComponent<{
<SelectPositionContext.Provider value={useSelectPositionManager()}> <SelectPositionContext.Provider value={useSelectPositionManager()}>
{children} {children}
</SelectPositionContext.Provider> </SelectPositionContext.Provider>
); )
export const useSelectPosition = (): Item | LayerProps | null => { export const useSelectPosition = (): Item | LayerProps | null => {
const { selectPosition } = useContext(SelectPositionContext); const { selectPosition } = useContext(SelectPositionContext)
return selectPosition; return selectPosition
};
export const useSetSelectPosition = (): UseSelectPositionManagerResult["setSelectPosition"] => {
const { setSelectPosition } = useContext(SelectPositionContext);
return setSelectPosition;
} }
export const useSetMarkerClicked = (): UseSelectPositionManagerResult["setMarkerClicked"] => { export const useSetSelectPosition = (): UseSelectPositionManagerResult['setSelectPosition'] => {
const { setMarkerClicked } = useContext(SelectPositionContext); const { setSelectPosition } = useContext(SelectPositionContext)
return setMarkerClicked; return setSelectPosition
} }
export const useSetMapClicked = (): UseSelectPositionManagerResult["setMapClicked"] => { export const useSetMarkerClicked = (): UseSelectPositionManagerResult['setMarkerClicked'] => {
const { setMapClicked } = useContext(SelectPositionContext); const { setMarkerClicked } = useContext(SelectPositionContext)
return setMapClicked; return setMarkerClicked
}
export const useSetMapClicked = (): UseSelectPositionManagerResult['setMapClicked'] => {
const { setMapClicked } = useContext(SelectPositionContext)
return setMapClicked
} }

View File

@ -1,13 +1,13 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import { useCallback, useReducer, createContext, useContext, useState } from "react"; import { useCallback, useReducer, createContext, useContext, useState } from 'react'
import * as React from "react"; import * as React from 'react'
import { Item, ItemsApi, Tag } from "../../../types"; import { Item, ItemsApi, Tag } from '../../../types'
import { hashTagRegex } from "../../../Utils/HashTagRegex"; import { hashTagRegex } from '../../../Utils/HashTagRegex'
import { getValue } from "../../../Utils/GetValue"; import { getValue } from '../../../Utils/GetValue'
type ActionType = type ActionType =
| { type: "ADD"; tag: Tag } | { type: 'ADD'; tag: Tag }
| { type: "REMOVE"; id: string }; | { type: 'REMOVE'; id: string };
type UseTagManagerResult = ReturnType<typeof useTagsManager>; type UseTagManagerResult = ReturnType<typeof useTagsManager>;
@ -18,7 +18,7 @@ const TagContext = createContext<UseTagManagerResult>({
setTagData: () => { }, setTagData: () => { },
getItemTags: () => [], getItemTags: () => [],
allTagsLoaded: false allTagsLoaded: false
}); })
function useTagsManager (initialTags: Tag[]): { function useTagsManager (initialTags: Tag[]): {
tags: Tag[]; tags: Tag[];
@ -28,92 +28,90 @@ function useTagsManager(initialTags: Tag[]): {
getItemTags: (item: Item) => Tag[]; getItemTags: (item: Item) => Tag[];
allTagsLoaded: boolean allTagsLoaded: boolean
} { } {
const [allTagsLoaded, setallTagsLoaded] = useState<boolean>(false)
const [allTagsLoaded, setallTagsLoaded] = useState<boolean>(false); const [tagCount, setTagCount] = useState<number>(0)
const [tagCount, setTagCount] = useState<number>(0);
const [tags, dispatch] = useReducer((state: Tag[], action: ActionType) => { const [tags, dispatch] = useReducer((state: Tag[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD": case 'ADD':
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const exist = state.find((tag) => const exist = state.find((tag) =>
tag.name.toLocaleLowerCase() === action.tag.name.toLocaleLowerCase() ? true : false tag.name.toLocaleLowerCase() === action.tag.name.toLocaleLowerCase()
); )
if (!exist) { if (!exist) {
const newState = [ const newState = [
...state, ...state,
{ ...action.tag } { ...action.tag }
]; ]
if(tagCount == newState.length) setallTagsLoaded(true); if (tagCount === newState.length) setallTagsLoaded(true)
return newState; return newState
} } else return state
else return state;
default: default:
throw new Error(); throw new Error()
} }
}, initialTags); }, initialTags)
const [api, setApi] = React.useState<ItemsApi<Tag>>({} as ItemsApi<Tag>) const [api, setApi] = React.useState<ItemsApi<Tag>>({} as ItemsApi<Tag>)
const setTagApi = useCallback(async (api: ItemsApi<Tag>) => { const setTagApi = useCallback(async (api: ItemsApi<Tag>) => {
setApi(api); setApi(api)
const result = await api.getItems(); const result = await api.getItems()
setTagCount(result.length); setTagCount(result.length)
if(tagCount == 0) setallTagsLoaded(true); if (tagCount === 0) setallTagsLoaded(true)
if (result) { if (result) {
result.map(tag => { result.map(tag => {
// tag.name = tag.name.toLocaleLowerCase(); // tag.name = tag.name.toLocaleLowerCase();
dispatch({ type: "ADD", tag }); dispatch({ type: 'ADD', tag })
return null
}) })
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const setTagData = useCallback((data: Tag[]) => { const setTagData = useCallback((data: Tag[]) => {
data.map(tag => { data.map(tag => {
// tag.name = tag.name.toLocaleLowerCase(); // tag.name = tag.name.toLocaleLowerCase();
dispatch({ type: "ADD", tag }) dispatch({ type: 'ADD', tag })
return null
}) })
}, []); }, [])
const addTag = (tag: Tag) => { const addTag = (tag: Tag) => {
dispatch({ dispatch({
type: "ADD", type: 'ADD',
tag, tag
}); })
if (!tags.some((t) => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) { if (!tags.some((t) => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) {
api?.createItem && api.createItem(tag); api?.createItem && api.createItem(tag)
}
} }
};
const getItemTags = useCallback((item: Item) => { const getItemTags = useCallback((item: Item) => {
const text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined; const 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 => {
if (tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { if (tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
itemTags.push(tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())!) itemTags.push(tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())!)
} }
return null
}) })
item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => { item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(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.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => { item.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
const need = tags.find(t=>t.id === n.tags_id); const need = tags.find(t => t.id === n.tags_id)
need && itemTags.push(need); need && itemTags.push(need)
}); return null
})
return itemTags return itemTags
}, [tags]); }, [tags])
return { tags, addTag, setTagApi, setTagData, getItemTags, allTagsLoaded }
return { tags, addTag, setTagApi, setTagData, getItemTags, allTagsLoaded };
} }
export const TagsProvider: React.FunctionComponent<{ export const TagsProvider: React.FunctionComponent<{
@ -122,35 +120,34 @@ export const TagsProvider: React.FunctionComponent<{
<TagContext.Provider value={useTagsManager(initialTags)}> <TagContext.Provider value={useTagsManager(initialTags)}>
{children} {children}
</TagContext.Provider> </TagContext.Provider>
); )
export const useTags = (): Tag[] => { export const useTags = (): Tag[] => {
const { tags } = useContext(TagContext); const { tags } = useContext(TagContext)
return tags; return tags
};
export const useAddTag = (): UseTagManagerResult["addTag"] => {
const { addTag } = useContext(TagContext);
return addTag;
};
export const useSetTagApi = (): UseTagManagerResult["setTagApi"] => {
const { setTagApi } = useContext(TagContext);
return setTagApi;
} }
export const useSetTagData = (): UseTagManagerResult["setTagData"] => { export const useAddTag = (): UseTagManagerResult['addTag'] => {
const { setTagData } = useContext(TagContext); const { addTag } = useContext(TagContext)
return setTagData; return addTag
} }
export const useSetTagApi = (): UseTagManagerResult['setTagApi'] => {
export const useGetItemTags = (): UseTagManagerResult["getItemTags"] => { const { setTagApi } = useContext(TagContext)
const { getItemTags } = useContext(TagContext); return setTagApi
return getItemTags;
} }
export const useAllTagsLoaded = (): UseTagManagerResult["allTagsLoaded"] => { export const useSetTagData = (): UseTagManagerResult['setTagData'] => {
const { allTagsLoaded } = useContext(TagContext); const { setTagData } = useContext(TagContext)
return allTagsLoaded; return setTagData
}
export const useGetItemTags = (): UseTagManagerResult['getItemTags'] => {
const { getItemTags } = useContext(TagContext)
return getItemTags
}
export const useAllTagsLoaded = (): UseTagManagerResult['allTagsLoaded'] => {
const { allTagsLoaded } = useContext(TagContext)
return allTagsLoaded
} }

View File

@ -1,30 +1,30 @@
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react'
export const useTimeout = (callback, delay) => { export const useTimeout = (callback, delay) => {
const callbackRef = useRef(callback); const callbackRef = useRef(callback)
const timeoutRef = useRef<any>(); const timeoutRef = useRef<any>()
useEffect(() => { useEffect(() => {
callbackRef.current = callback; callbackRef.current = callback
}, [callback]); }, [callback])
const set = useCallback(() => { const set = useCallback(() => {
timeoutRef.current = setTimeout(() => callbackRef.current(), delay); timeoutRef.current = setTimeout(() => callbackRef.current(), delay)
}, [delay]); }, [delay])
const clear = useCallback(() => { const clear = useCallback(() => {
timeoutRef.current && clearTimeout(timeoutRef.current); timeoutRef.current && clearTimeout(timeoutRef.current)
}, []); }, [])
useEffect(() => { useEffect(() => {
set(); set()
return clear; return clear
}, [delay, set, clear]); }, [delay, set, clear])
const reset = useCallback(() => { const reset = useCallback(() => {
clear(); clear()
set(); set()
}, [clear, set]); }, [clear, set])
return { reset, clear }; return { reset, clear }
} }

View File

@ -1,24 +1,24 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react'
function getWindowDimensions () { function getWindowDimensions () {
const { innerWidth: width, innerHeight: height } = window; const { innerWidth: width, innerHeight: height } = window
return { return {
width, width,
height height
}; }
} }
export default function useWindowDimensions () { export default function useWindowDimensions () {
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
useEffect(() => { useEffect(() => {
function handleResize () { function handleResize () {
setWindowDimensions(getWindowDimensions()); setWindowDimensions(getWindowDimensions())
} }
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize)
}, []); }, [])
return windowDimensions; return windowDimensions
} }

View File

@ -1,13 +1,13 @@
export { UtopiaMap } from './UtopiaMap'; export { UtopiaMap } from './UtopiaMap'
export { Layer } from './Layer'; export { Layer } from './Layer'
export { Tags } from "./Tags"; export { Tags } from './Tags'
export { Permissions } from "./Permissions"; export { Permissions } from './Permissions'
export {ItemForm} from './ItemForm'; export { ItemForm } from './ItemForm'
export {ItemView} from './ItemView'; export { ItemView } from './ItemView'
export {PopupTextAreaInput} from './Subcomponents/ItemPopupComponents/PopupTextAreaInput'; export { PopupTextAreaInput } from './Subcomponents/ItemPopupComponents/PopupTextAreaInput'
export {PopupStartEndInput} from './Subcomponents/ItemPopupComponents/PopupStartEndInput'; export { PopupStartEndInput } from './Subcomponents/ItemPopupComponents/PopupStartEndInput'
export {PopupTextInput} from './Subcomponents/ItemPopupComponents/PopupTextInput'; export { PopupTextInput } from './Subcomponents/ItemPopupComponents/PopupTextInput'
export { PopupCheckboxInput } from './Subcomponents/ItemPopupComponents/PopupCheckboxInput' export { PopupCheckboxInput } from './Subcomponents/ItemPopupComponents/PopupCheckboxInput'
export {TextView} from './Subcomponents/ItemPopupComponents/TextView'; export { TextView } from './Subcomponents/ItemPopupComponents/TextView'
export { StartEndView } from './Subcomponents/ItemPopupComponents/StartEndView' export { StartEndView } from './Subcomponents/ItemPopupComponents/StartEndView'
export { PopupButton } from './Subcomponents/ItemPopupComponents/PopupButton' export { PopupButton } from './Subcomponents/ItemPopupComponents/PopupButton'

View File

@ -1,9 +1,8 @@
import { useMap } from "react-leaflet" import { useMap } from 'react-leaflet'
export const setItemLocation = () => { export const setItemLocation = () => {
// eslint-disable-next-line no-unused-vars, react-hooks/rules-of-hooks // eslint-disable-next-line no-unused-vars, react-hooks/rules-of-hooks
const map = useMap(); const map = useMap()
return ( return (
<div></div> <div></div>

View File

@ -1,74 +1,72 @@
/* eslint-disable no-constant-condition */ /* eslint-disable no-constant-condition */
import { useItems, useUpdateItem, useAddItem } from '../Map/hooks/useItems' import { useItems, useUpdateItem, useAddItem } from '../Map/hooks/useItems'
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react'
import { getValue } from '../../Utils/GetValue'; import { getValue } from '../../Utils/GetValue'
import { useAuth } from '../Auth'; import { useAuth } from '../Auth'
import { useAddTag, useGetItemTags, useTags } from '../Map/hooks/useTags'; import { useAddTag, useGetItemTags, useTags } from '../Map/hooks/useTags'
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom'
import { Item, Tag } from '../../types'; import { Item, Tag } from '../../types'
import { MapOverlayPage } from '../Templates'; import { MapOverlayPage } from '../Templates'
import { useLayers } from '../Map/hooks/useLayers'; import { useLayers } from '../Map/hooks/useLayers'
import { useHasUserPermission } from '../Map/hooks/usePermissions'; import { useHasUserPermission } from '../Map/hooks/usePermissions'
import { OnepagerForm } from './Templates/OnepagerForm'; import { OnepagerForm } from './Templates/OnepagerForm'
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'; import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
import { SimpleForm } from './Templates/SimpleForm'; import { SimpleForm } from './Templates/SimpleForm'
import { TabsForm } from './Templates/TabsForm'; import { TabsForm } from './Templates/TabsForm'
import { FormHeader } from './Subcomponents/FormHeader'; import { FormHeader } from './Subcomponents/FormHeader'
export function ProfileForm ({ userType }: { userType: string }) { export function ProfileForm ({ userType }: { userType: string }) {
const [state, setState] = useState({ const [state, setState] = useState({
color: "", color: '',
id: "", id: '',
groupType: "wuerdekompass", groupType: 'wuerdekompass',
status: "active", status: 'active',
name: "", name: '',
subname: "", subname: '',
text: "", text: '',
contact: "", contact: '',
telephone: "", telephone: '',
nextAppointment: "", nextAppointment: '',
image: "", image: '',
markerIcon: "", markerIcon: '',
offers: [] as Tag[], offers: [] as Tag[],
needs: [] as Tag[], needs: [] as Tag[],
relations: [] as Item[], relations: [] as Item[],
start: "", start: '',
end: "" end: ''
}); })
const [updatePermission, setUpdatePermission] = useState<boolean>(false); const [updatePermission, setUpdatePermission] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false)
const [item, setItem] = useState<Item>({} as Item) const [item, setItem] = useState<Item>({} as Item)
const { user } = useAuth(); const { user } = useAuth()
const updateItem = useUpdateItem(); const updateItem = useUpdateItem()
const addItem = useAddItem(); const addItem = useAddItem()
const layers = useLayers(); const layers = useLayers()
const location = useLocation(); const location = useLocation()
const tags = useTags(); const tags = useTags()
const addTag = useAddTag(); const addTag = useAddTag()
const navigate = useNavigate(); const navigate = useNavigate()
const hasUserPermission = useHasUserPermission(); const hasUserPermission = useHasUserPermission()
const getItemTags = useGetItemTags(); const getItemTags = useGetItemTags()
const items = useItems(); const items = useItems()
const [urlParams, setUrlParams] = useState(new URLSearchParams(location.search));
const [urlParams, setUrlParams] = useState(new URLSearchParams(location.search))
useEffect(() => { useEffect(() => {
item && hasUserPermission("items", "update", item) && setUpdatePermission(true); item && hasUserPermission('items', 'update', item) && setUpdatePermission(true)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [item]) }, [item])
useEffect(() => { useEffect(() => {
const itemId = location.pathname.split("/")[2]; const itemId = location.pathname.split('/')[2]
const item = items.find(i => i.id === itemId); const item = items.find(i => i.id === itemId)
item && setItem(item); item && setItem(item)
const layer = layers.find(l => l.itemType.name == userType) const layer = layers.find(l => l.itemType.name === userType)
!item && setItem({ id: crypto.randomUUID(), name: user ? user.first_name : "", text: "", layer: layer, new: true }) !item && setItem({ id: crypto.randomUUID(), name: user ? user.first_name : '', text: '', layer, new: true })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [items]) }, [items])
@ -78,52 +76,52 @@ export function ProfileForm({ userType }: { userType: string }) {
? getValue(item, item.layer?.itemColorField) ? 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)
if (offer) acc.push(offer); if (offer) acc.push(offer)
return acc; return acc
}, []); }, [])
const needs = (item?.needs ?? []).reduce((acc: Tag[], o) => { const needs = (item?.needs ?? []).reduce((acc: Tag[], o) => {
const need = tags.find(t => t.id === o.tags_id); const need = tags.find(t => t.id === o.tags_id)
if (need) acc.push(need); if (need) acc.push(need)
return acc; return acc
}, []); }, [])
const relations = (item?.relations ?? []).reduce((acc: Item[], r) => { const relations = (item?.relations ?? []).reduce((acc: Item[], r) => {
const relatedItem = items.find(i => i.id === r.related_items_id); const relatedItem = items.find(i => i.id === r.related_items_id)
if (relatedItem) acc.push(relatedItem); if (relatedItem) acc.push(relatedItem)
return acc; return acc
}, []); }, [])
setState({ setState({
color: newColor, color: newColor,
id: item?.id ?? "", id: item?.id ?? '',
groupType: item?.group_type ?? "wuerdekompass", groupType: item?.group_type ?? 'wuerdekompass',
status: item?.status ?? "active", status: item?.status ?? 'active',
name: item?.name ?? "", name: item?.name ?? '',
subname: item?.subname ?? "", subname: item?.subname ?? '',
text: item?.text ?? "", text: item?.text ?? '',
contact: item?.contact ?? "", contact: item?.contact ?? '',
telephone: item?.telephone ?? "", telephone: item?.telephone ?? '',
nextAppointment: item?.next_appointment ?? "", nextAppointment: item?.next_appointment ?? '',
image: item?.image ?? "", image: item?.image ?? '',
markerIcon: item?.marker_icon ?? "", markerIcon: item?.marker_icon ?? '',
offers: offers, offers,
needs: needs, needs,
relations: relations, relations,
start: item?.start ?? "", start: item?.start ?? '',
end: item?.end ?? "" end: item?.end ?? ''
}); })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, tags, items]); }, [item, tags, items])
const [template, setTemplate] = useState<string>("") const [template, setTemplate] = useState<string>('')
useEffect(() => { useEffect(() => {
setTemplate(item.layer?.itemType.template || userType); setTemplate(item.layer?.itemType.template || userType)
}, [userType, item]) }, [userType, item])
return ( return (
@ -134,20 +132,20 @@ export function ProfileForm({ userType }: { userType: string }) {
<FormHeader item={item} state={state} setState={setState} /> <FormHeader item={item} state={state} setState={setState} />
{template == "onepager" && ( {template === 'onepager' && (
<OnepagerForm item={item} state={state} setState={setState}></OnepagerForm> <OnepagerForm item={item} state={state} setState={setState}></OnepagerForm>
)} )}
{template == "simple" && {template === 'simple' &&
<SimpleForm state={state} setState={setState}></SimpleForm> <SimpleForm state={state} setState={setState}></SimpleForm>
} }
{template == "tabs" && {template === 'tabs' &&
<TabsForm loading={loading} item={item} state={state} setState={setState} updatePermission={updatePermission} linkItem={(id) => linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)} setUrlParams={setUrlParams}></TabsForm> <TabsForm loading={loading} item={item} state={state} setState={setState} updatePermission={updatePermission} linkItem={(id) => linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)} setUrlParams={setUrlParams}></TabsForm>
} }
<div className="tw-mt-4 tw-mb-4"> <div className="tw-mt-4 tw-mb-4">
<button className={loading ? " tw-loading tw-btn tw-float-right" : "tw-btn tw-float-right"} onClick={() => onUpdateItem(state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, urlParams)} style={true ? { 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)}`, color: "#fff" } : { color: "#fff" }}>Update</button> <button className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'} onClick={() => onUpdateItem(state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, urlParams)} style={true ? { 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)}`, color: '#fff' } : { color: '#fff' }}>Update</button>
</div> </div>
</div> </div>

View File

@ -1,164 +1,161 @@
import { MapOverlayPage } from '../Templates' import { MapOverlayPage } from '../Templates'
import { useItems, useRemoveItem, useUpdateItem } from '../Map/hooks/useItems' import { useItems, useRemoveItem, useUpdateItem } from '../Map/hooks/useItems'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react'
import { Item, ItemsApi, Tag } from '../../types'; import { Item, ItemsApi, Tag } from '../../types'
import { useMap } from 'react-leaflet'; import { useMap } from 'react-leaflet'
import { LatLng } from 'leaflet'; import { LatLng } from 'leaflet'
import { useHasUserPermission } from '../Map/hooks/usePermissions'; import { useHasUserPermission } from '../Map/hooks/usePermissions'
import { HeaderView } from '../Map/Subcomponents/ItemPopupComponents/HeaderView'; import { HeaderView } from '../Map/Subcomponents/ItemPopupComponents/HeaderView'
import { useSelectPosition, useSetSelectPosition } from '../Map/hooks/useSelectPosition'; import { useSelectPosition, useSetSelectPosition } from '../Map/hooks/useSelectPosition'
import { useClusterRef } from '../Map/hooks/useClusterRef'; import { useClusterRef } from '../Map/hooks/useClusterRef'
import { useLeafletRefs } from '../Map/hooks/useLeafletRefs'; import { useLeafletRefs } from '../Map/hooks/useLeafletRefs'
import { getValue } from '../../Utils/GetValue'; import { getValue } from '../../Utils/GetValue'
import { TabsView } from './Templates/TabsView'; import { TabsView } from './Templates/TabsView'
import { OnepagerView } from './Templates/OnepagerView'; import { OnepagerView } from './Templates/OnepagerView'
import { SimpleView } from './Templates/SimpleView'; import { SimpleView } from './Templates/SimpleView'
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'; import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
import { useTags } from '../Map/hooks/useTags'; import { useTags } from '../Map/hooks/useTags'
export function ProfileView ({ userType, attestationApi }: { userType: string, attestationApi?: ItemsApi<any>}) { export function ProfileView ({ userType, attestationApi }: { userType: string, attestationApi?: ItemsApi<any>}) {
const [item, setItem] = useState<Item>() const [item, setItem] = useState<Item>()
const [updatePermission, setUpdatePermission] = useState<boolean>(false); const [updatePermission, setUpdatePermission] = useState<boolean>(false)
const [relations, setRelations] = useState<Array<Item>>([]); const [relations, setRelations] = useState<Array<Item>>([])
const [offers, setOffers] = useState<Array<Tag>>([]); const [offers, setOffers] = useState<Array<Tag>>([])
const [needs, setNeeds] = useState<Array<Tag>>([]); const [needs, setNeeds] = useState<Array<Tag>>([])
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false)
const [template, setTemplate] = useState<string>(""); const [template, setTemplate] = useState<string>('')
const location = useLocation(); const location = useLocation()
const items = useItems(); const items = useItems()
const updateItem = useUpdateItem(); const updateItem = useUpdateItem()
const map = useMap(); const map = useMap()
const selectPosition = useSelectPosition(); const selectPosition = useSelectPosition()
const removeItem = useRemoveItem(); const removeItem = useRemoveItem()
const tags = useTags(); const tags = useTags()
const navigate = useNavigate(); const navigate = useNavigate()
const hasUserPermission = useHasUserPermission(); const hasUserPermission = useHasUserPermission()
const setSelectPosition = useSetSelectPosition(); const setSelectPosition = useSetSelectPosition()
const clusterRef = useClusterRef(); const clusterRef = useClusterRef()
const leafletRefs = useLeafletRefs(); const leafletRefs = useLeafletRefs()
const [attestations, setAttestations] = useState<Array<any>>([]); const [attestations, setAttestations] = useState<Array<any>>([])
useEffect(() => { useEffect(() => {
if (attestationApi) { if (attestationApi) {
attestationApi.getItems() attestationApi.getItems()
.then(value => { .then(value => {
console.log(value); console.log(value)
setAttestations(value); setAttestations(value)
}) })
.catch(error => { .catch(error => {
console.error("Error fetching items:", error); console.error('Error fetching items:', error)
}); })
} }
}, [attestationApi]) }, [attestationApi])
useEffect(() => { useEffect(() => {
const itemId = location.pathname.split("/")[2]; const itemId = location.pathname.split('/')[2]
const item = items.find(i => i.id === itemId); const item = items.find(i => i.id === itemId)
item && setItem(item); item && setItem(item)
}, [items, location]) }, [items, location])
useEffect(() => { useEffect(() => {
setOffers([]); setOffers([])
setNeeds([]); setNeeds([])
setRelations([]); setRelations([])
item?.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => { item?.layer?.itemOffersField && 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?.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => { item?.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
const tag = tags.find(t => t.id === n.tags_id); const tag = tags.find(t => t.id === n.tags_id)
tag && setNeeds(current => [...current, tag]) tag && setNeeds(current => [...current, tag])
return null
}) })
item?.relations?.map(r => { 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 = async (marker, x) => {
await map.setView(new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4), undefined); await map.setView(new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4), undefined)
setTimeout(() => { setTimeout(() => {
marker.openPopup(); marker.openPopup()
}, 500); }, 500)
} }
if (item) { if (item) {
if (item.position) { if (item.position) {
const marker = Object.entries(leafletRefs).find(r => r[1].item == item)?.[1].marker; const marker = Object.entries(leafletRefs).find(r => r[1].item === item)?.[1].marker
marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => { marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => {
const bounds = map.getBounds(); const bounds = map.getBounds()
const x = bounds.getEast() - bounds.getWest(); const x = bounds.getEast() - bounds.getWest()
setMap(marker, x); setMap(marker, x)
} }
); )
} } else {
else { const parent = getFirstAncestor(item)
const parent = getFirstAncestor(item); const marker = Object.entries(leafletRefs).find(r => r[1].item === parent)?.[1].marker
const marker = Object.entries(leafletRefs).find(r => r[1].item == parent)?.[1].marker;
marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => { marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => {
const bounds = map.getBounds(); const bounds = map.getBounds()
const x = bounds.getEast() - bounds.getWest(); const x = bounds.getEast() - bounds.getWest()
setMap(marker, x); setMap(marker, x)
} }
); )
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [item]) }, [item])
const getFirstAncestor = (item: Item): Item | undefined => { const getFirstAncestor = (item: Item): Item | undefined => {
const parent = items.find(i => i.id === item.parent); const parent = items.find(i => i.id === item.parent)
if (parent?.parent) { if (parent?.parent) {
return getFirstAncestor(parent); return getFirstAncestor(parent)
} else { } else {
return parent; return parent
}
} }
};
useEffect(() => { useEffect(() => {
item && hasUserPermission("items", "update", item) && setUpdatePermission(true); item && hasUserPermission('items', 'update', item) && setUpdatePermission(true)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [item]) }, [item])
useEffect(() => { useEffect(() => {
selectPosition && map.closePopup(); selectPosition && map.closePopup()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectPosition]) }, [selectPosition])
useEffect(() => { useEffect(() => {
setTemplate(item?.layer?.itemType.template || userType); setTemplate(item?.layer?.itemType.template || userType)
}, [userType, item]) }, [userType, item])
return ( return (
<> <>
{item && {item &&
<MapOverlayPage key={item.id} className={`!tw-p-0 tw-mx-4 tw-mt-4 tw-mb-4 md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-min-w-80 tw-max-w-3xl !tw-left-0 sm:!tw-left-auto tw-top-0 tw-bottom-0 tw-transition-opacity tw-duration-500 ${!selectPosition ? 'tw-opacity-100 tw-pointer-events-auto' : 'tw-opacity-0 tw-pointer-events-none'}`}> <MapOverlayPage key={item.id} className={`!tw-p-0 tw-mx-4 tw-mt-4 tw-mb-4 md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-min-w-80 tw-max-w-3xl !tw-left-0 sm:!tw-left-auto tw-top-0 tw-bottom-0 tw-transition-opacity tw-duration-500 ${!selectPosition ? 'tw-opacity-100 tw-pointer-events-auto' : 'tw-opacity-0 tw-pointer-events-none'}`}>
<> <>
<div className={`tw-px-6 tw-pt-6`}> <div className={'tw-px-6 tw-pt-6'}>
<HeaderView api={item.layer?.api} item={item} deleteCallback={(e) => handleDelete(e, item, setLoading, removeItem, map, navigate)} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate("/") }} big truncateSubname={false} /> <HeaderView api={item.layer?.api} item={item} deleteCallback={(e) => handleDelete(e, item, setLoading, removeItem, map, navigate)} editCallback={() => navigate('/edit-item/' + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate('/') }} big truncateSubname={false} />
</div> </div>
{template == "onepager" && {template === 'onepager' &&
<OnepagerView item={item} userType={userType}/> <OnepagerView item={item} userType={userType}/>
} }
{template == "simple" && {template === 'simple' &&
<SimpleView item={item}/> <SimpleView item={item}/>
} }
{template == "tabs" && {template === 'tabs' &&
<TabsView userType={userType} attestations={attestations} item={item} loading={loading} offers={offers} needs={needs} relations={relations} updatePermission={updatePermission} linkItem={(id) => linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}/> <TabsView userType={userType} attestations={attestations} item={item} loading={loading} offers={offers} needs={needs} relations={relations} updatePermission={updatePermission} linkItem={(id) => linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}/>
} }
</> </>

View File

@ -1,14 +1,14 @@
import { useState } from "react"; import { useState } from 'react'
import { useHasUserPermission } from "../../Map/hooks/usePermissions"; import { useHasUserPermission } from '../../Map/hooks/usePermissions'
import DialogModal from "../../Templates/DialogModal"; import DialogModal from '../../Templates/DialogModal'
import { useItems } from "../../Map/hooks/useItems"; import { useItems } from '../../Map/hooks/useItems'
import { HeaderView } from "../../Map/Subcomponents/ItemPopupComponents/HeaderView"; import { HeaderView } from '../../Map/Subcomponents/ItemPopupComponents/HeaderView'
import { Item } from "../../../types"; import { Item } from '../../../types'
import { TextInput } from "../../Input"; import { TextInput } from '../../Input'
import { getValue } from "../../../Utils/GetValue"; import { getValue } from '../../../Utils/GetValue'
import { useGetItemTags } from "../../Map/hooks/useTags"; import { useGetItemTags } from '../../Map/hooks/useTags'
export function ActionButton({ item, triggerAddButton, triggerItemSelected, existingRelations, itemType, colorField, collection = "items", customStyle }: { export function ActionButton ({ item, triggerAddButton, triggerItemSelected, existingRelations, itemType, colorField, collection = 'items', customStyle }: {
triggerAddButton?: any, triggerAddButton?: any,
triggerItemSelected?: any, triggerItemSelected?: any,
existingRelations: Item[], existingRelations: Item[],
@ -18,39 +18,36 @@ export function ActionButton({ item, triggerAddButton, triggerItemSelected, exis
customStyle?:string, customStyle?:string,
item: Item item: Item
}) { }) {
const hasUserPermission = useHasUserPermission(); const hasUserPermission = useHasUserPermission()
const [modalOpen, setModalOpen] = useState<boolean>(false); const [modalOpen, setModalOpen] = useState<boolean>(false)
const [search, setSearch] = useState<string>(""); const [search, setSearch] = useState<string>('')
const getItemTags = useGetItemTags(); const getItemTags = useGetItemTags()
const items = useItems();
const filterdItems = items.filter(i => !itemType || i.layer?.itemType.name == itemType).filter(i => !existingRelations.some(s => s.id == i.id)).filter(i => i.id != item.id)
const items = useItems()
const filterdItems = items.filter(i => !itemType || i.layer?.itemType.name === itemType).filter(i => !existingRelations.some(s => s.id === i.id)).filter(i => i.id !== item.id)
return ( return (
<>{hasUserPermission(collection, "update", item) && <>{hasUserPermission(collection, 'update', item) &&
<> <>
<div className={`tw-absolute tw-right-4 tw-bottom-4 tw-flex tw-flex-col ${customStyle}`} > <div className={`tw-absolute tw-right-4 tw-bottom-4 tw-flex tw-flex-col ${customStyle}`} >
{triggerItemSelected && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { 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)}`, color: "#fff" }}> {triggerItemSelected && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { 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)}`, color: '#fff' }}>
<svg className="tw-h-5 tw-w-5" stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"></path></svg> <svg className="tw-h-5 tw-w-5" stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"></path></svg>
</button>} </button>}
{triggerAddButton && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow tw-mt-2" onClick={() => { 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)}`, color: "#fff" }}> {triggerAddButton && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow tw-mt-2" onClick={() => { 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)}`, color: '#fff' }}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="4" stroke="currentColor" className="tw-w-5 tw-h-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="4" stroke="currentColor" className="tw-w-5 tw-h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> <path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg> </svg>
</button>} </button>}
</div> </div>
<DialogModal title={"Select"} isOpened={modalOpen} onClose={() => (setModalOpen(false))} className="tw-w-xl sm:tw-w-2xl tw-min-h-80 tw-bg-base-200"> <DialogModal title={'Select'} isOpened={modalOpen} onClose={() => (setModalOpen(false))} className="tw-w-xl sm:tw-w-2xl tw-min-h-80 tw-bg-base-200">
<TextInput defaultValue='' placeholder='🔍 Search' containerStyle='lg:col-span-2 tw-m-4 ' updateFormValue={(val) => { setSearch(val) }}></TextInput> <TextInput defaultValue='' placeholder='🔍 Search' containerStyle='lg:col-span-2 tw-m-4 ' updateFormValue={(val) => { setSearch(val) }}></TextInput>
<div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2'> <div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2'>
{filterdItems.filter(item => { {filterdItems.filter(item => {
return search === '' return search === ''
? item : ? item
item.name.toLowerCase().includes(search.toLowerCase()); : item.name.toLowerCase().includes(search.toLowerCase())
}).map(i => <div key={i.id} 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-mx-4 tw-p-4 tw-mb-4 tw-h-fit' onClick={() => { triggerItemSelected(i.id); setModalOpen(false) }}> }).map(i => <div key={i.id} 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-mx-4 tw-p-4 tw-mb-4 tw-h-fit' onClick={() => { triggerItemSelected(i.id); setModalOpen(false) }}>
<HeaderView item={i} hideMenu></HeaderView> <HeaderView item={i} hideMenu></HeaderView>
</div>)} </div>)}

View File

@ -1,9 +1,9 @@
import * as React from "react"; import * as React from 'react'
import { useState, useCallback, useRef } from "react"; import { useState, useCallback, useRef } from 'react'
import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop'; import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop'
import { useAssetApi } from '../../AppShell/hooks/useAssets'; import { useAssetApi } from '../../AppShell/hooks/useAssets'
import 'react-image-crop/dist/ReactCrop.css'; import 'react-image-crop/dist/ReactCrop.css'
import DialogModal from "../../Templates/DialogModal"; import DialogModal from '../../Templates/DialogModal'
interface AvatarWidgetProps { interface AvatarWidgetProps {
avatar: string; avatar: string;
@ -11,108 +11,108 @@ interface AvatarWidgetProps {
} }
export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar }) => { export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar }) => {
const [crop, setCrop] = useState<Crop>(); const [crop, setCrop] = useState<Crop>()
const [image, setImage] = useState<string>(""); const [image, setImage] = useState<string>('')
const [cropModalOpen, setCropModalOpen] = useState<boolean>(false); const [cropModalOpen, setCropModalOpen] = useState<boolean>(false)
const [cropping, setCropping] = useState<boolean>(false); const [cropping, setCropping] = useState<boolean>(false)
const assetsApi = useAssetApi(); const assetsApi = useAssetApi()
const imgRef = useRef<HTMLImageElement>(null); const imgRef = useRef<HTMLImageElement>(null)
const onImageChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { const onImageChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files && event.target.files[0]; const file = event.target.files && event.target.files[0]
if (file) { if (file) {
const validFormats = ["image/jpeg", "image/png"]; const validFormats = ['image/jpeg', 'image/png']
const maxSizeMB = 10; const maxSizeMB = 10
const maxSizeBytes = maxSizeMB * 1024 * 1024; const maxSizeBytes = maxSizeMB * 1024 * 1024
if (!validFormats.includes(file.type)) { if (!validFormats.includes(file.type)) {
alert("Unsupported file format. Please upload a JPEG or PNG image."); alert('Unsupported file format. Please upload a JPEG or PNG image.')
return; return
} }
if (file.size > maxSizeBytes) { if (file.size > maxSizeBytes) {
alert(`File size exceeds ${maxSizeMB}MB. Please upload a smaller image.`); alert(`File size exceeds ${maxSizeMB}MB. Please upload a smaller image.`)
return; return
} }
setImage(URL.createObjectURL(file)); setImage(URL.createObjectURL(file))
setCropModalOpen(true); setCropModalOpen(true)
} else { } else {
alert("No file selected or an error occurred while selecting the file."); alert('No file selected or an error occurred while selecting the file.')
} }
}, []); }, [])
const onImageLoad = useCallback((e: React.SyntheticEvent<HTMLImageElement>) => { const onImageLoad = useCallback((e: React.SyntheticEvent<HTMLImageElement>) => {
const { width, height } = e.currentTarget; const { width, height } = e.currentTarget
setCrop(centerAspectCrop(width, height, 1)); setCrop(centerAspectCrop(width, height, 1))
}, []); }, [])
const centerAspectCrop = (mediaWidth: number, mediaHeight: number, aspect: number) => { const centerAspectCrop = (mediaWidth: number, mediaHeight: number, aspect: number) => {
return centerCrop( return centerCrop(
makeAspectCrop( makeAspectCrop(
{ {
unit: 'px', unit: 'px',
width: mediaWidth / 2, width: mediaWidth / 2
}, },
aspect, aspect,
mediaWidth, mediaWidth,
mediaHeight, mediaHeight
), ),
mediaWidth, mediaWidth,
mediaHeight, mediaHeight
); )
}; }
async function resizeImage (image: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<HTMLImageElement> { async function resizeImage (image: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<HTMLImageElement> {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d')
let width = image.width; let width = image.width
let height = image.height; let height = image.height
if (width > maxWidth) { if (width > maxWidth) {
height *= maxWidth / width; height *= maxWidth / width
width = maxWidth; width = maxWidth
} }
if (height > maxHeight) { if (height > maxHeight) {
width *= maxHeight / height; width *= maxHeight / height
height = maxHeight; height = maxHeight
} }
canvas.width = width; canvas.width = width
canvas.height = height; canvas.height = height
if (ctx) { if (ctx) {
ctx.drawImage(image, 0, 0, width, height); ctx.drawImage(image, 0, 0, width, height)
} }
const resizedImage = new Image(); const resizedImage = new Image()
resizedImage.src = canvas.toDataURL(); resizedImage.src = canvas.toDataURL()
await resizedImage.decode(); await resizedImage.decode()
return resizedImage; return resizedImage
} }
const renderCrop = useCallback(async () => { const renderCrop = useCallback(async () => {
const image = imgRef.current; const image = imgRef.current
if (crop && image) { if (crop && image) {
const resizedImage = await resizeImage(image, 1024, 1024); // Bildgröße vor dem Zuschneiden reduzieren const resizedImage = await resizeImage(image, 1024, 1024) // Bildgröße vor dem Zuschneiden reduzieren
const scaleX = resizedImage.naturalWidth / resizedImage.width; const scaleX = resizedImage.naturalWidth / resizedImage.width
const scaleY = resizedImage.naturalHeight / resizedImage.height; const scaleY = resizedImage.naturalHeight / resizedImage.height
const canvas = new OffscreenCanvas( const canvas = new OffscreenCanvas(
crop.width * scaleX, crop.width * scaleX,
crop.height * scaleY crop.height * scaleY
); )
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d')
const pixelRatio = window.devicePixelRatio; const pixelRatio = window.devicePixelRatio
canvas.width = crop.width * pixelRatio * scaleX; canvas.width = crop.width * pixelRatio * scaleX
canvas.height = crop.height * pixelRatio * scaleY; canvas.height = crop.height * pixelRatio * scaleY
if (ctx) { if (ctx) {
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
ctx.drawImage( ctx.drawImage(
resizedImage, resizedImage,
crop.x * scaleX, crop.x * scaleX,
@ -123,48 +123,47 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
0, 0,
crop.width * scaleX, crop.width * scaleX,
crop.height * scaleY crop.height * scaleY
); )
} }
const blob = await canvas.convertToBlob(); const blob = await canvas.convertToBlob()
await resizeBlob(blob); await resizeBlob(blob)
setCropping(false); setCropping(false)
setImage(""); setImage('')
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [crop]); }, [crop])
const resizeBlob = useCallback(async (blob: Blob) => { const resizeBlob = useCallback(async (blob: Blob) => {
const img = new Image(); const img = new Image()
img.src = URL.createObjectURL(blob); img.src = URL.createObjectURL(blob)
await img.decode(); await img.decode()
const canvas = new OffscreenCanvas(400, 400); const canvas = new OffscreenCanvas(400, 400)
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d')
ctx?.drawImage(img, 0, 0, 400, 400); ctx?.drawImage(img, 0, 0, 400, 400)
const resizedBlob = await canvas.convertToBlob(); const resizedBlob = await canvas.convertToBlob()
const asset = await assetsApi.upload(resizedBlob, "avatar"); const asset = await assetsApi.upload(resizedBlob, 'avatar')
setAvatar(asset.id); setAvatar(asset.id)
}, [assetsApi, setAvatar]); }, [assetsApi, setAvatar])
return ( return (
<> <>
{!cropping ? {!cropping
<label className="custom-file-upload"> ? <label className="custom-file-upload">
<input type="file" accept="image/*" className="tw-file-input tw-w-full tw-max-w-xs" onChange={onImageChange} /> <input type="file" accept="image/*" className="tw-file-input tw-w-full tw-max-w-xs" onChange={onImageChange} />
<div className='button tw-btn tw-btn-lg tw-btn-circle tw-animate-none'> <div className='button tw-btn tw-btn-lg tw-btn-circle tw-animate-none'>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="tw-w-6 tw-h-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="tw-w-6 tw-h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg> </svg>
</div> </div>
{avatar ? {avatar
<div className='tw-h-20 tw-w-20'> ? <div className='tw-h-20 tw-w-20'>
<img src={assetsApi.url + avatar} className='tw-h-20 tw-w-20 tw-rounded-full' /> <img src={assetsApi.url + avatar} className='tw-h-20 tw-w-20 tw-rounded-full' />
</div> </div>
: : <div className='tw-h-20 tw-w-20'>
<div className='tw-h-20 tw-w-20'> <svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 150 150" className='tw-w-20 tw-h-20 tw-rounded-full' style={{ backgroundColor: '#eee' }}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 150 150" className='tw-w-20 tw-h-20 tw-rounded-full' style={{ backgroundColor: "#eee" }}>
<path fill="#ccc" d="M 104.68731,56.689353 C 102.19435,80.640493 93.104981,97.26875 74.372196,97.26875 55.639402,97.26875 46.988823,82.308034 44.057005,57.289941 41.623314,34.938838 55.639402,15.800152 74.372196,15.800152 c 18.732785,0 32.451944,18.493971 30.315114,40.889201 z" /> <path fill="#ccc" d="M 104.68731,56.689353 C 102.19435,80.640493 93.104981,97.26875 74.372196,97.26875 55.639402,97.26875 46.988823,82.308034 44.057005,57.289941 41.623314,34.938838 55.639402,15.800152 74.372196,15.800152 c 18.732785,0 32.451944,18.493971 30.315114,40.889201 z" />
<path fill="#ccc" d="M 92.5675 89.6048 C 90.79484 93.47893 89.39893 102.4504 94.86478 106.9039 C 103.9375 114.2963 106.7064 116.4723 118.3117 118.9462 C 144.0432 124.4314 141.6492 138.1543 146.5244 149.2206 L 4.268444 149.1023 C 8.472223 138.6518 6.505799 124.7812 32.40051 118.387 C 41.80992 116.0635 45.66513 113.8823 53.58659 107.0158 C 58.52744 102.7329 57.52583 93.99267 56.43084 89.26926 C 52.49275 88.83011 94.1739 88.14054 92.5675 89.6048 z" /> <path fill="#ccc" d="M 92.5675 89.6048 C 90.79484 93.47893 89.39893 102.4504 94.86478 106.9039 C 103.9375 114.2963 106.7064 116.4723 118.3117 118.9462 C 144.0432 124.4314 141.6492 138.1543 146.5244 149.2206 L 4.268444 149.1023 C 8.472223 138.6518 6.505799 124.7812 32.40051 118.387 C 41.80992 116.0635 45.66513 113.8823 53.58659 107.0158 C 58.52744 102.7329 57.52583 93.99267 56.43084 89.26926 C 52.49275 88.83011 94.1739 88.14054 92.5675 89.6048 z" />
</svg> </svg>
@ -179,19 +178,19 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
title="" title=""
isOpened={cropModalOpen} isOpened={cropModalOpen}
onClose={() => { onClose={() => {
setCropModalOpen(false); setCropModalOpen(false)
setImage(""); setImage('')
}} }}
closeOnClickOutside={false}> closeOnClickOutside={false}>
<ReactCrop crop={crop} onChange={(c) => setCrop(c)} aspect={1} > <ReactCrop crop={crop} onChange={(c) => setCrop(c)} aspect={1} >
<img src={image} ref={imgRef} onLoad={onImageLoad} /> <img src={image} ref={imgRef} onLoad={onImageLoad} />
</ReactCrop> </ReactCrop>
<button className={`tw-btn tw-btn-primary`} onClick={() => { <button className={'tw-btn tw-btn-primary'} onClick={() => {
setCropping(true); setCropping(true)
setCropModalOpen(false); setCropModalOpen(false)
renderCrop(); renderCrop()
}}>Select</button> }}>Select</button>
</DialogModal> </DialogModal>
</> </>
); )
}; }

View File

@ -1,35 +1,34 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from 'react'
import * as React from "react"; import * as React from 'react'
import { HexColorPicker } from "react-colorful"; import { HexColorPicker } from 'react-colorful'
import "./ColorPicker.css" import './ColorPicker.css'
import useClickOutside from "../hooks/useClickOutside"; import useClickOutside from '../hooks/useClickOutside'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const ColorPicker = ({ color, onChange, className }) => { export const ColorPicker = ({ color, onChange, className }) => {
const popover = useRef<HTMLDivElement>(null); const popover = useRef<HTMLDivElement>(null)
const [isOpen, toggle] = useState(false); const [isOpen, toggle] = useState(false)
const close = useCallback(() => toggle(false), []); const close = useCallback(() => toggle(false), [])
useClickOutside(popover, close); useClickOutside(popover, close)
const colorPickerRef = React.useRef<HTMLDivElement>(null) const colorPickerRef = React.useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
// Füge dem Color-Picker explizit Event-Listener hinzu // Füge dem Color-Picker explizit Event-Listener hinzu
const colorPickerElement = colorPickerRef.current; const colorPickerElement = colorPickerRef.current
if (colorPickerElement) { if (colorPickerElement) {
const enablePropagation = (event) => { const enablePropagation = (event) => {
// Verhindere, dass Leaflet die Propagation stoppt // Verhindere, dass Leaflet die Propagation stoppt
event.stopPropagation = () => {}; event.stopPropagation = () => {}
}; };
// Event-Listener für den Color-Picker // Event-Listener für den Color-Picker
['click', 'dblclick', 'mousedown', 'touchstart'].forEach(eventType => { ['click', 'dblclick', 'mousedown', 'touchstart'].forEach(eventType => {
colorPickerElement.addEventListener(eventType, enablePropagation, true); colorPickerElement.addEventListener(eventType, enablePropagation, true)
}); })
} }
}, [])
}, []);
return ( return (
<div ref={colorPickerRef} className={`picker ${className}`}> <div ref={colorPickerRef} className={`picker ${className}`}>
@ -45,6 +44,5 @@ export const ColorPicker = ({ color, onChange, className }) => {
</div> </div>
)} )}
</div> </div>
); )
}; }

View File

@ -1,8 +1,8 @@
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
import { useAssetApi } from "../../AppShell/hooks/useAssets"; import { useAssetApi } from '../../AppShell/hooks/useAssets'
const ContactInfo = ({ email, telephone, name, avatar, link }: { email: string, telephone: string, name: string, avatar: string, link?: string }) => { const ContactInfo = ({ email, telephone, name, avatar, link }: { email: string, telephone: string, name: string, avatar: string, link?: string }) => {
const assetsApi = useAssetApi(); const assetsApi = useAssetApi()
return ( return (
<div className="tw-bg-base-200 tw-mb-6 tw-mt-6 tw-p-6"> <div className="tw-bg-base-200 tw-mb-6 tw-mt-6 tw-p-6">
@ -57,18 +57,18 @@ const ContactInfo = ({ email, telephone, name, avatar, link }: { email: string,
) )
} }
export default ContactInfo; export default ContactInfo
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const ConditionalLink = ({ url, children }) => { const ConditionalLink = ({ url, children }) => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
if (url) { if (url) {
return ( return (
<Link to={url+"?"+params}> <Link to={url + '?' + params}>
{children} {children}
</Link> </Link>
); )
}
return children
} }
return children;
};

View File

@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import { TextInput } from "../../Input" import { TextInput } from '../../Input'
import { AvatarWidget } from "./AvatarWidget" import { AvatarWidget } from './AvatarWidget'
import { ColorPicker } from "./ColorPicker" import { ColorPicker } from './ColorPicker'
export const FormHeader = ({ item, state, setState }) => { export const FormHeader = ({ item, state, setState }) => {
return ( return (
@ -13,13 +13,13 @@ export const FormHeader = ({item, state, setState}) => {
<ColorPicker color={state.color} onChange={(c) => setState(prevState => ({ <ColorPicker color={state.color} onChange={(c) => setState(prevState => ({
...prevState, ...prevState,
color: c color: c
}))} className={"-tw-left-6 tw-top-14 -tw-mr-6"} /> }))} className={'-tw-left-6 tw-top-14 -tw-mr-6'} />
<div className='tw-grow tw-mr-4'> <div className='tw-grow tw-mr-4'>
<TextInput placeholder="Name" defaultValue={item?.name ? item.name : ""} updateFormValue={(v) => setState(prevState => ({ <TextInput placeholder="Name" defaultValue={item?.name ? item.name : ''} updateFormValue={(v) => setState(prevState => ({
...prevState, ...prevState,
name: v name: v
}))} containerStyle='tw-grow tw-input-md' /> }))} containerStyle='tw-grow tw-input-md' />
<TextInput placeholder="Subtitle" defaultValue={item?.subname ? item.subname : ""} updateFormValue={(v) => setState(prevState => ({ <TextInput placeholder="Subtitle" defaultValue={item?.subname ? item.subname : ''} updateFormValue={(v) => setState(prevState => ({
...prevState, ...prevState,
subname: v subname: v
}))} containerStyle='tw-grow tw-input-sm tw-px-4 tw-mt-1' /> }))} containerStyle='tw-grow tw-input-sm tw-px-4 tw-mt-1' />

View File

@ -1,10 +1,7 @@
import { useEffect } from "react"; import { useEffect } from 'react'
import { getValue } from "../../../Utils/GetValue"; import { getValue } from '../../../Utils/GetValue'
import { Item } from "../../../types"; import { Item } from '../../../types'
import { useAssetApi } from "../../AppShell/hooks/useAssets"; import { useAssetApi } from '../../AppShell/hooks/useAssets'
export function LinkedItemsHeaderView ({ item, unlinkCallback, itemNameField, itemAvatarField, loading, unlinkPermission, itemSubnameField }: { export function LinkedItemsHeaderView ({ item, unlinkCallback, itemNameField, itemAvatarField, loading, unlinkPermission, itemSubnameField }: {
item: Item, item: Item,
@ -15,35 +12,30 @@ export function LinkedItemsHeaderView({ item, unlinkCallback, itemNameField, ite
loading?: boolean, loading?: boolean,
unlinkPermission: boolean unlinkPermission: boolean
}) { }) {
const assetsApi = useAssetApi()
const assetsApi = useAssetApi(); const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && 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 = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && 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(() => { useEffect(() => {
}, [item]) }, [item])
return ( return (
<> <>
<div className='tw-flex tw-flex-row'> <div className='tw-flex tw-flex-row'>
<div className={`tw-grow tw-max-w-[calc(100%-60px)] }`}> <div className={'tw-grow tw-max-w-[calc(100%-60px)] }'}>
<div className="flex items-center"> <div className="flex items-center">
{avatar && ( {avatar && (
<img <img
className={`tw-w-10 tw-inline tw-rounded-full`} className={'tw-w-10 tw-inline tw-rounded-full'}
src={avatar} src={avatar}
alt={item.name + " logo"} alt={item.name + ' logo'}
/> />
)} )}
<div className={`${avatar ? "tw-ml-2" : ""} tw-overflow-hidden`}> <div className={`${avatar ? 'tw-ml-2' : ''} tw-overflow-hidden`}>
<div className={`tw-text-xl tw-font-semibold tw-truncate`}> <div className={'tw-text-xl tw-font-semibold tw-truncate'}>
{title} {title}
</div> </div>
{subtitle && <div className="tw-text-xs tw-truncate tw-text-gray-500 "> {subtitle && <div className="tw-text-xs tw-truncate tw-text-gray-500 ">
@ -63,9 +55,9 @@ export function LinkedItemsHeaderView({ item, unlinkCallback, itemNameField, ite
<ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000"> <ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000">
{true && <li> {true && <li>
<a className='tw-cursor-pointer !tw-text-error' onClick={() => unlinkCallback(item.id)}> <a className='tw-cursor-pointer !tw-text-error' onClick={() => unlinkCallback(item.id)}>
{loading ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span> {loading
: ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
<svg className="tw-h-5 tw-w-5" stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M304.083 405.907c4.686 4.686 4.686 12.284 0 16.971l-44.674 44.674c-59.263 59.262-155.693 59.266-214.961 0-59.264-59.265-59.264-155.696 0-214.96l44.675-44.675c4.686-4.686 12.284-4.686 16.971 0l39.598 39.598c4.686 4.686 4.686 12.284 0 16.971l-44.675 44.674c-28.072 28.073-28.072 73.75 0 101.823 28.072 28.072 73.75 28.073 101.824 0l44.674-44.674c4.686-4.686 12.284-4.686 16.971 0l39.597 39.598zm-56.568-260.216c4.686 4.686 12.284 4.686 16.971 0l44.674-44.674c28.072-28.075 73.75-28.073 101.824 0 28.072 28.073 28.072 73.75 0 101.823l-44.675 44.674c-4.686 4.686-4.686 12.284 0 16.971l39.598 39.598c4.686 4.686 12.284 4.686 16.971 0l44.675-44.675c59.265-59.265 59.265-155.695 0-214.96-59.266-59.264-155.695-59.264-214.961 0l-44.674 44.674c-4.686 4.686-4.686 12.284 0 16.971l39.597 39.598zm234.828 359.28l22.627-22.627c9.373-9.373 9.373-24.569 0-33.941L63.598 7.029c-9.373-9.373-24.569-9.373-33.941 0L7.029 29.657c-9.373 9.373-9.373 24.569 0 33.941l441.373 441.373c9.373 9.372 24.569 9.372 33.941 0z"></path></svg>} : <svg className="tw-h-5 tw-w-5" stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M304.083 405.907c4.686 4.686 4.686 12.284 0 16.971l-44.674 44.674c-59.263 59.262-155.693 59.266-214.961 0-59.264-59.265-59.264-155.696 0-214.96l44.675-44.675c4.686-4.686 12.284-4.686 16.971 0l39.598 39.598c4.686 4.686 4.686 12.284 0 16.971l-44.675 44.674c-28.072 28.073-28.072 73.75 0 101.823 28.072 28.072 73.75 28.073 101.824 0l44.674-44.674c4.686-4.686 12.284-4.686 16.971 0l39.597 39.598zm-56.568-260.216c4.686 4.686 12.284 4.686 16.971 0l44.674-44.674c28.072-28.075 73.75-28.073 101.824 0 28.072 28.073 28.072 73.75 0 101.823l-44.675 44.674c-4.686 4.686-4.686 12.284 0 16.971l39.598 39.598c4.686 4.686 12.284 4.686 16.971 0l44.675-44.675c59.265-59.265 59.265-155.695 0-214.96-59.266-59.264-155.695-59.264-214.961 0l-44.674 44.674c-4.686 4.686-4.686 12.284 0 16.971l39.597 39.598zm234.828 359.28l22.627-22.627c9.373-9.373 9.373-24.569 0-33.941L63.598 7.029c-9.373-9.373-24.569-9.373-33.941 0L7.029 29.657c-9.373 9.373-9.373 24.569 0 33.941l441.373 441.373c9.373 9.372 24.569 9.372 33.941 0z"></path></svg>}
</a> </a>
</li>} </li>}
</ul> </ul>

View File

@ -1,12 +1,12 @@
import { LayerProps } from "../../../types"; import { LayerProps } from '../../../types'
import { useHasUserPermission } from "../../Map/hooks/usePermissions"; import { useHasUserPermission } from '../../Map/hooks/usePermissions'
export function PlusButton({ layer, triggerAction, color, collection="items" }: { layer?: LayerProps ,triggerAction: any, color: string, collection?:string }) { export function PlusButton ({ layer, triggerAction, color, collection = 'items' }: { layer?: LayerProps, triggerAction: any, color: string, collection?:string }) {
const hasUserPermission = useHasUserPermission(); const hasUserPermission = useHasUserPermission()
return ( return (
<>{hasUserPermission(collection, "create", undefined, layer) && <>{hasUserPermission(collection, 'create', undefined, layer) &&
<div className="tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-3000 tw-absolute tw-right-4 tw-bottom-4" > <div className="tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-3000 tw-absolute tw-right-4 tw-bottom-4" >
<button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { triggerAction() }} style={{ backgroundColor: color, color: "#fff"}}> <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { triggerAction() }} style={{ backgroundColor: color, color: '#fff' }}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor" className="tw-w-5 tw-h-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor" className="tw-w-5 tw-h-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> <path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg> </svg>

View File

@ -1,5 +1,4 @@
import SocialShareBar from './SocialShareBar'; import SocialShareBar from './SocialShareBar'
/* const flags = { /* const flags = {
de: ( de: (
@ -19,10 +18,10 @@ import SocialShareBar from './SocialShareBar';
}; */ }; */
const statusMapping = { const statusMapping = {
'in_planning': 'in Planung', in_planning: 'in Planung',
'paused': 'pausiert', paused: 'pausiert',
'active': 'aktiv' active: 'aktiv'
}; }
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const SubHeader = ({ type, status, url, title }) => ( const SubHeader = ({ type, status, url, title }) => (
@ -30,7 +29,7 @@ const SubHeader = ({ type, status, url, title }) => (
<div className='tw-float-left tw-mt-2 tw-mb-4 tw-flex tw-items-center'> <div className='tw-float-left tw-mt-2 tw-mb-4 tw-flex tw-items-center'>
{status && <div className="tw-mt-1.5"> {status && <div className="tw-mt-1.5">
<span className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-0.5 tw-px-2 tw-inline-flex tw-items-center tw-mr-2"><span className={`tw-w-2 tw-h-2 ${ status=="in_planning" && "tw-bg-blue-700"} ${ status=="paused" && "tw-bg-orange-400"} ${ status=="active" && "tw-bg-green-500"} tw-rounded-full tw-mr-1.5`}></span>{statusMapping[status]}</span> <span className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-0.5 tw-px-2 tw-inline-flex tw-items-center tw-mr-2"><span className={`tw-w-2 tw-h-2 ${status === 'in_planning' && 'tw-bg-blue-700'} ${status === 'paused' && 'tw-bg-orange-400'} ${status === 'active' && 'tw-bg-green-500'} tw-rounded-full tw-mr-1.5`}></span>{statusMapping[status]}</span>
</div>} </div>}
{type && <div className="tw-mt-1.5"> {type && <div className="tw-mt-1.5">
<span className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-1 tw-px-2">{type}</span> <span className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-1 tw-px-2">{type}</span>
@ -40,6 +39,6 @@ const SubHeader = ({ type, status, url, title }) => (
<SocialShareBar url={url} title={title} /> <SocialShareBar url={url} title={title} />
</div> </div>
</div> </div>
); )
export default SubHeader; export default SubHeader

View File

@ -1,4 +1,3 @@
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const RelationCard = ({ title, description, imageSrc }) => ( const RelationCard = ({ title, description, imageSrc }) => (
<div className={`tw-mb-6 ${imageSrc ? 'md:tw-flex md:tw-space-x-4' : ''}`}> <div className={`tw-mb-6 ${imageSrc ? 'md:tw-flex md:tw-space-x-4' : ''}`}>
@ -12,6 +11,6 @@ const RelationCard = ({ title, description, imageSrc }) => (
<p className="tw-mt-2 tw-text-sm tw-text-gray-600">{description}</p> <p className="tw-mt-2 tw-text-sm tw-text-gray-600">{description}</p>
</div> </div>
</div> </div>
); )
export default RelationCard; export default RelationCard

View File

@ -1,4 +1,4 @@
import SocialShareButton from './SocialShareButton'; import SocialShareButton from './SocialShareButton'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const SocialShareBar = ({ url, title, platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email'] }) => { const SocialShareBar = ({ url, title, platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email'] }) => {
@ -13,7 +13,7 @@ const SocialShareBar = ({url, title, platforms = ['facebook', 'twitter', 'linked
/> />
))} ))}
</div> </div>
); )
}; }
export default SocialShareBar; export default SocialShareBar

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from 'react'
const platformConfigs = { const platformConfigs = {
facebook: { facebook: {
@ -47,20 +47,20 @@ const platformConfigs = {
), ),
bgColor: '#444444' bgColor: '#444444'
} }
}; }
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const SocialShareButton = ({ platform, url, title }) => { const SocialShareButton = ({ platform, url, title }) => {
const config = platformConfigs[platform]; const config = platformConfigs[platform]
if (!config) { if (!config) {
return null; return null
} }
const { shareUrl, icon, bgColor } = config; const { shareUrl, icon, bgColor } = config
const finalShareUrl = shareUrl const finalShareUrl = shareUrl
.replace('{url}', encodeURIComponent(url)) .replace('{url}', encodeURIComponent(url))
.replace('{title}', encodeURIComponent(title)); .replace('{title}', encodeURIComponent(title))
return ( return (
<a <a
@ -75,7 +75,7 @@ const SocialShareButton = ({ platform, url, title }) => {
> >
{React.cloneElement(icon, { className: 'tw-w-4 tw-h-4 tw-fill-current' })} {React.cloneElement(icon, { className: 'tw-w-4 tw-h-4 tw-fill-current' })}
</a> </a>
); )
}; }
export default SocialShareButton; export default SocialShareButton

View File

@ -1,99 +1,95 @@
import * as React from 'react' import * as React from 'react'
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react'
import { useTags } from '../../Map/hooks/useTags'; import { useTags } from '../../Map/hooks/useTags'
import { Tag } from '../../../types'; import { Tag } from '../../../types'
import { Autocomplete } from '../../Input/Autocomplete'; import { Autocomplete } from '../../Input/Autocomplete'
import { randomColor } from '../../../Utils/RandomColor'; import { randomColor } from '../../../Utils/RandomColor'
import { decodeTag, encodeTag } from '../../../Utils/FormatTags'; import { decodeTag, encodeTag } from '../../../Utils/FormatTags'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate }) => { export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate }) => {
const [input, setInput] = useState('')
const [isKeyReleased, setIsKeyReleased] = useState(false)
const tags = useTags()
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([])
const [input, setInput] = useState(''); const [focusInput, setFocusInput] = useState<boolean>(false)
const [isKeyReleased, setIsKeyReleased] = useState(false); const [currentTags, setCurrentTags] = useState<Array<Tag>>(defaultTags)
const tags = useTags();
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([]);
const [focusInput, setFocusInput] = useState<boolean>(false);
const [currentTags, setCurrentTags] = useState<Array<Tag>>(defaultTags);
useEffect(() => { useEffect(() => {
setCurrentTags(defaultTags) setCurrentTags(defaultTags)
}, [defaultTags]) }, [defaultTags])
const onChange = (e) => { const onChange = (e) => {
const { value } = e.target; const { value } = e.target
setInput(value); setInput(value)
}; }
const onKeyDown = (e) => { const onKeyDown = (e) => {
const { key } = e; const { key } = e
const trimmedInput = input.trim(); const trimmedInput = input.trim()
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
if ((key === 'Enter' || key === ',') && trimmedInput.length && !defaultTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) { if ((key === 'Enter' || key === ',') && trimmedInput.length && !defaultTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) {
e.preventDefault(); e.preventDefault()
const newTag = tags.find(t => t.name === trimmedInput.toLocaleLowerCase()) const newTag = tags.find(t => t.name === trimmedInput.toLocaleLowerCase())
newTag && onUpdate([...currentTags, newTag]); newTag && onUpdate([...currentTags, newTag])
!newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: encodeTag(trimmedInput), color: randomColor() }]); !newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: encodeTag(trimmedInput), color: randomColor() }])
setInput(''); setInput('')
setPushFilteredSuggestions([]); setPushFilteredSuggestions([])
} }
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
if (key === "Backspace" && !input.length && defaultTags.length && isKeyReleased) { if (key === 'Backspace' && !input.length && defaultTags.length && isKeyReleased) {
const defaultTagsCopy = [...defaultTags]; const defaultTagsCopy = [...defaultTags]
const poppedTag = defaultTagsCopy.pop(); const poppedTag = defaultTagsCopy.pop()
e.preventDefault(); e.preventDefault()
onUpdate(defaultTagsCopy); onUpdate(defaultTagsCopy)
poppedTag && setInput(poppedTag.name); poppedTag && setInput(poppedTag.name)
} }
setIsKeyReleased(false); setIsKeyReleased(false)
}; }
const onKeyUp = () => { const onKeyUp = () => {
setIsKeyReleased(true); setIsKeyReleased(true)
} }
const deleteTag = (tag) => { const deleteTag = (tag) => {
onUpdate(currentTags.filter((t) => t !== tag)) onUpdate(currentTags.filter((t) => t !== tag))
} }
const onSelected = (tag) => { const onSelected = (tag) => {
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
if (!defaultTags.some(t => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) { if (!defaultTags.some(t => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) {
const newTag = tags.find(t => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase()) const newTag = tags.find(t => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())
newTag && onUpdate([...currentTags, newTag]); newTag && onUpdate([...currentTags, newTag])
!newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }]); !newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }])
setInput(''); setInput('')
setPushFilteredSuggestions([]); setPushFilteredSuggestions([])
} }
} }
const inputProps = { const inputProps = {
value: input, value: input,
placeholder: placeholder, placeholder,
onKeyDown: onKeyDown, onKeyDown,
onKeyUp: onKeyUp, onKeyUp,
onChange: onChange, onChange,
className: 'tw-bg-transparent tw-w-fit tw-mt-5 tw-h-fit' className: 'tw-bg-transparent tw-w-fit tw-mt-5 tw-h-fit'
} }
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
return ( return (
<div onClick={() => { <div onClick={() => {
setFocusInput(true); setFocusInput(true)
setTimeout(() => { setTimeout(() => {
setFocusInput(false) setFocusInput(false)
}, 200) }, 200)
}} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}> }} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}>
<div className='tw-flex tw-flex-wrap tw-h-fit'> <div className='tw-flex tw-flex-wrap tw-h-fit'>
{defaultTags.map((tag) => ( {defaultTags.map((tag) => (
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4' style={{ backgroundColor: tag.color ? tag.color : "#666" }}> <div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4' style={{ backgroundColor: tag.color ? tag.color : '#666' }}>
<div className="tw-card-actions tw-justify-end"> <div className="tw-card-actions tw-justify-end">
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (deleteTag(tag))}></label> <label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (deleteTag(tag))}></label>
</div><b>{decodeTag(tag.name)}</b> </div><b>{decodeTag(tag.name)}</b>

View File

@ -1,8 +1,8 @@
import * as React from 'react' import * as React from 'react'
import { useEffect } from "react"; import { useEffect } from 'react'
import { Item, Tag } from "../../../types" import { Item, Tag } from '../../../types'
import { TextAreaInput, TextInput } from "../../Input" import { TextAreaInput, TextInput } from '../../Input'
import ComboBoxInput from "../../Input/ComboBoxInput" import ComboBoxInput from '../../Input/ComboBoxInput'
export const OnepagerForm = ({ item, state, setState }: { export const OnepagerForm = ({ item, state, setState }: {
state: { state: {
@ -25,51 +25,49 @@ export const OnepagerForm = ({ item, state, setState }: {
setState: React.Dispatch<React.SetStateAction<any>>, setState: React.Dispatch<React.SetStateAction<any>>,
item: Item item: Item
}) => { }) => {
useEffect(() => { useEffect(() => {
switch (state.groupType) { switch (state.groupType) {
case "wuerdekompass": case 'wuerdekompass':
setState(prevState => ({ setState(prevState => ({
...prevState, ...prevState,
color: item?.layer?.menuColor || "#1A5FB4", color: item?.layer?.menuColor || '#1A5FB4',
markerIcon: "group", markerIcon: 'group',
image: "59e6a346-d1ee-4767-9e42-fc720fb535c9" image: '59e6a346-d1ee-4767-9e42-fc720fb535c9'
})); }))
break; break
case "themenkompass": case 'themenkompass':
setState(prevState => ({ setState(prevState => ({
...prevState, ...prevState,
color: "#26A269", color: '#26A269',
markerIcon: "group", markerIcon: 'group',
image: "59e6a346-d1ee-4767-9e42-fc720fb535c9" image: '59e6a346-d1ee-4767-9e42-fc720fb535c9'
})); }))
break; break
case "liebevoll.jetzt": case 'liebevoll.jetzt':
setState(prevState => ({ setState(prevState => ({
...prevState, ...prevState,
color: "#E8B620", color: '#E8B620',
markerIcon: "liebevoll.jetzt", markerIcon: 'liebevoll.jetzt',
image: "e735b96c-507b-471c-8317-386ece0ca51d" image: 'e735b96c-507b-471c-8317-386ece0ca51d'
})); }))
break; break
default: default:
break; break
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.groupType]) }, [state.groupType])
const typeMapping = [ const typeMapping = [
{ value: 'wuerdekompass', label: 'Regional-Gruppe' }, { value: 'wuerdekompass', label: 'Regional-Gruppe' },
{ value: 'themenkompass', label: 'Themen-Gruppe' }, { value: 'themenkompass', label: 'Themen-Gruppe' },
{ value: 'liebevoll.jetzt', label: 'liebevoll.jetzt' } { value: 'liebevoll.jetzt', label: 'liebevoll.jetzt' }
]; ]
const statusMapping = [ const statusMapping = [
{ value: 'active', label: 'aktiv' }, { value: 'active', label: 'aktiv' },
{ value: 'in_planning', label: 'in Planung' }, { value: 'in_planning', label: 'in Planung' },
{ value: 'paused', label: 'pausiert' } { value: 'paused', label: 'pausiert' }
]; ]
return ( return (
<div className="tw-space-y-6 tw-mt-6"> <div className="tw-space-y-6 tw-mt-6">
@ -153,7 +151,7 @@ export const OnepagerForm = ({ item, state, setState }: {
</label> </label>
<TextAreaInput <TextAreaInput
placeholder="Beschreibung" placeholder="Beschreibung"
defaultValue={state.text || ""} defaultValue={state.text || ''}
updateFormValue={(v) => setState(prevState => ({ updateFormValue={(v) => setState(prevState => ({
...prevState, ...prevState,
text: v text: v

View File

@ -1,30 +1,27 @@
import { Item } from "../../../types" import { Item } from '../../../types'
import { TextView } from "../../Map" import { TextView } from '../../Map'
import ContactInfo from "../Subcomponents/ContactInfo" import ContactInfo from '../Subcomponents/ContactInfo'
import ProfileSubHeader from "../Subcomponents/ProfileSubHeader" import ProfileSubHeader from '../Subcomponents/ProfileSubHeader'
import { useEffect, useState } from "react" import { useEffect, useState } from 'react'
import { useItems } from "../../Map/hooks/useItems" import { useItems } from '../../Map/hooks/useItems'
export const OnepagerView = ({ item, userType }:{item: Item, userType: string}) => { export const OnepagerView = ({ item, userType }:{item: Item, userType: string}) => {
const [profileOwner, setProfileOwner] = useState<Item>()
const [profile_owner, setProfileOwner] = useState<Item>(); const items = useItems()
const items = useItems();
useEffect(() => { useEffect(() => {
setProfileOwner(items.find(i => (i.user_created?.id === item.user_created?.id) && i.layer?.itemType.name === userType)); setProfileOwner(items.find(i => (i.user_created?.id === item.user_created?.id) && i.layer?.itemType.name === userType))
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, items]) }, [item, items])
const typeMapping = { const typeMapping = {
'wuerdekompass': 'Regional-Gruppe', wuerdekompass: 'Regional-Gruppe',
'themenkompass': 'Themenkompass-Gruppe', themenkompass: 'Themenkompass-Gruppe',
'liebevoll.jetzt': 'liebevoll.jetzt', 'liebevoll.jetzt': 'liebevoll.jetzt'
}; }
const groupType = item.group_type ? item.group_type : 'default'; const groupType = item.group_type ? item.group_type : 'default'
const groupTypeText = typeMapping[groupType]; const groupTypeText = typeMapping[groupType]
return ( return (
<div className='tw-h-full tw-overflow-y-auto fade'> <div className='tw-h-full tw-overflow-y-auto fade'>
@ -37,7 +34,7 @@ const groupTypeText = typeMapping[groupType];
/> />
</div> </div>
{item.user_created.first_name && ( {item.user_created.first_name && (
<ContactInfo link={`/item/${profile_owner?.id}`} name={profile_owner?.name ? profile_owner.name : item.user_created.first_name} avatar={profile_owner?.image ? profile_owner.image : item.user_created.avatar} email={item.contact} telephone={item.telephone} /> <ContactInfo link={`/item/${profileOwner?.id}`} name={profileOwner?.name ? profileOwner.name : item.user_created.first_name} avatar={profileOwner?.image ? profileOwner.image : item.user_created.avatar} email={item.contact} telephone={item.telephone} />
)} )}
{/* Description Section */} {/* Description Section */}

View File

@ -1,4 +1,4 @@
import { TextAreaInput } from "../../Input"; import { TextAreaInput } from '../../Input'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const SimpleForm = ({ state, setState }) => { export const SimpleForm = ({ state, setState }) => {
@ -6,7 +6,7 @@ export const SimpleForm = ({ state, setState }) => {
<TextAreaInput <TextAreaInput
placeholder="About me ..." placeholder="About me ..."
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
defaultValue={state?.text || ""} defaultValue={state?.text || ''}
updateFormValue={(v) => setState(prevState => ({ updateFormValue={(v) => setState(prevState => ({
...prevState, ...prevState,
text: v text: v
@ -14,5 +14,5 @@ export const SimpleForm = ({ state, setState }) => {
containerStyle='tw-mt-8 tw-h-full' containerStyle='tw-mt-8 tw-h-full'
inputStyle='tw-h-full' inputStyle='tw-h-full'
/> />
); )
}; }

View File

@ -1,44 +1,43 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import { useCallback, useEffect, useState } from "react" import { useCallback, useEffect, useState } from 'react'
import { TextAreaInput } from "../../Input" import { TextAreaInput } from '../../Input'
import { PopupStartEndInput, TextView } from "../../Map" import { PopupStartEndInput, TextView } from '../../Map'
import { ActionButton } from "../Subcomponents/ActionsButton" import { ActionButton } from '../Subcomponents/ActionsButton'
import { LinkedItemsHeaderView } from "../Subcomponents/LinkedItemsHeaderView" import { LinkedItemsHeaderView } from '../Subcomponents/LinkedItemsHeaderView'
import { TagsWidget } from "../Subcomponents/TagsWidget" import { TagsWidget } from '../Subcomponents/TagsWidget'
import { useNavigate } from "react-router-dom" import { useNavigate } from 'react-router-dom'
import { useUpdateItem } from "../../Map/hooks/useItems" import { useUpdateItem } from '../../Map/hooks/useItems'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const TabsForm = ({ item, state, setState, updatePermission, linkItem, unlinkItem, loading, setUrlParams }) => { export const TabsForm = ({ item, state, setState, updatePermission, linkItem, unlinkItem, loading, setUrlParams }) => {
const [activeTab, setActiveTab] = useState<number>(1)
const [activeTab, setActiveTab] = useState<number>(1); const navigate = useNavigate()
const navigate = useNavigate(); const updateItem = useUpdateItem()
const updateItem = useUpdateItem();
const updateActiveTab = useCallback((id: number) => { const updateActiveTab = useCallback((id: number) => {
setActiveTab(id); setActiveTab(id)
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
params.set("tab", `${id}`); params.set('tab', `${id}`)
const newUrl = location.pathname + "?" + params.toString(); const newUrl = location.pathname + '?' + params.toString()
window.history.pushState({}, '', newUrl); window.history.pushState({}, '', newUrl)
setUrlParams(params); setUrlParams(params)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname]); }, [location.pathname])
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const urlTab = params.get("tab"); const urlTab = params.get('tab')
setActiveTab(urlTab ? Number(urlTab) : 1); setActiveTab(urlTab ? Number(urlTab) : 1)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.search]); }, [location.search])
return ( return (
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-3"> <div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-3">
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label="Info" checked={activeTab == 1 && true} onChange={() => updateActiveTab(1)} /> <input type="radio" name="my_tabs_2" role="tab" className={'tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'} aria-label="Info" checked={activeTab === 1 && true} onChange={() => updateActiveTab(1)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-border-[var(--fallback-bc,oklch(var(--bc)/0.2))] tw-rounded-box tw-h-[calc(100dvh-332px)] tw-min-h-56 tw-border-none"> <div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-border-[var(--fallback-bc,oklch(var(--bc)/0.2))] tw-rounded-box tw-h-[calc(100dvh-332px)] tw-min-h-56 tw-border-none">
<div className={`tw-flex tw-flex-col tw-h-full ${item.layer.itemType.show_start_end_input && "tw-pt-4"}`}> <div className={`tw-flex tw-flex-col tw-h-full ${item.layer.itemType.show_start_end_input && 'tw-pt-4'}`}>
{item.layer.itemType.show_start_end_input && {item.layer.itemType.show_start_end_input &&
<PopupStartEndInput <PopupStartEndInput
item={item} item={item}
@ -54,17 +53,17 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
} }
<TextAreaInput placeholder="about ..." <TextAreaInput placeholder="about ..."
defaultValue={item?.text ? item.text : ""} defaultValue={item?.text ? item.text : ''}
updateFormValue={(v) => setState(prevState => ({ updateFormValue={(v) => setState(prevState => ({
...prevState, ...prevState,
text: v text: v
}))} }))}
containerStyle='tw-grow' containerStyle='tw-grow'
inputStyle={`tw-h-full ${!item.layer.itemType.show_start_end_input && "tw-border-t-0 tw-rounded-tl-none"}`} /> inputStyle={`tw-h-full ${!item.layer.itemType.show_start_end_input && 'tw-border-t-0 tw-rounded-tl-none'}`} />
<div> <div>
<TextAreaInput <TextAreaInput
placeholder="contact info ..." placeholder="contact info ..."
defaultValue={state.contact || ""} defaultValue={state.contact || ''}
updateFormValue={(c) => setState(prevState => ({ updateFormValue={(c) => setState(prevState => ({
...prevState, ...prevState,
contact: c contact: c
@ -77,7 +76,7 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
</div> </div>
{item.layer?.itemType.offers_and_needs && {item.layer?.itemType.offers_and_needs &&
<> <>
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab tw-min-w-[10em] [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label="Offers & Needs" checked={activeTab == 3 && true} onChange={() => updateActiveTab(3)} /> <input type="radio" name="my_tabs_2" role="tab" className={'tw-tab tw-min-w-[10em] [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'} aria-label="Offers & Needs" checked={activeTab === 3 && true} onChange={() => updateActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-border-[var(--fallback-bc,oklch(var(--bc)/0.2))] tw-rounded-box tw-h-[calc(100dvh-332px)] tw-min-h-56 tw-border-none"> <div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-border-[var(--fallback-bc,oklch(var(--bc)/0.2))] tw-rounded-box tw-h-[calc(100dvh-332px)] tw-min-h-56 tw-border-none">
<div className='tw-h-full'> <div className='tw-h-full'>
<div className='tw-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'> <div className='tw-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'>
@ -98,13 +97,12 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
} }
{item.layer?.itemType.relations && {item.layer?.itemType.relations &&
<> <>
<input type="radio" name="my_tabs_2" role="tab" className="tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label="Relations" checked={activeTab == 7 && true} onChange={() => updateActiveTab(7)} /> <input type="radio" name="my_tabs_2" role="tab" className="tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label="Relations" checked={activeTab === 7 && true} onChange={() => updateActiveTab(7)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-332px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mx-4 tw-overflow-x-hidden fade"> <div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-332px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mx-4 tw-overflow-x-hidden fade">
<div className='tw-h-full'> <div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2 tw-mb-4'> <div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2 tw-mb-4'>
{state.relations && state.relations.map(i => {state.relations && state.relations.map(i =>
<div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-mx-4 tw-p-6 tw-mb-4' onClick={() => navigate('/item/' + i.id)}> <div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-mx-4 tw-p-6 tw-mb-4' onClick={() => navigate('/item/' + i.id)}>
<LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={(id) => unlinkItem(id, item, updateItem)} loading={loading} /> <LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={(id) => unlinkItem(id, item, updateItem)} 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'>
@ -120,7 +118,6 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
</> </>
} }
</div> </div>
) )
} }

View File

@ -12,52 +12,50 @@ import { timeAgo } from '../../../Utils/TimeAgo'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
export const TabsView = ({ attestations, userType, item, offers, needs, relations, updatePermission, loading, linkItem, unlinkItem }: { attestations: Array<any>, userType: string, item: Item, offers: Array<Tag>, needs: Array<Tag>, relations: Array<Item>, updatePermission: boolean, loading: boolean, linkItem: (id: string) => Promise<void>, unlinkItem: (id: string) => Promise<void> }) => { export const TabsView = ({ attestations, userType, item, offers, needs, relations, updatePermission, loading, linkItem, unlinkItem }: { attestations: Array<any>, userType: string, item: Item, offers: Array<Tag>, needs: Array<Tag>, relations: Array<Item>, updatePermission: boolean, loading: boolean, linkItem: (id: string) => Promise<void>, unlinkItem: (id: string) => Promise<void> }) => {
const addFilterTag = useAddFilterTag()
const [activeTab, setActiveTab] = useState<number>()
const navigate = useNavigate()
const addFilterTag = useAddFilterTag(); const [addItemPopupType] = useState<string>('')
const [activeTab, setActiveTab] = useState<number>();
const navigate = useNavigate();
const [addItemPopupType, /* setAddItemPopupType */] = useState<string>(""); const items = useItems()
const assetsApi = useAssetApi()
const items = useItems();
const assetsApi = useAssetApi();
const getUserProfile = (id: string) => { const getUserProfile = (id: string) => {
return items.find(i => i.user_created.id === id && i.layer?.itemType.name === userType) return items.find(i => i.user_created.id === id && i.layer?.itemType.name === userType)
} }
useEffect(() => { useEffect(() => {
scroll(); scroll()
}, [addItemPopupType]) }, [addItemPopupType])
function scroll () { function scroll () {
tabRef.current?.scrollIntoView(); tabRef.current?.scrollIntoView()
} }
const tabRef = useRef<HTMLFormElement>(null); const tabRef = useRef<HTMLFormElement>(null)
const updateActiveTab = useCallback((id: number) => { const updateActiveTab = useCallback((id: number) => {
setActiveTab(id); setActiveTab(id)
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
params.set("tab", `${id}`); params.set('tab', `${id}`)
const newUrl = location.pathname + "?" + params.toString(); const newUrl = location.pathname + '?' + params.toString()
window.history.pushState({}, '', newUrl); window.history.pushState({}, '', newUrl)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname]); }, [location.pathname])
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const urlTab = params.get("tab"); const urlTab = params.get('tab')
setActiveTab(urlTab ? Number(urlTab) : 1); setActiveTab(urlTab ? Number(urlTab) : 1)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.search]); }, [location.search])
return ( return (
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2 tw-mb-2 tw-px-6"> <div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2 tw-mb-2 tw-px-6">
<input type="radio" name="my_tabs_2" role="tab" <input type="radio" name="my_tabs_2" role="tab"
className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} className={'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab != 1 ? "📝" : "📝\u00A0Info"}`} checked={activeTab == 1 && true} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 1 ? '📝' : '📝\u00A0Info'}`} checked={activeTab === 1 && true}
onChange={() => updateActiveTab(1)} /> onChange={() => updateActiveTab(1)} />
<div role="tabpanel" <div role="tabpanel"
className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden"> className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
@ -71,15 +69,15 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
{item.layer?.itemType.questlog && {item.layer?.itemType.questlog &&
<> <>
<input type="radio" name="my_tabs_2" role="tab" <input type="radio" name="my_tabs_2" role="tab"
className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} className={'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab != 2 ? "❤️" : "❤️\u00A0Credibility"}`} checked={activeTab == 2 && true} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Credibility'}`} checked={activeTab === 2 && true}
onChange={() => updateActiveTab(2)} /> onChange={() => updateActiveTab(2)} />
<div role="tabpanel" <div role="tabpanel"
className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden"> className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
<table className="sm:tw-table-sm md:tw-table-md"> <table className="sm:tw-table-sm md:tw-table-md">
<tbody> <tbody>
{attestations {attestations
.filter(a => a.to.some(t => t.directus_users_id == item.user_created.id)) .filter(a => a.to.some(t => t.directus_users_id === item.user_created.id))
.sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime()) .sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime())
.map((a, i) => ( .map((a, i) => (
<tr key={i}> <tr key={i}>
@ -92,7 +90,7 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
<div className='tw-mr-2'><i>{a.text}</i></div> <div className='tw-mr-2'><i>{a.text}</i></div>
</td> </td>
<td> <td>
<Link to={"/item/" + getUserProfile(a.user_created.id)?.id}> <Link to={'/item/' + getUserProfile(a.user_created.id)?.id}>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="tw-avatar"> <div className="tw-avatar">
<div className="tw-mask tw-rounded-full h-8 w-8 tw-mr-2"> <div className="tw-mask tw-rounded-full h-8 w-8 tw-mr-2">
@ -121,13 +119,13 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
<> <>
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 ${!(item.layer?.itemType.icon_as_labels && activeTab != 3) && "tw-min-w-[10.4em]"} [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab != 3 ? "♻️" : "♻️\u00A0Offers & Needs"}`} checked={activeTab == 3 && true} onChange={() => updateActiveTab(3)} /> <input type="radio" name="my_tabs_2" role="tab" className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 ${!(item.layer?.itemType.icon_as_labels && activeTab !== 3) && 'tw-min-w-[10.4em]'} [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 3 ? '♻️' : '♻️\u00A0Offers & Needs'}`} checked={activeTab === 3 && true} onChange={() => updateActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-overflow-y-auto fade tw-pt-4 tw-pb-1" > <div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-overflow-y-auto fade tw-pt-4 tw-pb-1" >
<div className='tw-h-full'> <div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1'> <div className='tw-grid tw-grid-cols-1'>
{ {
offers.length > 0 ? offers.length > 0
<div className='tw-col-span-1'> ? <div className='tw-col-span-1'>
<h3 className='-tw-mb-2'>Offers</h3> <h3 className='-tw-mb-2'>Offers</h3>
< div className='tw-flex tw-flex-wrap tw-mb-4'> < div className='tw-flex tw-flex-wrap tw-mb-4'>
{ {
@ -136,36 +134,36 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
}} />) }} />)
} }
</div> </div>
</div> : "" </div>
: ''
} }
{ {
needs.length > 0 ? needs.length > 0
<div className='tw-col-span-1'> ? <div className='tw-col-span-1'>
<h3 className='-tw-mb-2 tw-col-span-1'>Needs</h3> <h3 className='-tw-mb-2 tw-col-span-1'>Needs</h3>
< div className='tw-flex tw-flex-wrap tw-mb-4'> < div className='tw-flex tw-flex-wrap tw-mb-4'>
{ {
needs.map(n => <TagView key={n?.id} tag={n} onClick={() => addFilterTag(n)} />) needs.map(n => <TagView key={n?.id} tag={n} onClick={() => addFilterTag(n)} />)
} }
</div> </div>
</div> : "" </div>
: ''
} }
</div> </div>
</div> </div>
</div> </div>
</> </>
} }
{item.layer?.itemType.relations && {item.layer?.itemType.relations &&
<> <>
<input type="radio" name="my_tabs_2" role="tab" className="tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label={`${item.layer?.itemType.icon_as_labels && activeTab != 7 ? "🔗" : "🔗\u00A0Relations"}`} checked={activeTab == 7 && true} onChange={() => updateActiveTab(7)} /> <input type="radio" name="my_tabs_2" role="tab" className="tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 7 ? '🔗' : '🔗\u00A0Relations'}`} checked={activeTab === 7 && true} onChange={() => updateActiveTab(7)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mr-4 -tw-mb-4 tw-overflow-x-hidden"> <div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mr-4 -tw-mb-4 tw-overflow-x-hidden">
<div className='tw-h-full'> <div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2 tw-pb-4'> <div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2 tw-pb-4'>
{relations && relations.map(i => {relations && relations.map(i =>
<div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-p-6 tw-mr-4 tw-mb-4' onClick={() => navigate('/item/' + i.id)}> <div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-p-6 tw-mr-4 tw-mb-4' onClick={() => navigate('/item/' + i.id)}>
<LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={unlinkItem} loading={loading} /> <LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={unlinkItem} 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'>

View File

@ -1,43 +1,33 @@
import * as React from 'react' import * as React from 'react'
import { MapOverlayPage } from '../Templates' import { MapOverlayPage } from '../Templates'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useState } from 'react'; import { useState } from 'react'
import { UserItem } from '../../types'; import { UserItem } from '../../types'
import { toast } from 'react-toastify'; import { toast } from 'react-toastify'
import { useAuth } from '../Auth'; import { useAuth } from '../Auth'
import { TextInput } from '../Input'; import { TextInput } from '../Input'
export function UserSettings () { export function UserSettings () {
const { user, updateUser, loading, /* token */ } = useAuth(); const { user, updateUser, loading /* token */ } = useAuth()
const [id, setId] = useState<string>(""); const [id, setId] = useState<string>('')
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>('')
const [passwordChanged, setPasswordChanged] = useState<boolean>(false)
const navigate = useNavigate()
const [passwordChanged, setPasswordChanged] = useState<boolean>(false);
const navigate = useNavigate();
React.useEffect(() => { React.useEffect(() => {
setId(user?.id ? user.id : ""); setId(user?.id ? user.id : '')
setEmail(user?.email ? user.email : ""); setEmail(user?.email ? user.email : '')
setPassword(user?.password ? user.password : ""); setPassword(user?.password ? user.password : '')
}, [user]) }, [user])
const onUpdateUser = () => { const onUpdateUser = () => {
let changedUser = {} as UserItem; let changedUser = {} as UserItem
changedUser = { id: id, email: email, ...passwordChanged && { password: password } };
changedUser = { id, email, ...passwordChanged && { password } }
toast.promise( toast.promise(
@ -48,30 +38,27 @@ export function UserSettings() {
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
},
},
})
.then(() => navigate("/"));
} }
}
})
.then(() => navigate('/'))
}
return ( return (
<MapOverlayPage backdrop className='tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-fit md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-max-w-xl !tw-left-auto tw-top-0 tw-bottom-0'> <MapOverlayPage backdrop className='tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-fit md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-max-w-xl !tw-left-auto tw-top-0 tw-bottom-0'>
<div className={`tw-text-xl tw-font-semibold`}>Settings</div> <div className={'tw-text-xl tw-font-semibold'}>Settings</div>
<div className="tw-divider tw-mt-2"></div> <div className="tw-divider tw-mt-2"></div>
<div className="tw-grid tw-grid-cols-1 tw-gap-6"> <div className="tw-grid tw-grid-cols-1 tw-gap-6">
<TextInput type='email' placeholder="new E-Mail" defaultValue={user?.email ? user.email : ""} updateFormValue={(v) => setEmail(v)} /> <TextInput type='email' placeholder="new E-Mail" defaultValue={user?.email ? user.email : ''} updateFormValue={(v) => setEmail(v)} />
<TextInput type='password' placeholder="new Password" defaultValue={user?.password ? user.password : ""} updateFormValue={(v) => { <TextInput type='password' placeholder="new Password" defaultValue={user?.password ? user.password : ''} updateFormValue={(v) => {
setPassword(v); setPassword(v)
setPasswordChanged(true); setPasswordChanged(true)
}} /> }} />
{/* <ToogleInput updateType="syncData" labelTitle="Sync Data" defaultValue={true} updateFormValue={updateFormValue}/> */} {/* <ToogleInput updateType="syncData" labelTitle="Sync Data" defaultValue={true} updateFormValue={updateFormValue}/> */}
</div> </div>
<div className="tw-mt-8"><button className={loading ? " tw-loading tw-btn-disabled tw-btn tw-btn-primary tw-float-right" : "tw-btn tw-btn-primary tw-float-right"} onClick={() => onUpdateUser()}>Update</button></div> <div className="tw-mt-8"><button className={loading ? ' tw-loading tw-btn-disabled tw-btn tw-btn-primary tw-float-right' : 'tw-btn tw-btn-primary tw-float-right'} onClick={() => onUpdateUser()}>Update</button></div>
</MapOverlayPage> </MapOverlayPage>
) )
} }

View File

@ -1,35 +1,35 @@
import { useEffect } from "react"; import { useEffect } from 'react'
// Improved version of https://usehooks.com/useOnClickOutside/ // Improved version of https://usehooks.com/useOnClickOutside/
const useClickOutside = (ref, handler) => { const useClickOutside = (ref, handler) => {
useEffect(() => { useEffect(() => {
let startedInside = false; let startedInside = false
let startedWhenMounted = false; let startedWhenMounted = false
const listener = (event) => { const listener = (event) => {
// Do nothing if `mousedown` or `touchstart` started inside ref element // Do nothing if `mousedown` or `touchstart` started inside ref element
if (startedInside || !startedWhenMounted) return; if (startedInside || !startedWhenMounted) return
// Do nothing if clicking ref's element or descendent elements // Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) return; if (!ref.current || ref.current.contains(event.target)) return
handler(event); handler(event)
}; }
const validateEventStart = (event) => { const validateEventStart = (event) => {
startedWhenMounted = ref.current; startedWhenMounted = ref.current
startedInside = ref.current && ref.current.contains(event.target); startedInside = ref.current && ref.current.contains(event.target)
}; }
document.addEventListener("mousedown", validateEventStart); document.addEventListener('mousedown', validateEventStart)
document.addEventListener("touchstart", validateEventStart); document.addEventListener('touchstart', validateEventStart)
document.addEventListener("click", listener); document.addEventListener('click', listener)
return () => { return () => {
document.removeEventListener("mousedown", validateEventStart); document.removeEventListener('mousedown', validateEventStart)
document.removeEventListener("touchstart", validateEventStart); document.removeEventListener('touchstart', validateEventStart)
document.removeEventListener("click", listener); document.removeEventListener('click', listener)
}; }
}, [ref, handler]); }, [ref, handler])
}; }
export default useClickOutside; export default useClickOutside

View File

@ -1,4 +1,4 @@
export { UserSettings } from './UserSettings' export { UserSettings } from './UserSettings'
export {PlusButton} from "./Subcomponents/PlusButton" export { PlusButton } from './Subcomponents/PlusButton'
export {ProfileView} from "./ProfileView" export { ProfileView } from './ProfileView'
export {ProfileForm} from "./ProfileForm" export { ProfileForm } from './ProfileForm'

View File

@ -1,126 +1,126 @@
import * as React from 'react' import * as React from 'react'
import { Item } from '../../types'; import { Item } from '../../types'
import { encodeTag } from '../../Utils/FormatTags'; import { encodeTag } from '../../Utils/FormatTags'
import { hashTagRegex } from '../../Utils/HashTagRegex'; import { hashTagRegex } from '../../Utils/HashTagRegex'
import { randomColor } from '../../Utils/RandomColor'; import { randomColor } from '../../Utils/RandomColor'
import { toast } from 'react-toastify'; import { toast } from 'react-toastify'
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
export const submitNewItem = async (evt: any, type: string, item, user, setLoading, tags, addTag, addItem, linkItem, resetFilterTags, layers, addItemPopupType, setAddItemPopupType) => { export const submitNewItem = async (evt: any, type: string, item, user, setLoading, tags, addTag, addItem, linkItem, resetFilterTags, layers, addItemPopupType, setAddItemPopupType) => {
evt.preventDefault(); evt.preventDefault()
const formItem: Item = {} as Item; const formItem: Item = {} as Item
Array.from(evt.target).forEach((input: HTMLInputElement) => { Array.from(evt.target).forEach((input: HTMLInputElement) => {
if (input.name) { if (input.name) {
formItem[input.name] = input.value; formItem[input.name] = input.value
} }
}); })
setLoading(true); setLoading(true)
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => { formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() }) addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
} }
}); return null
const uuid = crypto.randomUUID(); })
const uuid = crypto.randomUUID()
const layer = layers.find(l => l.name.toLocaleLowerCase().replace("s", "") == addItemPopupType.toLocaleLowerCase()) const layer = layers.find(l => l.name.toLocaleLowerCase().replace('s', '') === addItemPopupType.toLocaleLowerCase())
let success = false; let success = false
try { try {
await layer?.api?.createItem!({ ...formItem, id: uuid, type: type, parent: item.id }); await layer?.api?.createItem!({ ...formItem, id: uuid, type, parent: item.id })
await linkItem(uuid); await linkItem(uuid)
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
addItem({ ...formItem, id: uuid, type: type, layer: layer, user_created: user, parent: item.id }); addItem({ ...formItem, id: uuid, type, layer, user_created: user, parent: item.id })
toast.success("New item created"); toast.success('New item created')
resetFilterTags(); resetFilterTags()
} }
setLoading(false); setLoading(false)
setAddItemPopupType(""); setAddItemPopupType('')
} }
export const linkItem = async (id: string, item, updateItem) => { export const linkItem = async (id: string, item, updateItem) => {
const new_relations = item.relations || []; const newRelations = item.relations || []
new_relations?.push({ items_id: item.id, related_items_id: id }) newRelations?.push({ items_id: item.id, related_items_id: id })
const updatedItem = { id: item.id, relations: new_relations } const updatedItem = { id: item.id, relations: newRelations }
let success = false; let success = false
try { try {
await item?.layer?.api?.updateItem!(updatedItem) await item?.layer?.api?.updateItem!(updatedItem)
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
updateItem({ ...item, relations: new_relations }) updateItem({ ...item, relations: newRelations })
toast.success("Item linked"); toast.success('Item linked')
} }
} }
export const unlinkItem = async (id: string, item, updateItem) => { export const unlinkItem = async (id: string, item, updateItem) => {
const new_relations = item.relations?.filter(r => r.related_items_id !== id) const newRelations = item.relations?.filter(r => r.related_items_id !== id)
const updatedItem = { id: item.id, relations: new_relations } const updatedItem = { id: item.id, relations: newRelations }
let success = false
let success = false;
try { try {
await item?.layer?.api?.updateItem!(updatedItem) await item?.layer?.api?.updateItem!(updatedItem)
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
updateItem({ ...item, relations: new_relations }) updateItem({ ...item, relations: newRelations })
toast.success("Item unlinked"); toast.success('Item unlinked')
} }
} }
export const handleDelete = async (event: React.MouseEvent<HTMLElement>, item, setLoading, removeItem, map, navigate) => { export const handleDelete = async (event: React.MouseEvent<HTMLElement>, item, setLoading, removeItem, map, navigate) => {
event.stopPropagation(); event.stopPropagation()
setLoading(true); setLoading(true)
let success = false; let success = false
try { try {
await item.layer?.api?.deleteItem!(item.id) await item.layer?.api?.deleteItem!(item.id)
success = true; success = true
} catch (error) { } catch (error) {
toast.error(error.toString()); toast.error(error.toString())
} }
if (success) { if (success) {
removeItem(item); removeItem(item)
toast.success("Item deleted"); toast.success('Item deleted')
} }
setLoading(false); setLoading(false)
map.closePopup(); map.closePopup()
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
window.history.pushState({}, "", "/" + `${params ? `?${params}` : ""}`); window.history.pushState({}, '', '/' + `${params ? `?${params}` : ''}`)
navigate("/"); navigate('/')
} }
export const onUpdateItem = async (state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, params) => { export const onUpdateItem = async (state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, params) => {
let changedItem = {} as Item; let changedItem = {} as Item
const offer_updates: Array<any> = []; const offerUpdates: Array<any> = []
// check for new offers // check for new offers
await state.offers?.map(o => { await state.offers?.map(o => {
const existingOffer = item?.offers?.find(t => t.tags_id === o.id) const existingOffer = item?.offers?.find(t => t.tags_id === o.id)
existingOffer && offer_updates.push(existingOffer.id) existingOffer && offerUpdates.push(existingOffer.id)
if (!existingOffer && !tags.some(t => t.id === o.id)) addTag({ ...o, offer_or_need: true }) if (!existingOffer && !tags.some(t => t.id === o.id)) addTag({ ...o, offer_or_need: true })
!existingOffer && offer_updates.push({ items_id: item?.id, tags_id: o.id }) !existingOffer && offerUpdates.push({ items_id: item?.id, tags_id: o.id })
}); return null
})
const needs_updates: Array<any> = []; const needsUpdates: Array<any> = []
await state.needs?.map(n => { await state.needs?.map(n => {
const existingNeed = item?.needs?.find(t => t.tags_id === n.id) const existingNeed = item?.needs?.find(t => t.tags_id === n.id)
existingNeed && needs_updates.push(existingNeed.id) existingNeed && needsUpdates.push(existingNeed.id)
!existingNeed && needs_updates.push({ items_id: item?.id, tags_id: n.id }) !existingNeed && needsUpdates.push({ items_id: item?.id, tags_id: n.id })
!existingNeed && !tags.some(t => t.id === n.id) && addTag({ ...n, offer_or_need: true }) !existingNeed && !tags.some(t => t.id === n.id) && addTag({ ...n, offer_or_need: true })
}); return null
})
// update profile item in current state // update profile item in current state
changedItem = { changedItem = {
@ -139,33 +139,36 @@ export const onUpdateItem = async (state, item, tags, addTag, setLoading, naviga
...state.markerIcon && { markerIcon: state.markerIcon }, ...state.markerIcon && { markerIcon: state.markerIcon },
next_appointment: state.nextAppointment, next_appointment: state.nextAppointment,
...state.image.length > 10 && { image: state.image }, ...state.image.length > 10 && { image: state.image },
...state.offers.length > 0 && { offers: offer_updates }, ...state.offers.length > 0 && { offers: offerUpdates },
...state.needs.length > 0 && { needs: needs_updates } ...state.needs.length > 0 && { needs: needsUpdates }
}; }
const offers_state: Array<any> = []; const offersState: Array<any> = []
const needs_state: Array<any> = []; const needsState: Array<any> = []
state.offers.map(o => { state.offers.map(o => {
offers_state.push({ items_id: item?.id, tags_id: o.id }) offersState.push({ items_id: item?.id, tags_id: o.id })
}); return null
})
state.needs.map(n => { state.needs.map(n => {
needs_state.push({ items_id: item?.id, tags_id: n.id }) needsState.push({ items_id: item?.id, tags_id: n.id })
}); return null
})
changedItem = { ...changedItem, offers: offers_state, needs: needs_state }; changedItem = { ...changedItem, offers: offersState, needs: needsState }
setLoading(true); setLoading(true)
await state.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => { await state.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
addTag({ id: crypto.randomUUID(), name: encodeTag(tag.slice(1).toLocaleLowerCase()), color: randomColor() }) addTag({ id: crypto.randomUUID(), name: encodeTag(tag.slice(1).toLocaleLowerCase()), color: randomColor() })
} }
}); return null
})
// take care that addTag request comes before item request // take care that addTag request comes before item request
await sleep(200); await sleep(200)
if (!item.new) { if (!item.new) {
item?.layer?.api?.updateItem && toast.promise( item?.layer?.api?.updateItem && toast.promise(
@ -176,19 +179,17 @@ export const onUpdateItem = async (state, item, tags, addTag, setLoading, naviga
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, }
}, }
}) })
.catch(setLoading(false)) .catch(setLoading(false))
.then(() => item && updateItem({ ...item, ...changedItem })) .then(() => item && updateItem({ ...item, ...changedItem }))
.then(() => { .then(() => {
setLoading(false); setLoading(false)
navigate(`/item/${item.id}${params && "?" + params}`) navigate(`/item/${item.id}${params && '?' + params}`)
}); })
} else {
} item.new = false
else {
item.new = false;
item.layer?.api?.createItem && toast.promise( item.layer?.api?.createItem && toast.promise(
item.layer?.api?.createItem(changedItem), item.layer?.api?.createItem(changedItem),
{ {
@ -197,14 +198,14 @@ export const onUpdateItem = async (state, item, tags, addTag, setLoading, naviga
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, }
}, }
}) })
.catch(setLoading(false)) .catch(setLoading(false))
.then(() => item && addItem({ ...item, ...changedItem, layer: item.layer, user_created: user, type: item.layer?.itemType })) .then(() => item && addItem({ ...item, ...changedItem, layer: item.layer, user_created: user, type: item.layer?.itemType }))
.then(() => { .then(() => {
setLoading(false); setLoading(false)
navigate(`/${params && "?" + params}`) navigate(`/${params && '?' + params}`)
}); })
} }
} }

View File

@ -2,45 +2,42 @@ import * as React from 'react'
import { MapOverlayPage } from './MapOverlayPage' import { MapOverlayPage } from './MapOverlayPage'
import { useItems } from '../Map/hooks/useItems' import { useItems } from '../Map/hooks/useItems'
import { useAssetApi } from '../AppShell/hooks/useAssets' import { useAssetApi } from '../AppShell/hooks/useAssets'
import { EmojiPicker } from './EmojiPicker'; import { EmojiPicker } from './EmojiPicker'
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom'
import { useRef, useState } from 'react'; import { useRef, useState, useEffect } from 'react'
import { Item, ItemsApi } from '../../types'; import { Item, ItemsApi } from '../../types'
import { useEffect } from 'react'; import { toast } from 'react-toastify'
import { toast } from 'react-toastify';
export const AttestationForm = ({ api }:{api?:ItemsApi<any>}) => { export const AttestationForm = ({ api }:{api?:ItemsApi<any>}) => {
const items = useItems()
const items = useItems(); const assetsApi = useAssetApi()
const assetsApi = useAssetApi(); const [users, setUsers] = useState<Array<Item>>()
const [users, setUsers] = useState<Array<Item>>(); const navigate = useNavigate()
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search)
const to_user_ids = params.get("to"); const toUserIds = params.get('to')
setUsers(items.filter(i => to_user_ids?.includes(i.id))) setUsers(items.filter(i => toUserIds?.includes(i.id)))
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [items, location]) }, [items, location])
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('')
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => { useEffect(() => {
if (inputRef.current) { if (inputRef.current) {
inputRef.current.style.width = 'auto'; inputRef.current.style.width = 'auto'
inputRef.current.style.width = `${inputRef.current.scrollWidth+20}px`; inputRef.current.style.width = `${inputRef.current.scrollWidth + 20}px`
} }
}, [inputValue]); }, [inputValue])
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value); setInputValue(event.target.value)
}; }
const sendAttestation = async () => { const sendAttestation = async () => {
const to : Array<any> = []; const to : Array<any> = []
users?.map(u => to.push({ directus_users_id: u.user_created.id })); users?.map(u => to.push({ directus_users_id: u.user_created.id }))
api?.createItem && toast.promise( api?.createItem && toast.promise(
api.createItem({ api.createItem({
@ -48,25 +45,23 @@ export const AttestationForm = ({api}:{api?:ItemsApi<any>}) => {
emoji: selectedEmoji, emoji: selectedEmoji,
color: selectedColor, color: selectedColor,
shape: selectedShape, shape: selectedShape,
to: to to
}), { }), {
pending: 'creating attestation ...', pending: 'creating attestation ...',
success: 'Attestation created', success: 'Attestation created',
error: { error: {
render ({ data }) { render ({ data }) {
return `${data}` return `${data}`
}, }
}, }
}) })
.then( () => navigate("/item/" + items.find(i => i.user_created.id===to[0].directus_users_id && i.layer?.itemType.name==="player")?.id+"?tab=2") .then(() => navigate('/item/' + items.find(i => i.user_created.id === to[0].directus_users_id && i.layer?.itemType.name === 'player')?.id + '?tab=2')
) )
} }
const [selectedEmoji, setSelectedEmoji] = useState('select badge'); const [selectedEmoji, setSelectedEmoji] = useState('select badge')
const [selectedShape, setSelectedShape] = useState('circle'); const [selectedShape, setSelectedShape] = useState('circle')
const [selectedColor, setSelectedColor] = useState('#fff0d6'); const [selectedColor, setSelectedColor] = useState('#fff0d6')
return ( return (
<MapOverlayPage backdrop className='tw-h-fit tw-min-h-56 tw-w-96'> <MapOverlayPage backdrop className='tw-h-fit tw-min-h-56 tw-w-96'>
@ -75,17 +70,18 @@ export const AttestationForm = ({api}:{api?:ItemsApi<any>}) => {
<div className='tw-flex tw-flex-row tw-justify-center tw-items-center tw-flex-wrap'> <div className='tw-flex tw-flex-row tw-justify-center tw-items-center tw-flex-wrap'>
{users && users.map((u, k) => ( {users && users.map((u, k) => (
<div key={k} className="tw-flex tw-items-center tw-space-x-3 tw-mx-2 tw-my-1"> <div key={k} className="tw-flex tw-items-center tw-space-x-3 tw-mx-2 tw-my-1">
{u.image ? <div className="tw-avatar"> {u.image
? <div className="tw-avatar">
<div className="tw-mask tw-mask-circle tw-w-8 tw-h-8"> <div className="tw-mask tw-mask-circle tw-w-8 tw-h-8">
<img src={assetsApi.url + u.image + "?width=40&heigth=40"} alt="Avatar" /> <img src={assetsApi.url + u.image + '?width=40&heigth=40'} alt="Avatar" />
</div> </div>
</div> : </div>
<div className='tw-mask tw-mask-circle tw-text-xl md:tw-text-2xl tw-bg-slate-200 tw-rounded-full tw-w-8 tw-h-8'></div>} : <div className='tw-mask tw-mask-circle tw-text-xl md:tw-text-2xl tw-bg-slate-200 tw-rounded-full tw-w-8 tw-h-8'></div>}
<div> <div>
<div className="tw-font-bold">{u.name}</div> <div className="tw-font-bold">{u.name}</div>
</div> </div>
</div> </div>
), ", ")} ), ', ')}
</div> </div>
<div className='tw-w-full'> <div className='tw-w-full'>

View File

@ -1,7 +1,6 @@
import { Link } from "react-router-dom" import { Link } from 'react-router-dom'
import * as React from "react" import * as React from 'react'
import {TitleCard} from "./TitleCard" import { TitleCard } from './TitleCard'
export function CardPage ({ title, hideTitle, children, parents } : { export function CardPage ({ title, hideTitle, children, parents } : {
title: string, title: string,
@ -9,8 +8,6 @@ export function CardPage({title, hideTitle, children, parents} : {
children?: React.ReactNode, children?: React.ReactNode,
parents?: Array<{name: string, path: string}> parents?: Array<{name: string, path: string}>
}) { }) {
return ( return (
<main className="tw-flex-1 tw-overflow-y-auto tw-overflow-x-hidden tw-pt-2 tw-px-6 tw-min-w-80 tw-flex tw-justify-center" > <main className="tw-flex-1 tw-overflow-y-auto tw-overflow-x-hidden tw-pt-2 tw-px-6 tw-min-w-80 tw-flex tw-justify-center" >
<div className='tw-w-full xl:tw-max-w-6xl '> <div className='tw-w-full xl:tw-max-w-6xl '>

View File

@ -1,33 +1,32 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react'
export const CircleLayout = ({ items, radius, fontSize } : {items: any, radius: number, fontSize: any}) => { export const CircleLayout = ({ items, radius, fontSize } : {items: any, radius: number, fontSize: any}) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
const container = containerRef.current; const container = containerRef.current
const itemCount = items.length; const itemCount = items.length
if (container) { if (container) {
for (let i = 0; i < itemCount; i++) { for (let i = 0; i < itemCount; i++) {
const startAngle = (Math.PI) / 2; const startAngle = (Math.PI) / 2
const angle = startAngle + (i / itemCount) * (2 * Math.PI); const angle = startAngle + (i / itemCount) * (2 * Math.PI)
const x = radius * Math.cos(angle); const x = radius * Math.cos(angle)
const y = radius * Math.sin(angle); const y = radius * Math.sin(angle)
const child = container.children[i] as HTMLElement; const child = container.children[i] as HTMLElement
child.style.transform = `translate(${x}px, ${y}px)`; child.style.transform = `translate(${x}px, ${y}px)`
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [items]); }, [items])
return ( return (
<div className="tw-absolute tw-mx-auto tw-flex tw-justify-center tw-items-center tw-h-full tw-w-full" ref={containerRef}> <div className="tw-absolute tw-mx-auto tw-flex tw-justify-center tw-items-center tw-h-full tw-w-full" ref={containerRef}>
{items.map((item: any) => ( {items.map((item: any) => (
<div key={item} className="tw-absolute" style={{fontSize: fontSize}}> <div key={item} className="tw-absolute" style={{ fontSize }}>
{item} {item}
</div> </div>
))} ))}
</div> </div>
); )
}; }

View File

@ -1,17 +1,16 @@
import { useState } from "react"; import { useState } from 'react'
import { timeAgo } from "../../Utils/TimeAgo"; import { timeAgo } from '../../Utils/TimeAgo'
import { Item } from "../../types"; import { Item } from '../../types'
export const DateUserInfo = ({ item }: { item: Item }) => { export const DateUserInfo = ({ item }: { item: Item }) => {
const [infoExpanded, setInfoExpanded] = useState<boolean>(false); const [infoExpanded, setInfoExpanded] = useState<boolean>(false)
return ( return (
<div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 -tw-mt-2' onClick={(e) => e.stopPropagation()}> <div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 -tw-mt-2' onClick={(e) => e.stopPropagation()}>
{ {
infoExpanded ? infoExpanded
<p className={`tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500`} onClick={() => setInfoExpanded(false)} >{`${item.date_updated && item.date_updated != item.date_created ? "updated" : "posted"} ${item && item.user_created && item.user_created.first_name ? `by ${item.user_created.first_name}` : ""} ${item.date_updated ? timeAgo(item.date_updated) : timeAgo(item.date_created!)}`}</p> ? <p className={'tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'} onClick={() => setInfoExpanded(false)} >{`${item.date_updated && item.date_updated !== item.date_created ? 'updated' : 'posted'} ${item && item.user_created && item.user_created.first_name ? `by ${item.user_created.first_name}` : ''} ${item.date_updated ? timeAgo(item.date_updated) : timeAgo(item.date_created!)}`}</p>
: : <p className="!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500" onClick={() => setInfoExpanded(true)}></p>
<p className="!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500" onClick={() => setInfoExpanded(true)}></p>
} }
<div className='tw-grow '></div> <div className='tw-grow '></div>
</div> </div>

View File

@ -1,17 +1,16 @@
import * as React from "react"; import * as React from 'react'
import { MouseEvent, useEffect, useRef } from "react"; import { MouseEvent, useEffect, useRef } from 'react'
const isClickInsideRectangle = (e: MouseEvent, element: HTMLElement) => { const isClickInsideRectangle = (e: MouseEvent, element: HTMLElement) => {
const r = element.getBoundingClientRect(); const r = element.getBoundingClientRect()
return ( return (
e.clientX > r.left && e.clientX > r.left &&
e.clientX < r.right && e.clientX < r.right &&
e.clientY > r.top && e.clientY > r.top &&
e.clientY < r.bottom e.clientY < r.bottom
); )
}; }
type Props = { type Props = {
title: string; title: string;
@ -30,26 +29,25 @@ const DialogModal = ({
children, children,
showCloseButton = true, showCloseButton = true,
closeOnClickOutside = true, closeOnClickOutside = true,
className, className
}: Props) => { }: Props) => {
const ref = useRef<HTMLDialogElement>(null)
const ref = useRef<HTMLDialogElement>(null);
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
ref.current?.showModal(); ref.current?.showModal()
ref.current?.classList.remove("tw-hidden"); ref.current?.classList.remove('tw-hidden')
document.body.classList.add("modal-open"); // prevent bg scroll document.body.classList.add('modal-open') // prevent bg scroll
} else { } else {
ref.current?.close(); ref.current?.close()
ref.current?.classList.add("tw-hidden"); ref.current?.classList.add('tw-hidden')
document.body.classList.remove("modal-open"); document.body.classList.remove('modal-open')
} }
}, [isOpened]); }, [isOpened])
if (isOpened) {
if(isOpened) return ( return (
<dialog className={`${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`} <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`}
ref={ref} ref={ref}
// eslint-disable-next-line react/no-unknown-property // eslint-disable-next-line react/no-unknown-property
@ -67,8 +65,8 @@ const DialogModal = ({
</div> </div>
</dialog> </dialog>
); )
else return (<></>) } else return (<></>)
}; }
export default DialogModal; export default DialogModal

View File

@ -1,11 +1,8 @@
import { useState } from 'react'; import { useState } from 'react'
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
export const EmojiPicker = ({ selectedEmoji, selectedColor, selectedShape, setSelectedEmoji, setSelectedColor, setSelectedShape }) => { export const EmojiPicker = ({ selectedEmoji, selectedColor, selectedShape, setSelectedEmoji, setSelectedColor, setSelectedShape }) => {
const [isOpen, setIsOpen] = useState(false)
const [isOpen, setIsOpen] = useState(false);
const emojis = [ const emojis = [
'❤️', '🙏', '👍', '🌻', '✨', '☀️', '❤️', '🙏', '👍', '🌻', '✨', '☀️',
@ -14,38 +11,35 @@ export const EmojiPicker = ({selectedEmoji, selectedColor, selectedShape, setSel
'🛠', '💻', '🕹', '🖨', '🚐', '🛒', '🛠', '💻', '🕹', '🖨', '🚐', '🛒',
'⚽️', '🧵', '👀', '🌱', '⚽️', '🧵', '👀', '🌱',
'🏕', '💪', '🎁', '🏹', '🏕', '💪', '🎁', '🏹',
'🥕', '🥇', '🥈', '🥉']; '🥕', '🥇', '🥈', '🥉']
const shapes = ["squircle", "circle", "hexagon-2"]; const shapes = ['squircle', 'circle', 'hexagon-2']
const colors = ["#FF99C8", "#fff0d6", "#FCF6BD", "#D0F4DE", "#A9DEF9", "#E4C1F9", "#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2"]
const colors = ['#FF99C8', '#fff0d6', '#FCF6BD', '#D0F4DE', '#A9DEF9', '#E4C1F9', '#de324c', '#f4895f', '#f8e16f', '#95cf92', '#369acc', '#9656a2']
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen)
}; }
const selectEmoji = (emoji) => { const selectEmoji = (emoji) => {
setSelectedEmoji(emoji); setSelectedEmoji(emoji)
setIsOpen(false); setIsOpen(false)
}; }
const selectShape = (shape) => { const selectShape = (shape) => {
setSelectedShape(shape); setSelectedShape(shape)
setIsOpen(false); setIsOpen(false)
}; }
const selectColor = (color) => { const selectColor = (color) => {
setSelectedColor(color); setSelectedColor(color)
setIsOpen(false); setIsOpen(false)
}; }
return ( return (
<> <>
<div <div
onClick={toggleDropdown} onClick={toggleDropdown}
className={`tw-cursor-pointer ${selectedEmoji=="select badge" ? 'tw-text-sm !tw-p-9 tw-text-center ' : 'tw-text-6xl'} tw-mask tw-mask-${selectedShape} tw-p-6 tw-bg-[${selectedColor}]`} className={`tw-cursor-pointer ${selectedEmoji === 'select badge' ? 'tw-text-sm !tw-p-9 tw-text-center ' : 'tw-text-6xl'} tw-mask tw-mask-${selectedShape} tw-p-6 tw-bg-[${selectedColor}]`}
> >
{selectedEmoji} {selectedEmoji}
</div> </div>
@ -57,7 +51,7 @@ export const EmojiPicker = ({selectedEmoji, selectedColor, selectedShape, setSel
<button <button
key={emoji} key={emoji}
onClick={() => selectEmoji(emoji)} onClick={() => selectEmoji(emoji)}
className={`tw-cursor-pointer tw-text-2xl tw-p-2 hover:tw-bg-base-200 tw-rounded-md ${emoji == selectedEmoji && "tw-bg-base-300"}`} className={`tw-cursor-pointer tw-text-2xl tw-p-2 hover:tw-bg-base-200 tw-rounded-md ${emoji === selectedEmoji && 'tw-bg-base-300'}`}
> >
{emoji} {emoji}
</button> </button>
@ -68,7 +62,7 @@ export const EmojiPicker = ({selectedEmoji, selectedColor, selectedShape, setSel
{shapes.map(shape => ( {shapes.map(shape => (
<div <div
key={shape} key={shape}
className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 ${shape == selectedShape && "tw-bg-base-300"}`} className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 ${shape === selectedShape && 'tw-bg-base-300'}`}
onClick={() => selectShape(shape)}> onClick={() => selectShape(shape)}>
<div className={`tw-h-12 tw-mask tw-mask-${shape} tw-bg-neutral-content`}></div> <div className={`tw-h-12 tw-mask tw-mask-${shape} tw-bg-neutral-content`}></div>
</div> </div>
@ -79,7 +73,7 @@ export const EmojiPicker = ({selectedEmoji, selectedColor, selectedShape, setSel
{colors.map(color => ( {colors.map(color => (
<div <div
key={color} key={color}
className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 tw-flex tw-justify-center tw-items-center ${color == selectedColor && "tw-bg-base-300"}`} className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 tw-flex tw-justify-center tw-items-center ${color === selectedColor && 'tw-bg-base-300'}`}
onClick={() => selectColor(color)}> onClick={() => selectColor(color)}>
<div className={`tw-h-8 tw-w-8 tw-rounded-full tw-bg-[${color}]`}></div> <div className={`tw-h-8 tw-w-8 tw-rounded-full tw-bg-[${color}]`}></div>
</div> </div>
@ -89,6 +83,5 @@ export const EmojiPicker = ({selectedEmoji, selectedColor, selectedShape, setSel
)} )}
</> </>
); )
}; }

View File

@ -7,16 +7,16 @@ import { getValue } from '../../Utils/GetValue'
import useWindowDimensions from '../Map/hooks/useWindowDimension' import useWindowDimensions from '../Map/hooks/useWindowDimension'
export const ItemCard = ({ i, loading, url, parameterField, deleteCallback }: { i: Item, loading: boolean, url: string, parameterField: string, deleteCallback: any }) => { export const ItemCard = ({ i, loading, url, parameterField, deleteCallback }: { i: Item, loading: boolean, url: string, parameterField: string, deleteCallback: any }) => {
const navigate = useNavigate(); const navigate = useNavigate()
const windowDimensions = useWindowDimensions(); const windowDimensions = useWindowDimensions()
return ( return (
<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={() => { <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={() => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search)
if (windowDimensions.width < 786 && i.position) navigate("/" + getValue(i, parameterField) + `${params ? `?${params}` : ""}`) if (windowDimensions.width < 786 && i.position) navigate('/' + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
else navigate(url + getValue(i, parameterField) + `${params ? `?${params}` : ""}`) else navigate(url + getValue(i, parameterField) + `${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> <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'> <div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
{i.layer?.itemType.show_start_end && {i.layer?.itemType.show_start_end &&
<StartEndView item={i}></StartEndView> <StartEndView item={i}></StartEndView>

Some files were not shown because too many files have changed in this diff Show More