mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
autofixes
This commit is contained in:
parent
0d6909bd25
commit
6ae276bedf
40
.eslintrc.js
40
.eslintrc.js
@ -2,7 +2,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true
|
es2021: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'standard',
|
'standard',
|
||||||
@ -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,7 +27,7 @@ module.exports = {
|
|||||||
// 'security',
|
// 'security',
|
||||||
// 'no-catch-all',
|
// 'no-catch-all',
|
||||||
'react',
|
'react',
|
||||||
'react-hooks'
|
'react-hooks',
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
// 'import/resolver': {
|
// 'import/resolver': {
|
||||||
@ -35,13 +35,13 @@ module.exports = {
|
|||||||
// node: true,
|
// node: true,
|
||||||
// },
|
// },
|
||||||
react: {
|
react: {
|
||||||
version: 'detect'
|
version: 'detect',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||||
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
||||||
'react/react-in-jsx-scope': 'off' // Disable requirement for React import
|
'react/react-in-jsx-scope': 'off', // Disable requirement for React import
|
||||||
// 'no-catch-all/no-catch-all': 'error',
|
// 'no-catch-all/no-catch-all': 'error',
|
||||||
// 'no-console': 'error',
|
// 'no-console': 'error',
|
||||||
// 'no-debugger': 'error',
|
// 'no-debugger': 'error',
|
||||||
@ -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'],
|
||||||
@ -158,19 +158,19 @@ module.exports = {
|
|||||||
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||||
// 'no-void': ['error', { allowAsStatement: true }],
|
// 'no-void': ['error', { allowAsStatement: true }],
|
||||||
// },
|
// },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['!*.json'],
|
||||||
|
plugins: ['prettier'],
|
||||||
|
extends: ['plugin:prettier/recommended'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'error',
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
files: ['!*.json'],
|
|
||||||
plugins: ['prettier'],
|
|
||||||
extends: ['plugin:prettier/recommended'],
|
|
||||||
rules: {
|
|
||||||
'prettier/prettier': 'error',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
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'],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {}
|
autoprefixer: {},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,14 +9,30 @@ export default {
|
|||||||
format: 'esm',
|
format: 'esm',
|
||||||
exports: 'named',
|
exports: 'named',
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
strict: false
|
strict: false,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
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']
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,16 +4,26 @@ 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 (
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||||
<SetAssetsApi assetsApi={assetsApi} />
|
<SetAssetsApi assetsApi={assetsApi} />
|
||||||
<NavBar userType={userType} appName={appName}></NavBar>
|
<NavBar userType={userType} appName={appName}></NavBar>
|
||||||
<div id="app-content" className="tw-flex-grow">
|
<div id='app-content' className='tw-flex-grow'>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ContextWrapper>
|
</ContextWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
type ContentProps = {
|
type ContentProps = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Content ({ children } : ContentProps) {
|
export function Content({ children }: ContentProps) {
|
||||||
return (
|
return (
|
||||||
<div className='tw-flex tw-flex-col tw-w-full tw-h-full tw-bg-base-200 tw-relative'>
|
<div className='tw-flex tw-flex-col tw-w-full tw-h-full tw-bg-base-200 tw-relative'>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,11 +31,7 @@ export const ContextWrapper = ({ children }) => {
|
|||||||
|
|
||||||
// Case 1: Only the Router is missing, but ContextWrapper is already provided
|
// Case 1: Only the Router is missing, but ContextWrapper is already provided
|
||||||
if (!location && isWrapped) {
|
if (!location && isWrapped) {
|
||||||
return (
|
return <Router>{children}</Router>
|
||||||
<Router>
|
|
||||||
{children}
|
|
||||||
</Router>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Neither Router nor ContextWrapper is present
|
// Case 2: Neither Router nor ContextWrapper is present
|
||||||
@ -43,9 +39,7 @@ export const ContextWrapper = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<ContextCheckContext.Provider value={true}>
|
<ContextCheckContext.Provider value={true}>
|
||||||
<Wrappers>
|
<Wrappers>{children}</Wrappers>
|
||||||
{children}
|
|
||||||
</Wrappers>
|
|
||||||
</ContextCheckContext.Provider>
|
</ContextCheckContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
)
|
)
|
||||||
@ -80,7 +74,8 @@ export const Wrappers = ({ children }) => {
|
|||||||
<AssetsProvider>
|
<AssetsProvider>
|
||||||
<ClusterRefProvider>
|
<ClusterRefProvider>
|
||||||
<QuestsProvider initialOpen={true}>
|
<QuestsProvider initialOpen={true}>
|
||||||
<ToastContainer position="top-right"
|
<ToastContainer
|
||||||
|
position='top-right'
|
||||||
autoClose={2000}
|
autoClose={2000}
|
||||||
hideProgressBar
|
hideProgressBar
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
@ -89,7 +84,8 @@ export const Wrappers = ({ children }) => {
|
|||||||
pauseOnFocusLoss
|
pauseOnFocusLoss
|
||||||
draggable
|
draggable
|
||||||
pauseOnHover
|
pauseOnHover
|
||||||
theme="light" />
|
theme='light'
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</QuestsProvider>
|
</QuestsProvider>
|
||||||
</ClusterRefProvider>
|
</ClusterRefProvider>
|
||||||
|
|||||||
@ -6,21 +6,23 @@ 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 { isAuthenticated, user, logout } = useAuth()
|
||||||
|
|
||||||
const [userProfile, setUserProfile] = useState<Item>({}as Item)
|
const [userProfile, setUserProfile] = useState<Item>({} as Item)
|
||||||
const items = useItems()
|
const items = useItems()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === userType)
|
const profile =
|
||||||
profile ? setUserProfile(profile) : setUserProfile({ id: crypto.randomUUID(), name: user?.first_name, text: '' })
|
user &&
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
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: '' })
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [user, items])
|
}, [user, items])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {}, [userProfile])
|
||||||
|
|
||||||
}, [userProfile])
|
|
||||||
|
|
||||||
const nameRef = useRef<any>(null)
|
const nameRef = useRef<any>(null)
|
||||||
const [nameWidth, setNameWidth] = useState<number>(0)
|
const [nameWidth, setNameWidth] = useState<number>(0)
|
||||||
@ -38,95 +40,156 @@ export default function NavBar ({ appName, userType }: { appName: string, userTy
|
|||||||
}, [location])
|
}, [location])
|
||||||
|
|
||||||
const onLogout = () => {
|
const onLogout = () => {
|
||||||
toast.promise(
|
toast.promise(logout(), {
|
||||||
logout(),
|
success: {
|
||||||
{
|
render() {
|
||||||
success: {
|
return 'Bye bye'
|
||||||
render () {
|
|
||||||
return 'Bye bye'
|
|
||||||
},
|
|
||||||
// other options
|
|
||||||
icon: '👋'
|
|
||||||
},
|
},
|
||||||
error: {
|
// other options
|
||||||
render ({ data }) {
|
icon: '👋',
|
||||||
return `${data}`
|
},
|
||||||
}
|
error: {
|
||||||
|
render({ data }) {
|
||||||
|
return `${data}`
|
||||||
},
|
},
|
||||||
pending: 'logging out ..'
|
},
|
||||||
})
|
pending: 'logging out ..',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showNav) {
|
if (showNav) {
|
||||||
return (
|
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
|
||||||
data-te-sidenav-toggle-ref
|
className='tw-btn tw-btn-square tw-btn-ghost'
|
||||||
data-te-target="#sidenav"
|
data-te-sidenav-toggle-ref
|
||||||
aria-controls="#sidenav"
|
data-te-target='#sidenav'
|
||||||
aria-haspopup="true">
|
aria-controls='#sidenav'
|
||||||
<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>
|
aria-haspopup='true'
|
||||||
</button>
|
>
|
||||||
<div className="tw-flex-1 tw-mr-2">
|
<svg
|
||||||
<div className={'tw-flex-1 tw-truncate tw-grid tw-grid-flow-col'} style={{ maxWidth: nameWidth + 60 }}>
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
<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>
|
fill='none'
|
||||||
<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>
|
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>
|
||||||
|
<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 }}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{isAuthenticated
|
{isAuthenticated ? (
|
||||||
? <div className="tw-flex-none">
|
<div className='tw-flex-none'>
|
||||||
<Link to={`${userProfile.id && '/item/' + userProfile.id}`} className="tw-flex tw-items-center">
|
<Link
|
||||||
{ userProfile?.image && <div className="tw-avatar">
|
to={`${userProfile.id && '/item/' + userProfile.id}`}
|
||||||
<div className="tw-w-10 tw-rounded-full">
|
className='tw-flex tw-items-center'
|
||||||
<img src={'https://api.utopia-lab.org/assets/' + userProfile.image} />
|
>
|
||||||
|
{userProfile?.image && (
|
||||||
|
<div className='tw-avatar'>
|
||||||
|
<div className='tw-w-10 tw-rounded-full'>
|
||||||
|
<img src={'https://api.utopia-lab.org/assets/' + userProfile.image} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className='tw-ml-2 tw-mr-2'>{userProfile.name || user?.first_name}</div>
|
||||||
|
</Link>
|
||||||
|
<div className='tw-dropdown tw-dropdown-end'>
|
||||||
|
<label tabIndex={0} className='tw-btn tw-btn-ghost tw-btn-square'>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
className='tw-h-5 tw-w-5'
|
||||||
|
viewBox='0 0 20 20'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</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]'
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<Link to={`${userProfile.id && '/edit-item/' + userProfile.id}`}>Profile</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to={'/user-settings'}>Settings</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
onLogout()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
|
||||||
<div className='tw-ml-2 tw-mr-2'>{userProfile.name || user?.first_name}</div>
|
|
||||||
</Link>
|
|
||||||
<div className="tw-dropdown tw-dropdown-end">
|
|
||||||
<label tabIndex={0} className="tw-btn tw-btn-ghost tw-btn-square">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<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>
|
|
||||||
</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]">
|
|
||||||
<li><Link to={`${userProfile.id && '/edit-item/' + userProfile.id}`}>Profile</Link></li>
|
|
||||||
<li><Link to={'/user-settings'}>Settings</Link></li>
|
|
||||||
<li><a onClick={() => { onLogout() }}>Logout</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
: <div>
|
<div>
|
||||||
|
<div className='tw-hidden md:tw-flex'>
|
||||||
|
<Link to={'/login'}>
|
||||||
|
<div className='tw-btn tw-btn-ghost tw-mr-2'>Login</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className="tw-hidden md:tw-flex">
|
<Link to={'/signup'}>
|
||||||
<Link to={'/login'}><div className="tw-btn tw-btn-ghost tw-mr-2">
|
<div className='tw-btn tw-btn-ghost tw-mr-2'>Sign Up</div>
|
||||||
Login
|
</Link>
|
||||||
</div></Link>
|
</div>
|
||||||
|
<div className='tw-dropdown tw-dropdown-end'>
|
||||||
<Link to={'/signup'}><div className="tw-btn tw-btn-ghost tw-mr-2">
|
<label tabIndex={1} className='tw-btn tw-btn-ghost md:tw-hidden'>
|
||||||
Sign Up
|
<svg
|
||||||
</div></Link>
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
className='tw-h-5 tw-w-5'
|
||||||
|
viewBox='0 0 20 20'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</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]'
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<Link to={'/login'}>Login</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to={'/signup'}>Sign Up</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-dropdown tw-dropdown-end">
|
)}
|
||||||
<label tabIndex={1} className="tw-btn tw-btn-ghost md:tw-hidden">
|
</div>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
</>
|
||||||
<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>
|
|
||||||
|
|
||||||
</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]">
|
|
||||||
<li><Link to={'/login'}>Login</Link></li>
|
|
||||||
<li><Link to={'/signup'}>Sign Up</Link></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
} else return (<></>)
|
} else return <></>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ 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(() => {
|
||||||
@ -10,7 +10,5 @@ export const SetAssetsApi = ({ assetsApi }:{assetsApi: AssetsApi}) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [assetsApi])
|
}, [assetsApi])
|
||||||
|
|
||||||
return (
|
return <></>
|
||||||
<></>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,20 @@
|
|||||||
import { useRef, useState, useEffect } from 'react'
|
import { useRef, useState, useEffect } from 'react'
|
||||||
import { NavLink, useLocation } from 'react-router-dom'
|
import { NavLink, useLocation } from 'react-router-dom'
|
||||||
import {
|
import { Sidenav, initTE } from 'tw-elements'
|
||||||
Sidenav,
|
|
||||||
initTE
|
|
||||||
} 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
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
icon: JSX.Element;
|
icon: JSX.Element
|
||||||
name: string;
|
name: string
|
||||||
submenu?: route;
|
submenu?: 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)
|
||||||
|
|
||||||
@ -34,9 +31,7 @@ export function SideBar ({ routes, bottomRoutes }: { routes: route[], bottomRout
|
|||||||
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
|
||||||
@ -55,84 +50,117 @@ export function SideBar ({ routes, bottomRoutes }: { routes: route[], bottomRout
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
id="sidenav"
|
id='sidenav'
|
||||||
className={`group tw-fixed tw-left-0 ${embedded ? 'tw-mt-0 tw-h-[100dvh]' : 'tw-mt-16 tw-h-[calc(100dvh-64px)]'} tw-top-0 tw-z-[10035] tw--translate-x-full tw-overflow-hidden tw-shadow-xl data-[te-sidenav-slim='true']:tw-hidden data-[te-sidenav-slim-collapsed='true']:tw-w-[56px] data-[te-sidenav-slim='true']:tw-w-[56px] data-[te-sidenav-hidden='false']:tw-translate-x-0 dark:tw-bg-zinc-800 [&[data-te-sidenav-slim-collapsed='true'][data-te-sidenav-slim='false']]:tw-hidden [&[data-te-sidenav-slim-collapsed='true'][data-te-sidenav-slim='true']]:[display:unset]`}
|
className={`group tw-fixed tw-left-0 ${embedded ? 'tw-mt-0 tw-h-[100dvh]' : 'tw-mt-16 tw-h-[calc(100dvh-64px)]'} tw-top-0 tw-z-[10035] tw--translate-x-full tw-overflow-hidden tw-shadow-xl data-[te-sidenav-slim='true']:tw-hidden data-[te-sidenav-slim-collapsed='true']:tw-w-[56px] data-[te-sidenav-slim='true']:tw-w-[56px] data-[te-sidenav-hidden='false']:tw-translate-x-0 dark:tw-bg-zinc-800 [&[data-te-sidenav-slim-collapsed='true'][data-te-sidenav-slim='false']]:tw-hidden [&[data-te-sidenav-slim-collapsed='true'][data-te-sidenav-slim='true']]:[display:unset]`}
|
||||||
data-te-sidenav-init
|
data-te-sidenav-init
|
||||||
data-te-sidenav-hidden="true"
|
data-te-sidenav-hidden='true'
|
||||||
data-te-sidenav-mode="side"
|
data-te-sidenav-mode='side'
|
||||||
data-te-sidenav-slim="true"
|
data-te-sidenav-slim='true'
|
||||||
data-te-sidenav-content="#app-content"
|
data-te-sidenav-content='#app-content'
|
||||||
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)]'}`}>
|
>
|
||||||
<ul className="tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0" data-te-sidenav-menu-ref>
|
<div
|
||||||
{
|
className={`tw-flex tw-flex-col ${embedded ? 'tw-h-full' : 'tw-h-[calc(100dvh-64px)]'}`}
|
||||||
routes.map((route, k) => {
|
>
|
||||||
return (
|
<ul
|
||||||
<li className="" key={k}>
|
className='tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0'
|
||||||
{
|
data-te-sidenav-menu-ref
|
||||||
route.submenu
|
>
|
||||||
? <SidebarSubmenu {...route} />
|
{routes.map((route, k) => {
|
||||||
: (<NavLink
|
return (
|
||||||
end
|
<li className='' key={k}>
|
||||||
target={route.blank ? '_blank' : '_self'}
|
{route.submenu ? (
|
||||||
to={`${route.path}${params && '?' + params}`}
|
<SidebarSubmenu {...route} />
|
||||||
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()
|
<NavLink
|
||||||
}}>
|
end
|
||||||
{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>
|
target={route.blank ? '_blank' : '_self'}
|
||||||
{
|
to={`${route.path}${params && '?' + params}`}
|
||||||
(location.pathname.includes(route.path) && route.path.length > 1) || location.pathname === route.path
|
className={({ isActive }) =>
|
||||||
? (<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 "
|
`${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`
|
||||||
aria-hidden="true"></span>)
|
}
|
||||||
: null
|
onClick={() => {
|
||||||
}
|
if (screen.width < 640 && !slim) instance.toggle()
|
||||||
</NavLink>)
|
}}
|
||||||
}
|
>
|
||||||
|
{route.icon}
|
||||||
</li>
|
<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 '
|
||||||
|
aria-hidden='true'
|
||||||
|
></span>
|
||||||
|
) : null}
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div id="slim-toggler" className='tw-w-full tw-bg-base-100 tw-flex-1 tw-grid tw-place-items-end' aria-haspopup="true" >
|
<div
|
||||||
|
id='slim-toggler'
|
||||||
|
className='tw-w-full tw-bg-base-100 tw-flex-1 tw-grid tw-place-items-end'
|
||||||
|
aria-haspopup='true'
|
||||||
|
>
|
||||||
<div className='tw-w-full'>
|
<div className='tw-w-full'>
|
||||||
|
<ul
|
||||||
<ul className="tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0 tw-mb-0" data-te-sidenav-menu-ref>
|
className='tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0 tw-mb-0'
|
||||||
{
|
data-te-sidenav-menu-ref
|
||||||
bottomRoutes?.map((route, k) => {
|
>
|
||||||
return (
|
{bottomRoutes?.map((route, k) => {
|
||||||
<li className="" key={k}>
|
return (
|
||||||
{
|
<li className='' key={k}>
|
||||||
route.submenu
|
{route.submenu ? (
|
||||||
? <SidebarSubmenu {...route} />
|
<SidebarSubmenu {...route} />
|
||||||
: (<NavLink
|
) : (
|
||||||
end
|
<NavLink
|
||||||
target={route.blank ? '_blank' : '_self'}
|
end
|
||||||
to={route.path}
|
target={route.blank ? '_blank' : '_self'}
|
||||||
className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => {
|
to={route.path}
|
||||||
if (screen.width < 640 && !slim) instance.toggle()
|
className={({ isActive }) =>
|
||||||
}}>
|
`${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`
|
||||||
{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>
|
}
|
||||||
{
|
onClick={() => {
|
||||||
(location.pathname.includes(route.path) && route.path.length > 1) || location.pathname === route.path
|
if (screen.width < 640 && !slim) instance.toggle()
|
||||||
? (<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
|
{route.icon}
|
||||||
}
|
<span
|
||||||
</NavLink>)
|
className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden"
|
||||||
}
|
data-te-sidenav-slim='false'
|
||||||
|
>
|
||||||
</li>
|
{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 '
|
||||||
|
aria-hidden='true'
|
||||||
|
></span>
|
||||||
|
) : null}
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,52 +2,74 @@ 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({
|
||||||
// eslint-disable-next-line no-undef
|
submenu,
|
||||||
icon: JSX.Element;
|
name,
|
||||||
name: string;
|
icon,
|
||||||
submenu?: any | undefined}) {
|
}: {
|
||||||
|
path: string
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
icon: JSX.Element
|
||||||
|
name: string
|
||||||
|
submenu?: any | undefined
|
||||||
|
}) {
|
||||||
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)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex-col'>
|
<div className='flex-col'>
|
||||||
|
{/** Route header */}
|
||||||
|
<div className='w-full' onClick={() => setIsExpanded(!isExpanded)}>
|
||||||
|
{icon}{' '}
|
||||||
|
<span
|
||||||
|
className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:hidden"
|
||||||
|
data-te-sidenav-slim='false'
|
||||||
|
>
|
||||||
|
{name}{' '}
|
||||||
|
</span>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className={
|
||||||
|
'w-5 h-5 mt-1 float-right delay-400 duration-500 transition-all ' +
|
||||||
|
(isExpanded ? 'rotate-180' : '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/** Route header */}
|
{/** Submenu list */}
|
||||||
<div className='w-full' onClick={() => setIsExpanded(!isExpanded)}>
|
<div className={' w-full data-[te-collapse-show]:!hidden ' + (isExpanded ? '' : 'hidden')}>
|
||||||
{icon} <span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:hidden" data-te-sidenav-slim="false">{name} </span>
|
<ul className={'menu menu-compact'}>
|
||||||
<ChevronDownIcon className={'w-5 h-5 mt-1 float-right delay-400 duration-500 transition-all ' + (isExpanded ? 'rotate-180' : '')}/>
|
{submenu.map((m, k) => {
|
||||||
</div>
|
return (
|
||||||
|
<li key={k}>
|
||||||
{/** Submenu list */}
|
<Link to={m.path} className=''>
|
||||||
<div className={' w-full data-[te-collapse-show]:!hidden ' + (isExpanded ? '' : 'hidden')} >
|
{m.icon}
|
||||||
<ul className={'menu menu-compact'} >
|
<span className='' data-te-sidenav-slim='false'>
|
||||||
{
|
{m.name}
|
||||||
submenu.map((m, k) => {
|
</span>
|
||||||
return (
|
{location.pathname === m.path ? (
|
||||||
<li key={k} >
|
<span
|
||||||
<Link to={m.path} className='' >
|
className='absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary '
|
||||||
{m.icon}<span className="" data-te-sidenav-slim="false">{m.name}</span>
|
aria-hidden='true'
|
||||||
{
|
></span>
|
||||||
location.pathname === m.path
|
) : null}
|
||||||
? (<span className="absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary "
|
</Link>
|
||||||
aria-hidden="true"></span>)
|
</li>
|
||||||
: null
|
)
|
||||||
}
|
})}
|
||||||
</Link>
|
</ul>
|
||||||
</li>
|
</div>
|
||||||
)
|
</div>
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
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()
|
||||||
@ -12,7 +12,7 @@ export const Sitemap = ({ url }:{url:string}) => {
|
|||||||
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'
|
||||||
@ -24,7 +24,7 @@ export const Sitemap = ({ url }:{url:string}) => {
|
|||||||
|
|
||||||
setSitemap(generateSitemap())
|
setSitemap(generateSitemap())
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [items])
|
}, [items])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -3,18 +3,18 @@ import { useCallback, useState, createContext, useContext } from 'react'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { AssetsApi } from '../../../types'
|
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) => {
|
||||||
@ -27,9 +27,7 @@ function useAssetsManager (): {
|
|||||||
export const AssetsProvider: React.FunctionComponent<{
|
export const AssetsProvider: React.FunctionComponent<{
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
}> = ({ children }) => (
|
}> = ({ children }) => (
|
||||||
<AssetContext.Provider value={useAssetsManager()}>
|
<AssetContext.Provider value={useAssetsManager()}>{children}</AssetContext.Provider>
|
||||||
{children}
|
|
||||||
</AssetContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useAssetApi = (): AssetsApi => {
|
export const useAssetApi = (): AssetsApi => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { toast } from 'react-toastify'
|
|||||||
import { useAuth } from './useAuth'
|
import { useAuth } from './useAuth'
|
||||||
import { MapOverlayPage } from '../Templates'
|
import { MapOverlayPage } from '../Templates'
|
||||||
|
|
||||||
export function LoginPage () {
|
export function LoginPage() {
|
||||||
const [email, setEmail] = useState<string>('')
|
const [email, setEmail] = useState<string>('')
|
||||||
const [password, setPassword] = useState<string>('')
|
const [password, setPassword] = useState<string>('')
|
||||||
|
|
||||||
@ -14,29 +14,27 @@ export function LoginPage () {
|
|||||||
|
|
||||||
// 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, password }), {
|
||||||
login({ email, password }),
|
success: {
|
||||||
{
|
render({ data }) {
|
||||||
success: {
|
navigate('/')
|
||||||
render ({ data }) {
|
return `Hi ${data?.first_name}`
|
||||||
navigate('/')
|
|
||||||
return `Hi ${data?.first_name}`
|
|
||||||
},
|
|
||||||
// other options
|
|
||||||
icon: '✌️'
|
|
||||||
},
|
},
|
||||||
error: {
|
// other options
|
||||||
render ({ data }) {
|
icon: '✌️',
|
||||||
return `${data}`
|
},
|
||||||
},
|
error: {
|
||||||
autoClose: 10000
|
render({ data }) {
|
||||||
|
return `${data}`
|
||||||
},
|
},
|
||||||
pending: 'logging in ...'
|
autoClose: 10000,
|
||||||
})
|
},
|
||||||
|
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()
|
||||||
@ -49,15 +47,40 @@ export function LoginPage () {
|
|||||||
}, [onLogin])
|
}, [onLogin])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
||||||
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Login</h2>
|
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Login</h2>
|
||||||
<input type="email" placeholder="E-Mail" value={email} onChange={e => setEmail(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
<input
|
||||||
<input type="password" placeholder="Password" onChange={e => setPassword(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
type='email'
|
||||||
<div className='tw-text-right tw-text-primary'><Link to="/reset-password"><span className="tw-text-sm tw-inline-block hover:tw-text-primary hover:tw-underline hover:tw-cursor-pointer tw-transition tw-duration-200">Forgot Password?</span></Link>
|
placeholder='E-Mail'
|
||||||
</div>
|
value={email}
|
||||||
<div className="tw-card-actions">
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
<button className={loading ? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary' : 'tw-btn tw-btn-primary tw-btn-block'} onClick={() => onLogin()}>{loading ? <span className="tw-loading tw-loading-spinner"></span> : 'Login'}</button>
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
</div>
|
/>
|
||||||
</MapOverlayPage>
|
<input
|
||||||
|
type='password'
|
||||||
|
placeholder='Password'
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
|
/>
|
||||||
|
<div className='tw-text-right tw-text-primary'>
|
||||||
|
<Link to='/reset-password'>
|
||||||
|
<span className='tw-text-sm tw-inline-block hover:tw-text-primary hover:tw-underline hover:tw-cursor-pointer tw-transition tw-duration-200'>
|
||||||
|
Forgot Password?
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className='tw-card-actions'>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
loading
|
||||||
|
? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary'
|
||||||
|
: 'tw-btn tw-btn-primary tw-btn-block'
|
||||||
|
}
|
||||||
|
onClick={() => onLogin()}
|
||||||
|
>
|
||||||
|
{loading ? <span className='tw-loading tw-loading-spinner'></span> : 'Login'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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 ({ resetUrl }) {
|
export function RequestPasswordPage({ resetUrl }) {
|
||||||
const [email, setEmail] = useState<string>('')
|
const [email, setEmail] = useState<string>('')
|
||||||
|
|
||||||
const { requestPasswordReset, loading } = useAuth()
|
const { requestPasswordReset, loading } = useAuth()
|
||||||
@ -13,33 +13,46 @@ export function RequestPasswordPage ({ resetUrl }) {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const onReset = async () => {
|
const onReset = async () => {
|
||||||
await toast.promise(
|
await toast.promise(requestPasswordReset(email, resetUrl), {
|
||||||
requestPasswordReset(email, resetUrl),
|
success: {
|
||||||
{
|
render() {
|
||||||
success: {
|
navigate('/')
|
||||||
render () {
|
return 'Check your mailbox'
|
||||||
navigate('/')
|
|
||||||
return 'Check your mailbox'
|
|
||||||
},
|
|
||||||
// other options
|
|
||||||
icon: '📬'
|
|
||||||
},
|
},
|
||||||
error: {
|
// other options
|
||||||
render ({ data }) {
|
icon: '📬',
|
||||||
return `${data}`
|
},
|
||||||
}
|
error: {
|
||||||
|
render({ data }) {
|
||||||
|
return `${data}`
|
||||||
},
|
},
|
||||||
pending: 'sending email ...'
|
},
|
||||||
})
|
pending: 'sending email ...',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
||||||
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Reset Password</h2>
|
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Reset Password</h2>
|
||||||
<input type="email" placeholder="E-Mail" value={email} onChange={e => setEmail(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
<input
|
||||||
<div className="tw-card-actions tw-mt-4">
|
type='email'
|
||||||
<button className={loading ? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary' : 'tw-btn tw-btn-primary tw-btn-block'} onClick={() => onReset()}>{loading ? <span className="tw-loading tw-loading-spinner"></span> : 'Send'}</button>
|
placeholder='E-Mail'
|
||||||
</div>
|
value={email}
|
||||||
</MapOverlayPage>
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
|
/>
|
||||||
|
<div className='tw-card-actions tw-mt-4'>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
loading
|
||||||
|
? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary'
|
||||||
|
: 'tw-btn tw-btn-primary tw-btn-block'
|
||||||
|
}
|
||||||
|
onClick={() => onReset()}
|
||||||
|
>
|
||||||
|
{loading ? <span className='tw-loading tw-loading-spinner'></span> : 'Send'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { toast } from 'react-toastify'
|
|||||||
import { useAuth } from './useAuth'
|
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()
|
||||||
@ -16,31 +16,43 @@ export function SetNewPasswordPage () {
|
|||||||
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: {
|
||||||
{
|
render() {
|
||||||
success: {
|
navigate('/')
|
||||||
render () {
|
return 'New password set'
|
||||||
navigate('/')
|
|
||||||
return 'New password set'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
error: {
|
},
|
||||||
render ({ data }) {
|
error: {
|
||||||
return `${data}`
|
render({ data }) {
|
||||||
}
|
return `${data}`
|
||||||
},
|
},
|
||||||
pending: 'setting password ...'
|
},
|
||||||
})
|
pending: 'setting password ...',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
||||||
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Set new Password</h2>
|
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Set new Password</h2>
|
||||||
<input type="password" placeholder="Password" onChange={e => setPassword(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
<input
|
||||||
<div className="tw-card-actions tw-mt-4">
|
type='password'
|
||||||
<button className={loading ? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary' : 'tw-btn tw-btn-primary tw-btn-block'} onClick={() => onReset()}>{loading ? <span className="tw-loading tw-loading-spinner"></span> : 'Set'}</button>
|
placeholder='Password'
|
||||||
</div>
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
</MapOverlayPage>
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
|
/>
|
||||||
|
<div className='tw-card-actions tw-mt-4'>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
loading
|
||||||
|
? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary'
|
||||||
|
: 'tw-btn tw-btn-primary tw-btn-block'
|
||||||
|
}
|
||||||
|
onClick={() => onReset()}
|
||||||
|
>
|
||||||
|
{loading ? <span className='tw-loading tw-loading-spinner'></span> : 'Set'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { toast } from 'react-toastify'
|
|||||||
import { useAuth } from './useAuth'
|
import { useAuth } from './useAuth'
|
||||||
import { MapOverlayPage } from '../Templates'
|
import { MapOverlayPage } from '../Templates'
|
||||||
|
|
||||||
export function SignupPage () {
|
export function SignupPage() {
|
||||||
const [email, setEmail] = useState<string>('')
|
const [email, setEmail] = useState<string>('')
|
||||||
const [userName, setUserName] = useState<string>('')
|
const [userName, setUserName] = useState<string>('')
|
||||||
|
|
||||||
@ -17,29 +17,27 @@ export function SignupPage () {
|
|||||||
|
|
||||||
// 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, password }, userName), {
|
||||||
register({ email, password }, userName),
|
success: {
|
||||||
{
|
render({ data }) {
|
||||||
success: {
|
navigate('/')
|
||||||
render ({ data }) {
|
return `Hi ${data?.first_name}`
|
||||||
navigate('/')
|
|
||||||
return `Hi ${data?.first_name}`
|
|
||||||
},
|
|
||||||
// other options
|
|
||||||
icon: '✌️'
|
|
||||||
},
|
},
|
||||||
error: {
|
// other options
|
||||||
render ({ data }) {
|
icon: '✌️',
|
||||||
return `${data}`
|
},
|
||||||
},
|
error: {
|
||||||
autoClose: 10000
|
render({ data }) {
|
||||||
|
return `${data}`
|
||||||
},
|
},
|
||||||
pending: 'creating new user ...'
|
autoClose: 10000,
|
||||||
})
|
},
|
||||||
|
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()
|
||||||
@ -52,14 +50,40 @@ export function SignupPage () {
|
|||||||
}, [onRegister])
|
}, [onRegister])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
<MapOverlayPage backdrop className='tw-max-w-xs tw-h-fit'>
|
||||||
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Sign Up</h2>
|
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Sign Up</h2>
|
||||||
<input type="text" placeholder="Name" value={userName} onChange={e => setUserName(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
<input
|
||||||
<input type="email" placeholder="E-Mail" value={email} onChange={e => setEmail(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
type='text'
|
||||||
<input type="password" placeholder="Password" onChange={e => setPassword(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
placeholder='Name'
|
||||||
<div className="tw-card-actions tw-mt-4">
|
value={userName}
|
||||||
<button className={loading ? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary' : 'tw-btn tw-btn-primary tw-btn-block'} onClick={() => onRegister()}>{loading ? <span className="tw-loading tw-loading-spinner"></span> : 'Sign Up'}</button>
|
onChange={(e) => setUserName(e.target.value)}
|
||||||
</div>
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
</MapOverlayPage>
|
/>
|
||||||
|
<input
|
||||||
|
type='email'
|
||||||
|
placeholder='E-Mail'
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
placeholder='Password'
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className='tw-input tw-input-bordered tw-w-full tw-max-w-xs'
|
||||||
|
/>
|
||||||
|
<div className='tw-card-actions tw-mt-4'>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
loading
|
||||||
|
? 'tw-btn tw-btn-disabled tw-btn-block tw-btn-primary'
|
||||||
|
: 'tw-btn tw-btn-primary tw-btn-block'
|
||||||
|
}
|
||||||
|
onClick={() => onRegister()}
|
||||||
|
>
|
||||||
|
{loading ? <span className='tw-loading tw-loading-spinner'></span> : 'Sign Up'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,32 +3,32 @@ import * as React from 'react'
|
|||||||
import { UserApi, UserItem } from '../../types'
|
import { UserApi, UserItem } from '../../types'
|
||||||
|
|
||||||
type AuthProviderProps = {
|
type AuthProviderProps = {
|
||||||
userApi: UserApi,
|
userApi: UserApi
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthCredentials = {
|
type AuthCredentials = {
|
||||||
email: string;
|
email: string
|
||||||
password: string;
|
password: string
|
||||||
otp?: string | undefined;
|
otp?: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthContextProps = {
|
type AuthContextProps = {
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean
|
||||||
user: UserItem | null;
|
user: UserItem | null
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
login: (credentials: AuthCredentials) => Promise<UserItem | undefined>,
|
login: (credentials: AuthCredentials) => Promise<UserItem | undefined>
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
register: (credentials: AuthCredentials, userName: string) => Promise<UserItem | undefined>,
|
register: (credentials: AuthCredentials, userName: string) => Promise<UserItem | undefined>
|
||||||
loading: boolean,
|
loading: boolean
|
||||||
logout: () => Promise<any>,
|
logout: () => Promise<any>
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
updateUser: (user: UserItem) => any,
|
updateUser: (user: UserItem) => any
|
||||||
token: string | null,
|
token: string | null
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
requestPasswordReset: (email:string, reset_url: string) => Promise<any>,
|
requestPasswordReset: (email: string, reset_url: string) => Promise<any>
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
passwordReset: (token:string, new_password:string) => Promise<any>
|
passwordReset: (token: string, new_password: string) => Promise<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextProps>({
|
const AuthContext = createContext<AuthContextProps>({
|
||||||
@ -41,7 +41,7 @@ const AuthContext = createContext<AuthContextProps>({
|
|||||||
updateUser: () => Promise.reject(Error('Unimplemented')),
|
updateUser: () => Promise.reject(Error('Unimplemented')),
|
||||||
token: '',
|
token: '',
|
||||||
requestPasswordReset: () => Promise.reject(Error('Unimplemented')),
|
requestPasswordReset: () => Promise.reject(Error('Unimplemented')),
|
||||||
passwordReset: () => Promise.reject(Error('Unimplemented'))
|
passwordReset: () => Promise.reject(Error('Unimplemented')),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
||||||
@ -54,10 +54,10 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
|||||||
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)
|
||||||
@ -78,18 +78,21 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
|||||||
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
|
||||||
@ -134,7 +137,7 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordReset = async (token: string, newPassword:string): Promise<any> => {
|
const passwordReset = async (token: string, newPassword: string): Promise<any> => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
await userApi.passwordReset(token, newPassword)
|
await userApi.passwordReset(token, newPassword)
|
||||||
@ -147,7 +150,18 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
|||||||
|
|
||||||
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>
|
||||||
|
|||||||
@ -1,25 +1,34 @@
|
|||||||
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) { window.my_modal_3.showModal() }
|
if (showOnStartup) {
|
||||||
|
window.my_modal_3.showModal()
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* You can open the modal using ID.showModal() method */}
|
||||||
{/* You can open the modal using ID.showModal() method */}
|
<dialog id='my_modal_3' className='tw-modal tw-transition-all tw-duration-300'>
|
||||||
<dialog id="my_modal_3" className="tw-modal tw-transition-all tw-duration-300">
|
<form method='dialog' className='tw-modal-box tw-transition-none'>
|
||||||
<form method="dialog" className="tw-modal-box tw-transition-none">
|
<button className='tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2 focus:tw-outline-none'>
|
||||||
<button className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2 focus:tw-outline-none">✕</button>
|
✕
|
||||||
{children}
|
</button>
|
||||||
</form>
|
{children}
|
||||||
<form method="dialog" className="tw-modal-backdrop">
|
</form>
|
||||||
<button>close</button>
|
<form method='dialog' className='tw-modal-backdrop'>
|
||||||
</form>
|
<button>close</button>
|
||||||
</dialog>
|
</form>
|
||||||
</>
|
</dialog>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ 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()
|
||||||
@ -19,33 +19,87 @@ export function Quests () {
|
|||||||
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
|
<>
|
||||||
? <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]">
|
{questsOpen ? (
|
||||||
<div className="tw-card-body tw-p-4 tw-pt-0">
|
<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-actions tw-justify-end">
|
<div className='tw-card-body tw-p-4 tw-pt-0'>
|
||||||
<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>
|
<div className='tw-card-actions tw-justify-end'>
|
||||||
</div>
|
<label
|
||||||
<h2 className="tw-card-title tw-m-auto ">
|
className='tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-1 tw-top-1'
|
||||||
Level 1
|
onClick={() => setQuestsOpen(false)}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="#aaa" className="tw-w-5 tw-h-5 tw-cursor-pointer">
|
>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
|
✕
|
||||||
</svg>
|
</label>
|
||||||
|
|
||||||
</h2>
|
|
||||||
<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 || 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} /><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} /><span className='tw-text-sm tw-label-text tw-mx-2'>Upload Avatar</span></label></li>
|
|
||||||
</ul>
|
|
||||||
{ /** <button className='tw-btn tw-btn-xs tw-btn-neutral tw-w-fit tw-self-center tw-mt-1'>Next ></button> */
|
|
||||||
} </div>
|
|
||||||
</div>
|
</div>
|
||||||
: ''
|
<h2 className='tw-card-title tw-m-auto '>
|
||||||
}
|
Level 1
|
||||||
</>
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
fill='none'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke='#aaa'
|
||||||
|
className='tw-w-5 tw-h-5 tw-cursor-pointer'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
d='M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</h2>
|
||||||
|
<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 || 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}
|
||||||
|
/>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
<span className='tw-text-sm tw-label-text tw-mx-2'>Upload Avatar</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{/** <button className='tw-btn tw-btn-xs tw-btn-neutral tw-w-fit tw-self-center tw-mt-1'>Next ></button> */}{' '}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { useCallback, useState, createContext, useContext } from 'react'
|
import { useCallback, useState, 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)
|
||||||
|
|
||||||
@ -23,11 +23,10 @@ function useQuestsManager (initialOpen: boolean): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const QuestsProvider: React.FunctionComponent<{
|
export const QuestsProvider: React.FunctionComponent<{
|
||||||
initialOpen: boolean, children?: React.ReactNode
|
initialOpen: boolean
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialOpen, children }) => (
|
}> = ({ initialOpen, children }) => (
|
||||||
<QuestContext.Provider value={useQuestsManager(initialOpen)}>
|
<QuestContext.Provider value={useQuestsManager(initialOpen)}>{children}</QuestContext.Provider>
|
||||||
{children}
|
|
||||||
</QuestContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useQuestsOpen = (): boolean => {
|
export const useQuestsOpen = (): boolean => {
|
||||||
|
|||||||
@ -3,7 +3,19 @@ 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)
|
||||||
|
|
||||||
@ -18,17 +30,15 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
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
|
return inputLength === 0
|
||||||
? []
|
? []
|
||||||
: suggestions.filter(tag =>
|
: suggestions.filter((tag) => tag.name.toLowerCase().slice(0, inputLength) === inputValue)
|
||||||
tag.name.toLowerCase().slice(0, inputLength) === inputValue
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
@ -40,17 +50,18 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSuggestionClick (suggestion) {
|
function handleSuggestionClick(suggestion) {
|
||||||
onSelected(suggestion)
|
onSelected(suggestion)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
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) {
|
||||||
@ -67,10 +78,21 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} tabIndex="-1" onKeyDown={handleKeyDown}/>
|
<input
|
||||||
<ul className={`tw-absolute tw-z-[4000] ${filteredSuggestions.length > 0 && 'tw-bg-base-100 tw-rounded-xl tw-p-2'}`}>
|
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'}`}
|
||||||
|
>
|
||||||
{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>
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { useState } from 'react'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
interface ComboBoxProps {
|
interface ComboBoxProps {
|
||||||
id?: string;
|
id?: string
|
||||||
options: { value: string, label: string }[];
|
options: { value: string; label: string }[]
|
||||||
value: string;
|
value: string
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
onValueChange: (newValue: string) => void;
|
onValueChange: (newValue: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => {
|
const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => {
|
||||||
@ -22,12 +22,14 @@ const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) =>
|
|||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
id={id}
|
id={id}
|
||||||
className="tw-form-select tw-block tw-w-full tw-py-2 tw-px-4 tw-border tw-border-gray-300 rounded-md tw-shadow-sm tw-text-sm focus:tw-outline-none focus:tw-ring-indigo-500 focus:tw-border-indigo-500 sm:tw-text-sm"
|
className='tw-form-select tw-block tw-w-full tw-py-2 tw-px-4 tw-border tw-border-gray-300 rounded-md tw-shadow-sm tw-text-sm focus:tw-outline-none focus:tw-ring-indigo-500 focus:tw-border-indigo-500 sm:tw-text-sm'
|
||||||
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,21 +3,30 @@ import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircle
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
type SelectBoxProps = {
|
type SelectBoxProps = {
|
||||||
labelTitle?: string;
|
labelTitle?: string
|
||||||
labelStyle?: string;
|
labelStyle?: string
|
||||||
type?: string;
|
type?: string
|
||||||
containerStyle?: string;
|
containerStyle?: string
|
||||||
defaultValue: string;
|
defaultValue: string
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
updateFormValue: (value: string) => void;
|
updateFormValue: (value: string) => void
|
||||||
|
|
||||||
options: {name: string, value: string}[];
|
options: { name: string; value: string }[]
|
||||||
labelDescription?: string
|
labelDescription?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
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 || '')
|
||||||
|
|
||||||
@ -27,22 +36,37 @@ export function SelectBox (props : SelectBoxProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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'>
|
||||||
{labelDescription && <div className="tw-tooltip tw-tooltip-right" data-tip={labelDescription}><InformationCircleIcon className='tw-w-4 tw-h-4'/></div>}
|
{labelTitle}
|
||||||
</div>
|
{labelDescription && (
|
||||||
</label>
|
<div className='tw-tooltip tw-tooltip-right' data-tip={labelDescription}>
|
||||||
: ''}
|
<InformationCircleIcon className='tw-w-4 tw-h-4' />
|
||||||
<select className="tw-select tw-select-bordered tw-w-full" value={value} onChange={(e) => updateValue(e.target.value)}>
|
</div>
|
||||||
<option disabled value="PLACEHOLDER">{placeholder}</option>
|
)}
|
||||||
{
|
</div>
|
||||||
options.map((o, k) => {
|
</label>
|
||||||
return <option value={o.value || o.name} key={k}>{o.name}</option>
|
) : (
|
||||||
})
|
''
|
||||||
}
|
)}
|
||||||
</select>
|
<select
|
||||||
</div>
|
className='tw-select tw-select-bordered tw-w-full'
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => updateValue(e.target.value)}
|
||||||
|
>
|
||||||
|
<option disabled value='PLACEHOLDER'>
|
||||||
|
{placeholder}
|
||||||
|
</option>
|
||||||
|
{options.map((o, k) => {
|
||||||
|
return (
|
||||||
|
<option value={o.value || o.name} key={k}>
|
||||||
|
{o.name}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,22 +4,31 @@ import Tribute from 'tributejs'
|
|||||||
import { useTags } from '../Map/hooks/useTags'
|
import { useTags } from '../Map/hooks/useTags'
|
||||||
|
|
||||||
type TextAreaProps = {
|
type TextAreaProps = {
|
||||||
labelTitle?: string;
|
labelTitle?: string
|
||||||
labelStyle?: string;
|
labelStyle?: string
|
||||||
containerStyle?: string;
|
containerStyle?: string
|
||||||
dataField?: string;
|
dataField?: string
|
||||||
inputStyle?: string;
|
inputStyle?: string
|
||||||
defaultValue: string;
|
defaultValue: string
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
updateFormValue?: (value: string) => void;
|
updateFormValue?: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KeyValue {
|
interface KeyValue {
|
||||||
[key: string]: string;
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@ -30,7 +39,7 @@ export function TextAreaInput ({ labelTitle, dataField, labelStyle, containerSty
|
|||||||
|
|
||||||
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 })
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -45,7 +54,7 @@ export function TextAreaInput ({ labelTitle, dataField, labelStyle, containerSty
|
|||||||
},
|
},
|
||||||
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(() => {
|
||||||
@ -71,23 +80,21 @@ export function TextAreaInput ({ labelTitle, dataField, labelStyle, containerSty
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`tw-form-control tw-w-full ${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}
|
||||||
)
|
<textarea
|
||||||
: null}
|
required
|
||||||
<textarea
|
ref={ref}
|
||||||
required
|
value={inputValue}
|
||||||
ref={ref}
|
name={dataField}
|
||||||
value={inputValue}
|
className={`tw-textarea tw-textarea-bordered tw-w-full tw-leading-5 ${inputStyle || ''}`}
|
||||||
name={dataField}
|
placeholder={placeholder || ''}
|
||||||
className={`tw-textarea tw-textarea-bordered tw-w-full tw-leading-5 ${inputStyle || ''}`}
|
onChange={handleChange}
|
||||||
placeholder={placeholder || ''}
|
></textarea>
|
||||||
onChange={handleChange}
|
</div>
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,20 +2,31 @@ import { useEffect, useState } from 'react'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
type InputTextProps = {
|
type InputTextProps = {
|
||||||
labelTitle?: string;
|
labelTitle?: string
|
||||||
labelStyle?: string;
|
labelStyle?: string
|
||||||
type?: string;
|
type?: string
|
||||||
dataField?: string;
|
dataField?: string
|
||||||
containerStyle?: string;
|
containerStyle?: string
|
||||||
inputStyle?: string;
|
inputStyle?: string
|
||||||
defaultValue?: string;
|
defaultValue?: string
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
autocomplete?: string
|
autocomplete?: string
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
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(() => {
|
||||||
@ -31,24 +42,22 @@ export function TextInput ({ labelTitle, labelStyle, type, dataField, containerS
|
|||||||
}
|
}
|
||||||
|
|
||||||
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}
|
||||||
)
|
<input
|
||||||
: null}
|
required
|
||||||
<input
|
type={type || 'text'}
|
||||||
required
|
name={dataField}
|
||||||
type={type || 'text'}
|
value={inputValue}
|
||||||
name={dataField}
|
placeholder={placeholder || ''}
|
||||||
value={inputValue}
|
autoComplete={autocomplete}
|
||||||
placeholder={placeholder || ''}
|
onChange={handleChange}
|
||||||
autoComplete={autocomplete}
|
className={`tw-input tw-input-bordered tw-w-full ${inputStyle || ''}`}
|
||||||
onChange={handleChange}
|
/>
|
||||||
className={`tw-input tw-input-bordered tw-w-full ${inputStyle || ''}`}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,17 @@ 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)
|
||||||
|
|
||||||
@ -11,23 +21,23 @@ export const ItemForm = ({ children, item, title, setPopupTitle }: { children?:
|
|||||||
}, [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, 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',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,13 @@ 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 }) : '',
|
||||||
? React.cloneElement(child, { item })
|
)
|
||||||
: ''
|
|
||||||
)
|
|
||||||
: ''}
|
: ''}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -18,9 +16,9 @@ export const ItemView = ({ children, item }: { children?: React.ReactNode, item?
|
|||||||
|
|
||||||
ItemView.propTypes = {
|
ItemView.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
__TYPE: PropTypes.string
|
__TYPE: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.defaultProps = {
|
ItemView.defaultProps = {
|
||||||
__TYPE: 'ItemView'
|
__TYPE: 'ItemView',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,12 @@ import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
|
|||||||
import { useAllItemsLoaded, useItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
|
import { useAllItemsLoaded, useItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { ItemFormPopup } from './Subcomponents/ItemFormPopup'
|
import { ItemFormPopup } from './Subcomponents/ItemFormPopup'
|
||||||
import { useFilterTags, useIsGroupTypeVisible, useIsLayerVisible, useVisibleGroupType } from './hooks/useFilter'
|
import {
|
||||||
|
useFilterTags,
|
||||||
|
useIsGroupTypeVisible,
|
||||||
|
useIsLayerVisible,
|
||||||
|
useVisibleGroupType,
|
||||||
|
} from './hooks/useFilter'
|
||||||
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'
|
||||||
@ -49,7 +54,7 @@ export const Layer = ({
|
|||||||
listed = true,
|
listed = true,
|
||||||
setItemFormPopup,
|
setItemFormPopup,
|
||||||
itemFormPopup,
|
itemFormPopup,
|
||||||
clusterRef
|
clusterRef,
|
||||||
}: LayerProps) => {
|
}: LayerProps) => {
|
||||||
const filterTags = useFilterTags()
|
const filterTags = useFilterTags()
|
||||||
|
|
||||||
@ -84,45 +89,121 @@ export const Layer = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
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 })
|
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
|
// 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 })
|
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 = ''
|
let title = ''
|
||||||
if (item.name) title = item.name
|
if (item.name) title = item.name
|
||||||
else if (item.layer?.itemNameField) title = getValue(item, item.layer.itemNameField)
|
else if (item.layer?.itemNameField) title = getValue(item, item.layer.itemNameField)
|
||||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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 &&
|
||||||
ref.marker.openPopup()
|
clusterRef.hasLayer(ref.marker) &&
|
||||||
})
|
clusterRef?.zoomToShowLayer(ref.marker, () => {
|
||||||
|
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
|
||||||
document.querySelector('meta[property="og:description"]')?.setAttribute('content', ref.item.text)
|
.querySelector('meta[property="og:title"]')
|
||||||
|
?.setAttribute('content', ref.item.name)
|
||||||
|
document
|
||||||
|
.querySelector('meta[property="og:description"]')
|
||||||
|
?.setAttribute('content', ref.item.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +217,7 @@ export const Layer = ({
|
|||||||
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)
|
||||||
@ -148,103 +229,179 @@ export const Layer = ({
|
|||||||
}, [tagsReady])
|
}, [tagsReady])
|
||||||
|
|
||||||
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
|
||||||
?.filter(item => item.layer && isLayerVisible(item.layer))
|
? item
|
||||||
.filter(item => (item.group_type && isGroupTypeVisible(item.group_type)) || visibleGroupTypes.length === 0)
|
: filterTags.some((tag) =>
|
||||||
.map((item: Item) => {
|
getItemTags(item).some(
|
||||||
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
(filterTag) =>
|
||||||
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField)
|
filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase(),
|
||||||
else item[itemTextField] = ''
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
?.filter((item) => item.layer && isLayerVisible(item.layer))
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
(item.group_type && isGroupTypeVisible(item.group_type)) ||
|
||||||
|
visibleGroupTypes.length === 0,
|
||||||
|
)
|
||||||
|
.map((item: Item) => {
|
||||||
|
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
||||||
|
if (getValue(item, itemTextField)) item[itemTextField] = getValue(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)}`)) { return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `) }
|
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) {
|
||||||
return item[itemTextField]
|
return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `)
|
||||||
})
|
}
|
||||||
}
|
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 (
|
||||||
const newTag = { id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() }
|
!tags.find(
|
||||||
setNewTagsToAdd(current => [...current, newTag])
|
(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(),
|
||||||
|
}
|
||||||
|
setNewTagsToAdd((current) => [...current, newTag])
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
!tagsReady && setTagsReady(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemTags = getItemTags(item)
|
||||||
|
|
||||||
|
const latitude =
|
||||||
|
itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined
|
||||||
|
const longitude =
|
||||||
|
itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined
|
||||||
|
|
||||||
|
let color1 = markerDefaultColor
|
||||||
|
let color2 = markerDefaultColor2
|
||||||
|
if (itemColorField && getValue(item, itemColorField) != null)
|
||||||
|
color1 = getValue(item, itemColorField)
|
||||||
|
else if (itemTags && itemTags[0]) {
|
||||||
|
color1 = itemTags[0].color
|
||||||
|
}
|
||||||
|
if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color
|
||||||
|
else if (itemTags && itemTags[1]) {
|
||||||
|
color2 = itemTags[1].color
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Marker
|
||||||
|
ref={(r) => {
|
||||||
|
if (!(item.id in leafletRefs && leafletRefs[item.id].marker === r)) {
|
||||||
|
r && addMarker(item, r)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
eventHandlers={{
|
||||||
|
click: () => {
|
||||||
|
selectPosition && setMarkerClicked(item)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
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',
|
||||||
|
) ? (
|
||||||
|
React.Children.toArray(children).map((child) =>
|
||||||
|
React.isValidElement(child) && child.props.__TYPE === 'ItemView' ? (
|
||||||
|
<ItemViewPopup
|
||||||
|
ref={(r) => {
|
||||||
|
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) {
|
||||||
|
r && addPopup(item, r as Popup)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
key={item.id + item.name}
|
||||||
|
item={item}
|
||||||
|
setItemFormPopup={setItemFormPopup}
|
||||||
|
>
|
||||||
|
{child}
|
||||||
|
</ItemViewPopup>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ItemViewPopup
|
||||||
|
key={item.id + item.name}
|
||||||
|
ref={(r) => {
|
||||||
|
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) {
|
||||||
|
r && addPopup(item, r as Popup)
|
||||||
}
|
}
|
||||||
return null
|
}}
|
||||||
})
|
item={item}
|
||||||
!tagsReady && setTagsReady(true)
|
setItemFormPopup={setItemFormPopup}
|
||||||
}
|
/>
|
||||||
|
</>
|
||||||
const itemTags = getItemTags(item)
|
)}
|
||||||
|
<Tooltip offset={[0, -38]} direction='top'>
|
||||||
const latitude = itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined
|
{item.name ? item.name : getValue(item, itemNameField)}
|
||||||
const longitude = itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined
|
</Tooltip>
|
||||||
|
</Marker>
|
||||||
let color1 = markerDefaultColor
|
)
|
||||||
let color2 = markerDefaultColor2
|
} else return null
|
||||||
if (itemColorField && getValue(item, itemColorField) != null) color1 = getValue(item, itemColorField)
|
})}
|
||||||
else if (itemTags && itemTags[0]) {
|
{
|
||||||
color1 = itemTags[0].color
|
// {children}}
|
||||||
}
|
}
|
||||||
if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color
|
{itemFormPopup &&
|
||||||
else if (itemTags && itemTags[1]) {
|
itemFormPopup.layer!.name === name &&
|
||||||
color2 = itemTags[1].color
|
(children &&
|
||||||
}
|
React.Children.toArray(children).some(
|
||||||
return (
|
(child) => React.isValidElement(child) && child.props.__TYPE === 'ItemForm',
|
||||||
<Marker ref={(r) => {
|
) ? (
|
||||||
if (!(item.id in leafletRefs && leafletRefs[item.id].marker === r)) { r && addMarker(item, r) }
|
React.Children.toArray(children).map((child) =>
|
||||||
}}
|
React.isValidElement(child) && child.props.__TYPE === 'ItemForm' ? (
|
||||||
eventHandlers={{
|
<ItemFormPopup
|
||||||
click: () => {
|
key={setItemFormPopup?.name}
|
||||||
selectPosition && setMarkerClicked(item)
|
position={itemFormPopup!.position}
|
||||||
}
|
layer={itemFormPopup!.layer}
|
||||||
}}
|
setItemFormPopup={setItemFormPopup}
|
||||||
icon={MarkerIconFactory(markerShape, color1, color2, item.markerIcon ? item.markerIcon : markerIcon)} key={item.id} position={[latitude, longitude]}>
|
item={itemFormPopup!.item}
|
||||||
{
|
>
|
||||||
(children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === 'ItemView')
|
{child}
|
||||||
? React.Children.toArray(children).map((child) =>
|
</ItemFormPopup>
|
||||||
React.isValidElement(child) && child.props.__TYPE === 'ItemView'
|
) : (
|
||||||
? <ItemViewPopup ref={(r) => {
|
''
|
||||||
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { r && addPopup(item, r as Popup) }
|
),
|
||||||
}} key={item.id + item.name}
|
)
|
||||||
item={item}
|
) : (
|
||||||
setItemFormPopup={setItemFormPopup}>
|
<>
|
||||||
{child}
|
<ItemFormPopup
|
||||||
</ItemViewPopup>
|
position={itemFormPopup!.position}
|
||||||
: ''
|
layer={itemFormPopup!.layer}
|
||||||
)
|
setItemFormPopup={setItemFormPopup}
|
||||||
: <>
|
item={itemFormPopup!.item}
|
||||||
<ItemViewPopup key={item.id + item.name} ref={(r) => {
|
/>
|
||||||
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { r && addPopup(item, r as Popup) }
|
</>
|
||||||
}}
|
))}
|
||||||
item={item}
|
</>
|
||||||
setItemFormPopup={setItemFormPopup} />
|
|
||||||
</>)
|
|
||||||
}
|
|
||||||
<Tooltip offset={[0, -38]} direction='top'>{item.name ? item.name : getValue(item, itemNameField)}</Tooltip>
|
|
||||||
</Marker>
|
|
||||||
)
|
|
||||||
} else return null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{// {children}}
|
|
||||||
}
|
|
||||||
{itemFormPopup && itemFormPopup.layer!.name === name &&
|
|
||||||
(children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === 'ItemForm')
|
|
||||||
? React.Children.toArray(children).map((child) =>
|
|
||||||
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 position={itemFormPopup!.position} layer={itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={itemFormPopup!.item} />
|
|
||||||
</>)
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,15 @@ 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()
|
||||||
@ -17,7 +25,5 @@ export function Permissions ({ data, api, adminRole } : {data?: Permission[], ap
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [api, data, adminRole, user])
|
}, [api, data, adminRole, user])
|
||||||
|
|
||||||
return (
|
return <></>
|
||||||
<></>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,51 +2,77 @@ 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 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
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
</svg>
|
fill='none'
|
||||||
</label>
|
viewBox='0 0 24 24'
|
||||||
<ul tabIndex={0} className="tw-dropdown-content tw-pr-1 tw-list-none">
|
strokeWidth='3'
|
||||||
{layers.map((layer) => (
|
stroke='currentColor'
|
||||||
layer.api?.createItem && hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) && layer.listed && (
|
className='tw-w-5 tw-h-5'
|
||||||
<li key={layer.name} >
|
>
|
||||||
<a>
|
<path strokeLinecap='round' strokeLinejoin='round' d='M12 4.5v15m7.5-7.5h-15' />
|
||||||
<div className="tw-tooltip tw-tooltip-left" data-tip={layer.menuText}>
|
</svg>
|
||||||
<button tabIndex={0}
|
</label>
|
||||||
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"
|
<ul tabIndex={0} className='tw-dropdown-content tw-pr-1 tw-list-none'>
|
||||||
style={{ backgroundColor: layer.menuColor || '#777' }}
|
{layers.map(
|
||||||
onClick={() => { triggerAction(layer) }}>
|
(layer) =>
|
||||||
<img src={layer.menuIcon} className="tw-h-6 tw-w-6 tw-text-white" style={{ filter: 'invert(100%) brightness(200%)' }} />
|
layer.api?.createItem &&
|
||||||
</button>
|
hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) &&
|
||||||
</div>
|
layer.listed && (
|
||||||
</a>
|
<li key={layer.name}>
|
||||||
</li>
|
<a>
|
||||||
)
|
<div className='tw-tooltip tw-tooltip-left' data-tip={layer.menuText}>
|
||||||
|
<button
|
||||||
))}
|
tabIndex={0}
|
||||||
</ul>
|
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'
|
||||||
</div>
|
style={{ backgroundColor: layer.menuColor || '#777' }}
|
||||||
: ''
|
onClick={() => {
|
||||||
}
|
triggerAction(layer)
|
||||||
</>
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={layer.menuIcon}
|
||||||
|
className='tw-h-6 tw-w-6 tw-text-white'
|
||||||
|
style={{ filter: 'invert(100%) brightness(200%)' }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
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(() => {
|
||||||
@ -12,9 +22,12 @@ export const Control = ({ position, children, zIndex, absolute }: { position: 't
|
|||||||
}, [controlContainerRef])
|
}, [controlContainerRef])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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'}`}>
|
<div
|
||||||
|
ref={controlContainerRef}
|
||||||
{children}
|
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}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,23 @@
|
|||||||
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) => addVisibleGroupType(layer.value))
|
||||||
addVisibleGroupType(layer.value)
|
|
||||||
)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -20,35 +27,67 @@ export function FilterControl () {
|
|||||||
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
|
||||||
<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={() => {
|
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'
|
||||||
setOpen(false)
|
onClick={() => {
|
||||||
}}>
|
setOpen(false)
|
||||||
<p className='tw-text-center '>✕</p></label>
|
}}
|
||||||
<ul className='tw-flex-row'>
|
>
|
||||||
{
|
<p className='tw-text-center '>✕</p>
|
||||||
groupTypes.map(groupType =>
|
</label>
|
||||||
<li key={groupType.value}><label htmlFor={groupType.value} className="tw-label tw-justify-normal tw-pt-1 tw-pb-1"><input id={groupType.value} onChange={() => toggleVisibleGroupType(groupType.value)} type="checkbox" className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={isGroupTypeVisible(groupType.value)} /><span className='tw-text-sm tw-label-text tw-mx-2 tw-cursor-pointer'>{groupType.text}</span></label></li>
|
<ul className='tw-flex-row'>
|
||||||
)
|
{groupTypes.map((groupType) => (
|
||||||
}
|
<li key={groupType.value}>
|
||||||
</ul>
|
<label
|
||||||
</div>
|
htmlFor={groupType.value}
|
||||||
: <div className="tw-indicator">
|
className='tw-label tw-justify-normal tw-pt-1 tw-pb-1'
|
||||||
{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={() => {
|
<input
|
||||||
setOpen(true)
|
id={groupType.value}
|
||||||
}}>
|
onChange={() => toggleVisibleGroupType(groupType.value)}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2.3} stroke="currentColor" className="size-6">
|
type='checkbox'
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
|
className='tw-checkbox tw-checkbox-xs tw-checkbox-success'
|
||||||
</svg>
|
checked={isGroupTypeVisible(groupType.value)}
|
||||||
</div>
|
/>
|
||||||
</div>
|
<span className='tw-text-sm tw-label-text tw-mx-2 tw-cursor-pointer'>
|
||||||
|
{groupType.text}
|
||||||
}
|
</span>
|
||||||
|
</label>
|
||||||
</div >
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
<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)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
fill='none'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
strokeWidth={2.3}
|
||||||
|
stroke='currentColor'
|
||||||
|
className='size-6'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
d='M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,21 +7,31 @@ export const GratitudeControl = () => {
|
|||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
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'>
|
||||||
{
|
{
|
||||||
|
<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={() => {
|
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'
|
||||||
navigate('/select-user')
|
onClick={() => {
|
||||||
}}>
|
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">
|
}}
|
||||||
<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
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
</div>
|
fill='none'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
}
|
strokeWidth={2.5}
|
||||||
|
stroke='currentColor'
|
||||||
</div>
|
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'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
} else return (<></>)
|
} else return <></>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ 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()
|
||||||
@ -11,34 +11,65 @@ export function LayerControl () {
|
|||||||
const toggleVisibleLayer = useToggleVisibleLayer()
|
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
|
||||||
<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={() => {
|
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'
|
||||||
setOpen(false)
|
onClick={() => {
|
||||||
}}>
|
setOpen(false)
|
||||||
<p className='tw-text-center '>✕</p></label>
|
}}
|
||||||
<ul className='tw-flex-row'>
|
>
|
||||||
{
|
<p className='tw-text-center '>✕</p>
|
||||||
layers.map(layer =>
|
</label>
|
||||||
(layer.listed && <li key={layer.name}><label htmlFor={layer.name} className="tw-label tw-justify-normal tw-pt-1 tw-pb-1"><input id={layer.name} onChange={() => toggleVisibleLayer(layer)} type="checkbox" className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={isLayerVisible(layer)} /><span className='tw-text-sm tw-label-text tw-mx-2 tw-cursor-pointer'>{layer.name}</span></label></li>)
|
<ul className='tw-flex-row'>
|
||||||
)
|
{layers.map(
|
||||||
}
|
(layer) =>
|
||||||
</ul>
|
layer.listed && (
|
||||||
</div>
|
<li key={layer.name}>
|
||||||
: <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={() => {
|
<label
|
||||||
setOpen(true)
|
htmlFor={layer.name}
|
||||||
}}>
|
className='tw-label tw-justify-normal tw-pt-1 tw-pb-1'
|
||||||
<svg version="1.1" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
>
|
||||||
<path id="svg_1" fill="currentColor" d="m2.75565,11.90727l-1.03852,0.28372c-0.77718,0.38859 -0.77718,1.0138 0,1.4023l7.0156,3.5078c0.77718,0.38859 2.0275,0.38859 2.8047,0l7.0156,-3.5078c0.77718,-0.38859 0.77718,-1.0138 0,-1.4023l-0.63311,-0.48643l-4.67718,2.23624c-1.5452,0.77262 -3.31877,1.58343 -4.86407,0.81081l-5.62302,-2.84434z" />
|
<input
|
||||||
<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" />
|
id={layer.name}
|
||||||
</svg>
|
onChange={() => toggleVisibleLayer(layer)}
|
||||||
|
type='checkbox'
|
||||||
</div>
|
className='tw-checkbox tw-checkbox-xs tw-checkbox-success'
|
||||||
|
checked={isLayerVisible(layer)}
|
||||||
}
|
/>
|
||||||
|
<span className='tw-text-sm tw-label-text tw-mx-2 tw-cursor-pointer'>
|
||||||
|
{layer.name}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</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={() => {
|
||||||
|
setOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg version='1.1' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path
|
||||||
|
id='svg_1'
|
||||||
|
fill='currentColor'
|
||||||
|
d='m2.75565,11.90727l-1.03852,0.28372c-0.77718,0.38859 -0.77718,1.0138 0,1.4023l7.0156,3.5078c0.77718,0.38859 2.0275,0.38859 2.8047,0l7.0156,-3.5078c0.77718,-0.38859 0.77718,-1.0138 0,-1.4023l-0.63311,-0.48643l-4.67718,2.23624c-1.5452,0.77262 -3.31877,1.58343 -4.86407,0.81081l-5.62302,-2.84434z'
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,26 +29,39 @@ export const LocateControl = () => {
|
|||||||
locationfound: () => {
|
locationfound: () => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setActive(true)
|
setActive(true)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
<div className="tw-card tw-h-12 tw-w-12 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-ml-2">
|
<>
|
||||||
|
<div className='tw-card tw-h-12 tw-w-12 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-ml-2'>
|
||||||
<div className="tw-card-body tw-card tw-p-2 tw-h-10 tw-w-10 " onClick={() => {
|
<div
|
||||||
if (active) {
|
className='tw-card-body tw-card tw-p-2 tw-h-10 tw-w-10 '
|
||||||
lc.stop()
|
onClick={() => {
|
||||||
setActive(false)
|
if (active) {
|
||||||
} else {
|
lc.stop()
|
||||||
lc.start()
|
setActive(false)
|
||||||
setLoading(true)
|
} else {
|
||||||
}
|
lc.start()
|
||||||
}}>{loading
|
setLoading(true)
|
||||||
? <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>
|
>
|
||||||
</svg>}
|
{loading ? (
|
||||||
|
<span className='tw-loading tw-loading-spinner tw-loading-md tw-mt-1'></span>
|
||||||
</div>
|
) : (
|
||||||
</div></>)
|
<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>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,34 @@
|
|||||||
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
|
||||||
<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)}>
|
className='tw-card tw-bg-base-100 tw-shadow-xl tw-my-2 tw-w-10'
|
||||||
<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>
|
onClick={(e) => e.stopPropagation()}
|
||||||
</div>
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,32 +40,40 @@ export const SearchControl = () => {
|
|||||||
},
|
},
|
||||||
popupclose: () => {
|
popupclose: () => {
|
||||||
setPopupOpen(false)
|
setPopupOpen(false)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
useDebounce(() => {
|
useDebounce(
|
||||||
const searchGeo = async () => {
|
() => {
|
||||||
try {
|
const searchGeo = async () => {
|
||||||
const { data } = await axios.get(
|
try {
|
||||||
`https://photon.komoot.io/api/?q=${value}&limit=5`
|
const { data } = await axios.get(`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(
|
||||||
setItemsResults(items.filter(item => {
|
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 &&
|
||||||
let phrase = value
|
((item.layer?.listed && item.name?.toLowerCase().includes(value.toLowerCase())) ||
|
||||||
if (value.startsWith('#')) phrase = value.substring(1)
|
item.text?.toLowerCase().includes(value.toLowerCase()))
|
||||||
setTagsResults(tags.filter(tag => tag.name?.toLowerCase().includes(phrase.toLowerCase())))
|
)
|
||||||
}, 500, [value])
|
}),
|
||||||
|
)
|
||||||
|
let phrase = value
|
||||||
|
if (value.startsWith('#')) phrase = value.substring(1)
|
||||||
|
setTagsResults(tags.filter((tag) => tag.name?.toLowerCase().includes(phrase.toLowerCase())))
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
[value],
|
||||||
|
)
|
||||||
|
|
||||||
const hide = async () => {
|
const hide = async () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -83,107 +91,236 @@ export const SearchControl = () => {
|
|||||||
embedded !== 'true' && setEmbedded(false)
|
embedded !== 'true' && setEmbedded(false)
|
||||||
}, [location])
|
}, [location])
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
{!(windowDimensions.height < 500 && popupOpen && hideSuggestions) &&
|
<>
|
||||||
<div className='tw-w-[calc(100vw-2rem)] tw-max-w-[22rem] '>
|
{!(windowDimensions.height < 500 && popupOpen && hideSuggestions) && (
|
||||||
<div className='tw-flex tw-flex-row'>
|
<div className='tw-w-[calc(100vw-2rem)] tw-max-w-[22rem] '>
|
||||||
{embedded && <SidebarControl />}
|
<div className='tw-flex tw-flex-row'>
|
||||||
<div className='tw-relative'>
|
{embedded && <SidebarControl />}
|
||||||
<input type="text" placeholder="search ..." autoComplete="off" value={value} className="tw-input tw-input-bordered tw-grow tw-shadow-xl tw-rounded-lg tw-pr-12"
|
<div className='tw-relative'>
|
||||||
ref={searchInput}
|
<input
|
||||||
onChange={(e) => setValue(e.target.value)}
|
type='text'
|
||||||
onFocus={() => {
|
placeholder='search ...'
|
||||||
setHideSuggestions(false)
|
autoComplete='off'
|
||||||
if (windowDimensions.width < 500) map.closePopup()
|
value={value}
|
||||||
}}
|
className='tw-input tw-input-bordered tw-grow tw-shadow-xl tw-rounded-lg tw-pr-12'
|
||||||
onBlur={() => hide()} />
|
ref={searchInput}
|
||||||
{value.length > 0 && <button className="tw-btn tw-btn-sm tw-btn-circle tw-absolute tw-right-2 tw-top-2" onClick={() => setValue('')}>✕</button>}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
</div>
|
onFocus={() => {
|
||||||
<LocateControl />
|
setHideSuggestions(false)
|
||||||
</div>
|
if (windowDimensions.width < 500) map.closePopup()
|
||||||
{hideSuggestions || (Array.from(geoResults).length === 0 && itemsResults.length === 0 && tagsResults.length === 0 && !isGeoCoordinate(value)) || value.length === 0
|
}}
|
||||||
? ''
|
onBlur={() => hide()}
|
||||||
: <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 &&
|
{value.length > 0 && (
|
||||||
<div className='tw-flex tw-flex-wrap'>
|
<button
|
||||||
{tagsResults.slice(0, 3).map(tag => (
|
className='tw-btn tw-btn-sm tw-btn-circle tw-absolute tw-right-2 tw-top-2'
|
||||||
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-1 tw-px-4 tw-shadow-md tw-card tw-mr-2 tw-mb-2 tw-cursor-pointer' style={{ backgroundColor: tag.color }} onClick={() => {
|
onClick={() => setValue('')}
|
||||||
addFilterTag(tag)
|
>
|
||||||
}}>
|
✕
|
||||||
<b>#{decodeTag(tag.name)}</b>
|
</button>
|
||||||
</div>
|
)}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{itemsResults.length > 0 && tagsResults.length > 0 && <hr className='tw-opacity-50'></hr>}
|
|
||||||
{itemsResults.slice(0, 5).map(item => (
|
|
||||||
<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
|
|
||||||
if (marker) {
|
|
||||||
navigate(`/${item.id}?${new URLSearchParams(window.location.search)}`)
|
|
||||||
} else {
|
|
||||||
navigate('item/' + item.id + '?' + new URLSearchParams(window.location.search))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}><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" />
|
|
||||||
<div>
|
|
||||||
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{item.name}</div>
|
|
||||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{item.text}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{Array.from(geoResults).length > 0 && (itemsResults.length > 0 || tagsResults.length > 0) && <hr className='tw-opacity-50'></hr>}
|
|
||||||
{Array.from(geoResults).map((geo) => (
|
|
||||||
<div className='tw-flex tw-flex-row hover:tw-font-bold tw-cursor-pointer' key={Math.random()} onClick={() => {
|
|
||||||
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()) })
|
|
||||||
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 })
|
|
||||||
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">
|
|
||||||
<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" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
))}
|
|
||||||
{isGeoCoordinate(value) &&
|
|
||||||
<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()) })
|
|
||||||
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">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 3v1.5M3 21v-6m0 0 2.77-.693a9 9 0 0 1 6.208.682l.108.054a9 9 0 0 0 6.086.71l3.114-.732a48.524 48.524 0 0 1-.005-10.499l-3.11.732a9 9 0 0 1-6.085-.711l-.108-.054a9 9 0 0 0-6.208-.682L3 4.5M3 15V4.5" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<LocateControl />
|
||||||
</>
|
</div>
|
||||||
|
{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'>
|
||||||
|
{tagsResults.length > 0 && (
|
||||||
|
<div className='tw-flex tw-flex-wrap'>
|
||||||
|
{tagsResults.slice(0, 3).map((tag) => (
|
||||||
|
<div
|
||||||
|
key={tag.name}
|
||||||
|
className='tw-rounded-2xl tw-text-white tw-p-1 tw-px-4 tw-shadow-md tw-card tw-mr-2 tw-mb-2 tw-cursor-pointer'
|
||||||
|
style={{ backgroundColor: tag.color }}
|
||||||
|
onClick={() => {
|
||||||
|
addFilterTag(tag)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<b>#{decodeTag(tag.name)}</b>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{itemsResults.length > 0 && tagsResults.length > 0 && (
|
||||||
|
<hr className='tw-opacity-50'></hr>
|
||||||
|
)}
|
||||||
|
{itemsResults.slice(0, 5).map((item) => (
|
||||||
|
<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
|
||||||
|
if (marker) {
|
||||||
|
navigate(`/${item.id}?${new URLSearchParams(window.location.search)}`)
|
||||||
|
} else {
|
||||||
|
navigate(
|
||||||
|
'item/' + item.id + '?' + new URLSearchParams(window.location.search),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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'
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||||
|
{item.text}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{Array.from(geoResults).length > 0 &&
|
||||||
|
(itemsResults.length > 0 || tagsResults.length > 0) && (
|
||||||
|
<hr className='tw-opacity-50'></hr>
|
||||||
|
)}
|
||||||
|
{Array.from(geoResults).map((geo) => (
|
||||||
|
<div
|
||||||
|
className='tw-flex tw-flex-row hover:tw-font-bold tw-cursor-pointer'
|
||||||
|
key={Math.random()}
|
||||||
|
onClick={() => {
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
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 },
|
||||||
|
)
|
||||||
|
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'
|
||||||
|
>
|
||||||
|
<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'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{isGeoCoordinate(value) && (
|
||||||
|
<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())
|
||||||
|
})
|
||||||
|
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'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
d='M3 3v1.5M3 21v-6m0 0 2.77-.693a9 9 0 0 1 6.208.682l.108.054a9 9 0 0 0 6.086.71l3.114-.732a48.524 48.524 0 0 1-.005-10.499l-3.11.732a9 9 0 0 1-6.085-.711l-.108-.054a9 9 0 0 0-6.208-.682L3 4.5M3 15V4.5'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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])
|
||||||
@ -195,6 +332,6 @@ function extractCoordinates (input): number[] | null {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,32 @@
|
|||||||
// 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 '>
|
||||||
<div className="tw-card-body tw-card tw-p-0">
|
<div className='tw-card-body tw-card tw-p-0'>
|
||||||
<button className="tw-btn tw-btn-square tw-btn-ghost tw-rounded-2xl"
|
<button
|
||||||
data-te-sidenav-toggle-ref
|
className='tw-btn tw-btn-square tw-btn-ghost tw-rounded-2xl'
|
||||||
data-te-target="#sidenav"
|
data-te-sidenav-toggle-ref
|
||||||
aria-controls="#sidenav"
|
data-te-target='#sidenav'
|
||||||
aria-haspopup="true">
|
aria-controls='#sidenav'
|
||||||
<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>
|
aria-haspopup='true'
|
||||||
</button>
|
>
|
||||||
|
<svg
|
||||||
</div>
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
</div></>)
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,14 +8,23 @@ export const TagsControl = () => {
|
|||||||
|
|
||||||
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'>
|
||||||
{
|
{filterTags.map((tag) => (
|
||||||
filterTags.map(tag =>
|
<div
|
||||||
<div key={tag.id} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mr-2 tw-mb-2' style={{ backgroundColor: tag.color }}>
|
key={tag.id}
|
||||||
<div className="tw-card-actions tw-justify-end">
|
className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mr-2 tw-mb-2'
|
||||||
<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={() => (removeFilterTag(tag.name!))}>✕</label>
|
style={{ backgroundColor: tag.color }}
|
||||||
</div><b>#{decodeTag(tag.name)}</b>
|
>
|
||||||
</div>
|
<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'
|
||||||
</div>)
|
onClick={() => removeFilterTag(tag.name!)}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<b>#{decodeTag(tag.name)}</b>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,14 +14,14 @@ import { useAddTag, useTags } from '../hooks/useTags'
|
|||||||
import { useAuth } from '../../Auth'
|
import { useAuth } from '../../Auth'
|
||||||
|
|
||||||
export interface ItemFormPopupProps {
|
export interface ItemFormPopupProps {
|
||||||
position: LatLng,
|
position: LatLng
|
||||||
layer: LayerProps,
|
layer: LayerProps
|
||||||
item?: Item,
|
item?: Item
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode
|
||||||
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
|
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -55,12 +55,16 @@ export function ItemFormPopup (props: ItemFormPopupProps) {
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
setSpinner(true)
|
setSpinner(true)
|
||||||
|
|
||||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
formItem.text &&
|
||||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
formItem.text
|
||||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
.toLocaleLowerCase()
|
||||||
}
|
.match(hashTagRegex)
|
||||||
return null
|
?.map((tag) => {
|
||||||
})
|
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
|
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
if (props.item) {
|
if (props.item) {
|
||||||
let success = false
|
let success = false
|
||||||
@ -79,27 +83,46 @@ export function ItemFormPopup (props: ItemFormPopupProps) {
|
|||||||
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 &&
|
||||||
(!props.layer.onlyOnePerOwner || !item) && await props.layer.api?.createItem!({ ...formItem, id: uuid, name: formItem.name ? formItem.name : user?.first_name })
|
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,
|
||||||
|
}))
|
||||||
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 })
|
;(!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 = () => {
|
||||||
@ -113,39 +136,74 @@ export function ItemFormPopup (props: ItemFormPopupProps) {
|
|||||||
}, [props.position])
|
}, [props.position])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LeafletPopup minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}
|
<LeafletPopup
|
||||||
eventHandlers={{
|
minWidth={275}
|
||||||
remove: () => {
|
maxWidth={275}
|
||||||
setTimeout(function () {
|
autoPanPadding={[20, 80]}
|
||||||
resetPopup()
|
eventHandlers={{
|
||||||
}, 100)
|
remove: () => {
|
||||||
}
|
setTimeout(function () {
|
||||||
}}
|
resetPopup()
|
||||||
position={props.position}>
|
}, 100)
|
||||||
<form ref={formRef} onReset={resetPopup} autoComplete='off' onSubmit={e => handleSubmit(e)}>
|
},
|
||||||
{props.item
|
}}
|
||||||
? <div className='tw-h-3'></div>
|
position={props.position}
|
||||||
: <div className='tw-flex tw-justify-center'><b className="tw-text-xl tw-text-center tw-font-bold">{ props.layer.menuText}</b></div>
|
>
|
||||||
}
|
<form ref={formRef} onReset={resetPopup} autoComplete='off' onSubmit={(e) => handleSubmit(e)}>
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
|
||||||
{props.children
|
{props.children ? (
|
||||||
|
React.Children.toArray(props.children).map((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,
|
||||||
|
})
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<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'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
? React.Children.toArray(props.children).map((child) =>
|
<div className='tw-flex tw-justify-center'>
|
||||||
React.isValidElement<{ item: Item, test: string, setPopupTitle: React.Dispatch<React.SetStateAction<string>> }>(child)
|
<button
|
||||||
? React.cloneElement(child, { item: props.item, key: props.position.toString(), setPopupTitle })
|
className={
|
||||||
: ''
|
spinner
|
||||||
)
|
? 'tw-btn tw-btn-disabled tw-mt-5 tw-place-self-center'
|
||||||
|
: 'tw-btn tw-mt-5 tw-place-self-center'
|
||||||
: <>
|
}
|
||||||
<TextInput type="text" placeholder="Name" dataField="name" defaultValue={props.item ? props.item.name : ''} inputStyle='' />
|
type='submit'
|
||||||
<TextAreaInput key={props.position.toString()} placeholder="Text" dataField="text" defaultValue={props.item ? props.item.text : ''} inputStyle='tw-h-40 tw-mt-5' />
|
>
|
||||||
</>
|
{spinner ? <span className='tw-loading tw-loading-spinner'></span> : 'Save'}
|
||||||
}
|
</button>
|
||||||
|
</div>
|
||||||
<div className='tw-flex tw-justify-center'>
|
</form>
|
||||||
<button className={spinner ? 'tw-btn tw-btn-disabled tw-mt-5 tw-place-self-center' : 'tw-btn tw-mt-5 tw-place-self-center'} type='submit'>{spinner ? <span className="tw-loading tw-loading-spinner"></span> : 'Save'}</button>
|
</LeafletPopup>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</LeafletPopup>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,20 +6,35 @@ 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: Item,
|
item,
|
||||||
api?: ItemsApi<any>,
|
api,
|
||||||
editCallback?: any,
|
editCallback,
|
||||||
deleteCallback?: any,
|
deleteCallback,
|
||||||
setPositionCallback?: any,
|
setPositionCallback,
|
||||||
itemNameField?: string,
|
itemNameField,
|
||||||
itemAvatarField?: string,
|
itemSubnameField,
|
||||||
itemSubnameField?: string,
|
itemAvatarField,
|
||||||
loading?: boolean,
|
loading,
|
||||||
hideMenu?: boolean,
|
hideMenu = false,
|
||||||
big?: boolean,
|
big = false,
|
||||||
hideSubname?: boolean,
|
truncateSubname = true,
|
||||||
truncateSubname?: boolean,
|
hideSubname = false,
|
||||||
|
showAddress = false,
|
||||||
|
}: {
|
||||||
|
item: Item
|
||||||
|
api?: ItemsApi<any>
|
||||||
|
editCallback?: any
|
||||||
|
deleteCallback?: any
|
||||||
|
setPositionCallback?: any
|
||||||
|
itemNameField?: string
|
||||||
|
itemAvatarField?: string
|
||||||
|
itemSubnameField?: string
|
||||||
|
loading?: boolean
|
||||||
|
hideMenu?: boolean
|
||||||
|
big?: boolean
|
||||||
|
hideSubname?: boolean
|
||||||
|
truncateSubname?: boolean
|
||||||
showAddress?: boolean
|
showAddress?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [modalOpen, setModalOpen] = React.useState<boolean>(false)
|
const [modalOpen, setModalOpen] = React.useState<boolean>(false)
|
||||||
@ -28,9 +43,23 @@ export function HeaderView ({ item, api, editCallback, deleteCallback, setPositi
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const assetsApi = useAssetApi()
|
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 avatar =
|
||||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField)
|
itemAvatarField && getValue(item, itemAvatarField)
|
||||||
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField)
|
? 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] = React.useState<string>('')
|
const [address] = React.useState<string>('')
|
||||||
|
|
||||||
@ -45,10 +74,12 @@ export function HeaderView ({ item, api, editCallback, deleteCallback, setPositi
|
|||||||
<>
|
<>
|
||||||
<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}
|
||||||
@ -58,64 +89,146 @@ export function HeaderView ({ item, api, editCallback, deleteCallback, setPositi
|
|||||||
</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 && (
|
||||||
{address}
|
<div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
|
||||||
</div>}
|
{address}
|
||||||
{subtitle && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
|
</div>
|
||||||
{subtitle}
|
)}
|
||||||
</div>}
|
{subtitle && !hideSubname && (
|
||||||
|
<div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
|
||||||
|
{subtitle}
|
||||||
|
</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) ||
|
||||||
!hideMenu &&
|
hasUserPermission(api?.collectionName!, 'update', item)) &&
|
||||||
<div className="tw-dropdown tw-dropdown-bottom">
|
!hideMenu && (
|
||||||
<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">
|
<div className='tw-dropdown tw-dropdown-bottom'>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
<label
|
||||||
<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" />
|
tabIndex={0}
|
||||||
</svg>
|
className='tw-bg-base-100 tw-btn tw-m-1 tw-leading-3 tw-border-none tw-min-h-0 tw-h-6'
|
||||||
</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">
|
<svg
|
||||||
{((api?.updateItem && hasUserPermission(api.collectionName!, 'update', item))) && editCallback && <li>
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
<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)}>
|
className='tw-h-5 tw-w-5'
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
viewBox='0 0 20 20'
|
||||||
<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" />
|
fill='currentColor'
|
||||||
</svg>
|
>
|
||||||
</a>
|
<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' />
|
||||||
</li>}
|
</svg>
|
||||||
{((api?.updateItem && hasUserPermission(api.collectionName!, 'update', item))) && setPositionCallback && <li>
|
</label>
|
||||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={setPositionCallback}>
|
<ul
|
||||||
<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">
|
tabIndex={0}
|
||||||
<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>
|
className='tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000'
|
||||||
</svg>
|
>
|
||||||
</a>
|
{api?.updateItem &&
|
||||||
</li>}
|
hasUserPermission(api.collectionName!, 'update', item) &&
|
||||||
{api?.deleteItem && hasUserPermission(api.collectionName!, 'delete', item) && deleteCallback && <li>
|
editCallback && (
|
||||||
<a className='tw-cursor-pointer !tw-text-error' onClick={openDeleteModal}>
|
<li>
|
||||||
{loading
|
<a
|
||||||
? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
|
className='!tw-text-base-content tw-cursor-pointer'
|
||||||
: <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
onClick={(e) =>
|
||||||
<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" />
|
item.layer?.customEditLink
|
||||||
</svg>}
|
? navigate(
|
||||||
</a>
|
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && '?' + params}` : ''} `,
|
||||||
</li>}
|
)
|
||||||
</ul>
|
: editCallback(e)
|
||||||
</div>}
|
}
|
||||||
|
>
|
||||||
|
<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' />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{api?.updateItem &&
|
||||||
|
hasUserPermission(api.collectionName!, 'update', item) &&
|
||||||
|
setPositionCallback && (
|
||||||
|
<li>
|
||||||
|
<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'
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{api?.deleteItem &&
|
||||||
|
hasUserPermission(api.collectionName!, 'delete', item) &&
|
||||||
|
deleteCallback && (
|
||||||
|
<li>
|
||||||
|
<a className='tw-cursor-pointer !tw-text-error' onClick={openDeleteModal}>
|
||||||
|
{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'
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<DialogModal isOpened={modalOpen} title="Are you sure?" showCloseButton={false} onClose={() => (setModalOpen(false))} >
|
<DialogModal
|
||||||
<div onClick={(e) => e.stopPropagation()} >
|
isOpened={modalOpen}
|
||||||
<span>Do you want to delete <b>{item.name}</b>?</span>
|
title='Are you sure?'
|
||||||
<div className="tw-grid">
|
showCloseButton={false}
|
||||||
<div className="tw-flex tw-justify-between" >
|
onClose={() => setModalOpen(false)}
|
||||||
<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>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
|
<span>
|
||||||
|
Do you want to delete <b>{item.name}</b>?
|
||||||
|
</span>
|
||||||
|
<div className='tw-grid'>
|
||||||
|
<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' onClick={() => setModalOpen(false)}>
|
||||||
|
No
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,12 +3,32 @@ 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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,24 @@
|
|||||||
import { Item } from '../../../../types'
|
import { Item } from '../../../../types'
|
||||||
|
|
||||||
export const PopupCheckboxInput = ({ dataField, label, item }:
|
export const PopupCheckboxInput = ({
|
||||||
{
|
dataField,
|
||||||
dataField: string,
|
label,
|
||||||
label: string,
|
item,
|
||||||
item?: Item
|
}: {
|
||||||
}) => {
|
dataField: string
|
||||||
|
label: string
|
||||||
|
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,19 +3,42 @@ import { TextInput } from '../../../Input'
|
|||||||
import { Item } from '../../../../types'
|
import { Item } from '../../../../types'
|
||||||
|
|
||||||
type StartEndInputProps = {
|
type StartEndInputProps = {
|
||||||
item?:Item,
|
item?: Item
|
||||||
showLabels?: boolean
|
showLabels?: boolean
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
updateStartValue?: (value: string) => void;
|
updateStartValue?: (value: string) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
updateEndValue?: (value: string) => void;
|
updateEndValue?: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
<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>
|
type='date'
|
||||||
</div>
|
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>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,23 @@ import * as React from 'react'
|
|||||||
import { TextAreaInput } from '../../../Input'
|
import { TextAreaInput } from '../../../Input'
|
||||||
import { Item } from '../../../../types'
|
import { Item } from '../../../../types'
|
||||||
|
|
||||||
export const PopupTextAreaInput = ({ dataField, placeholder, style, item }:
|
export const PopupTextAreaInput = ({
|
||||||
{
|
dataField,
|
||||||
dataField: string,
|
placeholder,
|
||||||
placeholder: string,
|
style,
|
||||||
style?: string,
|
item,
|
||||||
item?: Item
|
}: {
|
||||||
}) => {
|
dataField: string
|
||||||
|
placeholder: string
|
||||||
|
style?: string
|
||||||
|
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,25 @@ import * as React from 'react'
|
|||||||
import { TextInput } from '../../../Input'
|
import { TextInput } from '../../../Input'
|
||||||
import { Item } from '../../../../types'
|
import { Item } from '../../../../types'
|
||||||
|
|
||||||
export const PopupTextInput = ({ dataField, placeholder, style, item }:
|
export const PopupTextInput = ({
|
||||||
{
|
dataField,
|
||||||
dataField: string,
|
placeholder,
|
||||||
placeholder: string,
|
style,
|
||||||
style?: string,
|
item,
|
||||||
item?: Item
|
}: {
|
||||||
}) => {
|
dataField: string
|
||||||
|
placeholder: string
|
||||||
|
style?: string
|
||||||
|
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,56 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Item } from '../../../../types'
|
import { Item } from '../../../../types'
|
||||||
|
|
||||||
export const StartEndView = ({ item } : {item?:Item}) => {
|
export const StartEndView = ({ item }: { item?: Item }) => {
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-flex-row tw-mb-4 tw-mt-1">
|
<div className='tw-flex tw-flex-row tw-mb-4 tw-mt-1'>
|
||||||
<div className="tw-basis-2/5 tw-flex tw-flex-row">
|
<div className='tw-basis-2/5 tw-flex tw-flex-row'>
|
||||||
<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
|
||||||
<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" />
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
</svg>
|
className='tw-h-4 tw-w-4 tw-mr-2'
|
||||||
<time className='tw-align-middle' dateTime={item && item.start ? item.start.substring(0, 10) : ''}>{item && item.start ? new Date(item.start).toLocaleDateString() : ''}</time>
|
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'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<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 className='tw-basis-1/5 tw-place-content-center'>
|
||||||
|
<span>-</span>
|
||||||
|
</div>
|
||||||
|
<div className='tw-basis-2/5 tw-flex tw-flex-row'>
|
||||||
|
<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'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<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 className="tw-basis-1/5 tw-place-content-center">
|
|
||||||
<span>-</span>
|
|
||||||
</div>
|
|
||||||
<div className="tw-basis-2/5 tw-flex tw-flex-row">
|
|
||||||
<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" />
|
|
||||||
</svg>
|
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,14 +9,30 @@ 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) { text = replacedText = rawText } else if (itemTextField && item) { text = getValue(item, itemTextField) } else { text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : '' }
|
if (rawText) {
|
||||||
|
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)
|
||||||
|
|
||||||
@ -27,11 +43,11 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
|||||||
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
|
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
|
||||||
let shortUrl = 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})`
|
||||||
@ -51,60 +67,39 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 className='tw-max-w-full tw-rounded tw-shadow' src={src} alt={alt} title={title} />
|
||||||
className="tw-max-w-full tw-rounded tw-shadow"
|
|
||||||
src={src}
|
|
||||||
alt={alt}
|
|
||||||
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} target='_blank' rel='noreferrer'>
|
||||||
href={href}
|
{' '}
|
||||||
target='_blank' rel="noreferrer"
|
{children}
|
||||||
> {children}</a>
|
</a>
|
||||||
)
|
)
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
const CustomHashTagLink = ({ children, tag, item }) => {
|
const CustomHashTagLink = ({ children, tag, item }) => {
|
||||||
@ -115,7 +110,10 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
|||||||
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 */
|
||||||
@ -125,61 +123,65 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
|||||||
<iframe
|
<iframe
|
||||||
className='tw-w-full'
|
className='tw-w-full'
|
||||||
src={url}
|
src={url}
|
||||||
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
|
||||||
p: CustomParagraph,
|
className={'tw-text-map tw-leading-map tw-text-sm'}
|
||||||
a: ({ href, children }) => {
|
remarkPlugins={[remarkBreaks]}
|
||||||
// eslint-disable-next-line react/prop-types
|
components={{
|
||||||
const isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v=')
|
p: CustomParagraph,
|
||||||
// eslint-disable-next-line react/prop-types
|
a: ({ href, children }) => {
|
||||||
const isRumbleVideo = href?.startsWith('https://rumble.com/embed/')
|
|
||||||
|
|
||||||
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 isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v=')
|
||||||
const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const isRumbleVideo = href?.startsWith('https://rumble.com/embed/')
|
||||||
|
|
||||||
return (
|
if (isYouTubeVideo) {
|
||||||
<MemoizedVideoEmbed url={youtubeEmbedUrl}></MemoizedVideoEmbed>
|
// eslint-disable-next-line react/prop-types
|
||||||
)
|
const videoId = href?.split('v=')[1].split('&')[0]
|
||||||
}
|
const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`
|
||||||
if (isRumbleVideo) {
|
|
||||||
return (
|
return <MemoizedVideoEmbed url={youtubeEmbedUrl}></MemoizedVideoEmbed>
|
||||||
<MemoizedVideoEmbed url={href!}></MemoizedVideoEmbed>
|
}
|
||||||
)
|
if (isRumbleVideo) {
|
||||||
}
|
return <MemoizedVideoEmbed url={href!}></MemoizedVideoEmbed>
|
||||||
// eslint-disable-next-line react/prop-types
|
}
|
||||||
if (href?.startsWith('#')) {
|
// eslint-disable-next-line react/prop-types
|
||||||
const tag = tags.find(t => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase())
|
if (href?.startsWith('#')) {
|
||||||
return <CustomHashTagLink tag={tag} item={item}>{children}</CustomHashTagLink>
|
const tag = tags.find(
|
||||||
} else {
|
(t) => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase(),
|
||||||
return (
|
)
|
||||||
<CustomExternalLink href={href}>{children}</CustomExternalLink>
|
return (
|
||||||
)
|
<CustomHashTagLink tag={tag} item={item}>
|
||||||
}
|
{children}
|
||||||
},
|
</CustomHashTagLink>
|
||||||
ul: CustomUnorderdList,
|
)
|
||||||
ol: CustomOrderdList,
|
} else {
|
||||||
img: CustomImage,
|
return <CustomExternalLink href={href}>{children}</CustomExternalLink>
|
||||||
hr: CustomHorizontalRow,
|
}
|
||||||
h1: CustomH1,
|
},
|
||||||
h2: CustomH2,
|
ul: CustomUnorderdList,
|
||||||
h3: CustomH3,
|
ol: CustomOrderdList,
|
||||||
h4: CustomH4,
|
img: CustomImage,
|
||||||
h5: CustomH5,
|
hr: CustomHorizontalRow,
|
||||||
h6: CustomH6
|
h1: CustomH1,
|
||||||
}}>
|
h2: CustomH2,
|
||||||
|
h3: CustomH3,
|
||||||
|
h4: CustomH4,
|
||||||
|
h5: CustomH5,
|
||||||
|
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
|
||||||
return text
|
return text
|
||||||
.replace(/!\[.*?\]\(.*?\)/g, '') // Remove images
|
.replace(/!\[.*?\]\(.*?\)/g, '') // Remove images
|
||||||
@ -191,7 +193,7 @@ function removeMarkdownKeepLinksAndParagraphs (text) {
|
|||||||
.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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,8 +13,8 @@ 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>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,16 @@ export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: a
|
|||||||
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>) => {
|
||||||
@ -40,8 +49,10 @@ export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: a
|
|||||||
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 &&
|
||||||
props.item.layer?.onlyOnePerOwner && await props.item.layer.api?.updateItem!({ id: props.item.id, position: null })
|
(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 }))
|
||||||
success = true
|
success = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error.toString())
|
toast.error(error.toString())
|
||||||
@ -61,30 +72,45 @@ export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: a
|
|||||||
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 ? (
|
||||||
{
|
<p
|
||||||
infoExpanded
|
className={'tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'}
|
||||||
? <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>
|
>{`${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> */
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
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'>
|
||||||
<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
|
||||||
setSelectNewItemPosition(null)
|
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={() => {
|
||||||
<p className='tw-text-center '>✕</p></label>
|
setSelectNewItemPosition(null)
|
||||||
<div className="tw-alert tw-bg-base-100 tw-text-base-content">
|
}}
|
||||||
<div>
|
>
|
||||||
<span className="tw-text-lg">Select position on the map!</span>
|
<p className='tw-text-center '>✕</p>
|
||||||
</div>
|
</label>
|
||||||
</div>
|
<div className='tw-alert tw-bg-base-100 tw-text-base-content'>
|
||||||
|
<div>
|
||||||
|
<span className='tw-text-lg'>Select position on the map!</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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()
|
||||||
|
|
||||||
@ -26,10 +26,18 @@ export function Tags ({ data, api } : {data?: Tag[], api?: ItemsApi<Tag>}) {
|
|||||||
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
|
return null
|
||||||
})
|
})
|
||||||
@ -38,7 +46,5 @@ export function Tags ({ data, api } : {data?: Tag[], api?: ItemsApi<Tag>}) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [location, tags])
|
}, [location, tags])
|
||||||
|
|
||||||
return (
|
return <></>
|
||||||
<></>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,11 @@ 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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,11 @@ 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'
|
||||||
@ -26,7 +30,7 @@ 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],
|
||||||
@ -36,7 +40,7 @@ export function UtopiaMapInner ({
|
|||||||
showFilterControl = false,
|
showFilterControl = false,
|
||||||
showGratitudeControl = false,
|
showGratitudeControl = false,
|
||||||
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()
|
||||||
@ -50,22 +54,23 @@ export function UtopiaMapInner ({
|
|||||||
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 &&
|
||||||
toast(<TextView rawText={infoText}/>, { autoClose: false })
|
setTimeout(() => {
|
||||||
}, 4000)
|
toast(<TextView rawText={infoText} />, { autoClose: false })
|
||||||
|
}, 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()
|
||||||
@ -74,7 +79,7 @@ export function UtopiaMapInner ({
|
|||||||
setMapClicked({ position: e.latlng, setItemFormPopup })
|
setMapClicked({ position: e.latlng, setItemFormPopup })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
moveend: () => { }
|
moveend: () => {},
|
||||||
})
|
})
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -86,7 +91,12 @@ export function UtopiaMapInner ({
|
|||||||
}
|
}
|
||||||
document.title = document.title.split('-')[0]
|
document.title = document.title.split('-')[0]
|
||||||
document.querySelector('meta[property="og:title"]')?.setAttribute('content', document.title)
|
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
|
||||||
|
.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
|
||||||
@ -97,50 +107,69 @@ export function UtopiaMapInner ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}>
|
<div
|
||||||
<MapContainer ref={mapDivRef} style={{ height, width }} center={new LatLng(center[0], center[1])} zoom={zoom} zoomControl={false} maxZoom={19}>
|
className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}
|
||||||
<Outlet />
|
>
|
||||||
<Control position="topLeft" zIndex="1000" absolute>
|
<MapContainer
|
||||||
<SearchControl />
|
ref={mapDivRef}
|
||||||
<TagsControl />
|
style={{ height, width }}
|
||||||
</Control>
|
center={new LatLng(center[0], center[1])}
|
||||||
<Control position="bottomLeft" zIndex="999" absolute>
|
zoom={zoom}
|
||||||
{showFilterControl && <FilterControl />}
|
zoomControl={false}
|
||||||
{showLayerControl && <LayerControl />}
|
maxZoom={19}
|
||||||
{showGratitudeControl && <GratitudeControl />}
|
>
|
||||||
</Control>
|
<Outlet />
|
||||||
<TileLayer
|
<Control position='topLeft' zIndex='1000' absolute>
|
||||||
maxZoom={19}
|
<SearchControl />
|
||||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
<TagsControl />
|
||||||
url="https://tile.osmand.net/hd/{z}/{x}/{y}.png"
|
</Control>
|
||||||
/>
|
<Control position='bottomLeft' zIndex='999' absolute>
|
||||||
<MarkerClusterGroup ref={(r) => setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
|
{showFilterControl && <FilterControl />}
|
||||||
{
|
{showLayerControl && <LayerControl />}
|
||||||
React.Children.toArray(children).map((child) =>
|
{showGratitudeControl && <GratitudeControl />}
|
||||||
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child)
|
</Control>
|
||||||
? React.cloneElement(child, { setItemFormPopup, itemFormPopup, clusterRef })
|
<TileLayer
|
||||||
: child
|
maxZoom={19}
|
||||||
)
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
}
|
url='https://tile.osmand.net/hd/{z}/{x}/{y}.png'
|
||||||
</MarkerClusterGroup>
|
/>
|
||||||
{geo && (
|
<MarkerClusterGroup
|
||||||
<GeoJSON
|
ref={(r) => setClusterRef(r)}
|
||||||
data={geo}
|
showCoverageOnHover
|
||||||
onEachFeature={onEachFeature}
|
chunkedLoading
|
||||||
eventHandlers={{
|
maxClusterRadius={50}
|
||||||
click: (e) => {
|
removeOutsideVisibleBounds={false}
|
||||||
if (selectNewItemPosition) {
|
>
|
||||||
e.layer!.closePopup()
|
{React.Children.toArray(children).map((child) =>
|
||||||
setMapClicked({ position: e.latlng, setItemFormPopup })
|
React.isValidElement<{
|
||||||
}
|
setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>
|
||||||
}
|
itemFormPopup: ItemFormPopupProps | null
|
||||||
}}
|
clusterRef: React.MutableRefObject<undefined>
|
||||||
/>
|
}>(child)
|
||||||
)}
|
? React.cloneElement(child, { setItemFormPopup, itemFormPopup, clusterRef })
|
||||||
<MapEventListener />
|
: child,
|
||||||
</MapContainer>
|
)}
|
||||||
<AddButton triggerAction={setSelectNewItemPosition} />
|
</MarkerClusterGroup>
|
||||||
{selectNewItemPosition != null && <SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />}
|
{geo && (
|
||||||
</div>
|
<GeoJSON
|
||||||
|
data={geo}
|
||||||
|
onEachFeature={onEachFeature}
|
||||||
|
eventHandlers={{
|
||||||
|
click: (e) => {
|
||||||
|
if (selectNewItemPosition) {
|
||||||
|
e.layer!.closePopup()
|
||||||
|
setMapClicked({ position: e.latlng, setItemFormPopup })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MapEventListener />
|
||||||
|
</MapContainer>
|
||||||
|
<AddButton triggerAction={setSelectNewItemPosition} />
|
||||||
|
{selectNewItemPosition != null && (
|
||||||
|
<SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,28 @@
|
|||||||
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}</ClusterRefContext.Provider>
|
||||||
{children}
|
|
||||||
</ClusterRefContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useClusterRef = (): any => {
|
export const useClusterRef = (): any => {
|
||||||
|
|||||||
@ -16,66 +16,60 @@ type ActionType =
|
|||||||
| { 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: () => {},
|
||||||
removeFilterTag: () => { },
|
removeFilterTag: () => {},
|
||||||
resetFilterTags: () => { },
|
resetFilterTags: () => {},
|
||||||
setSearchPhrase: () => { },
|
setSearchPhrase: () => {},
|
||||||
addVisibleLayer: () => { },
|
addVisibleLayer: () => {},
|
||||||
toggleVisibleLayer: () => { },
|
toggleVisibleLayer: () => {},
|
||||||
resetVisibleLayers: () => { },
|
resetVisibleLayers: () => {},
|
||||||
isLayerVisible: () => true,
|
isLayerVisible: () => true,
|
||||||
|
|
||||||
addVisibleGroupType: () => { },
|
addVisibleGroupType: () => {},
|
||||||
toggleVisibleGroupType: () => { },
|
toggleVisibleGroupType: () => {},
|
||||||
isGroupTypeVisible: () => true
|
isGroupTypeVisible: () => true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function useFilterManager (initialTags: Tag[]): {
|
function useFilterManager(initialTags: Tag[]): {
|
||||||
filterTags: Tag[];
|
filterTags: Tag[]
|
||||||
searchPhrase: string;
|
searchPhrase: string
|
||||||
visibleLayers: LayerProps[];
|
visibleLayers: LayerProps[]
|
||||||
visibleGroupTypes: string[];
|
visibleGroupTypes: string[]
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
addFilterTag: (tag: Tag) => void;
|
addFilterTag: (tag: Tag) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
removeFilterTag: (name: string) => void;
|
removeFilterTag: (name: string) => void
|
||||||
resetFilterTags: () => void;
|
resetFilterTags: () => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
setSearchPhrase: (phrase: string) => void;
|
setSearchPhrase: (phrase: string) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
addVisibleLayer: (layer: LayerProps) => void;
|
addVisibleLayer: (layer: LayerProps) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
toggleVisibleLayer: (layer: LayerProps) => void;
|
toggleVisibleLayer: (layer: LayerProps) => void
|
||||||
resetVisibleLayers: () => void;
|
resetVisibleLayers: () => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
isLayerVisible: (layer: LayerProps) => boolean;
|
isLayerVisible: (layer: LayerProps) => boolean
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
addVisibleGroupType: (groupType: string) => void;
|
addVisibleGroupType: (groupType: string) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
toggleVisibleGroupType: (groupType: string) => void;
|
toggleVisibleGroupType: (groupType: string) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
isGroupTypeVisible: (groupType: string) => boolean;
|
isGroupTypeVisible: (groupType: string) => boolean
|
||||||
} {
|
} {
|
||||||
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)
|
||||||
tag.id === action.tag.id
|
|
||||||
)
|
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
return [
|
return [...state, action.tag]
|
||||||
...state,
|
|
||||||
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)
|
||||||
@ -93,18 +87,12 @@ function useFilterManager (initialTags: Tag[]): {
|
|||||||
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)
|
||||||
layer.name === action.layer.name
|
|
||||||
)
|
|
||||||
if (!exist1) {
|
if (!exist1) {
|
||||||
return [
|
return [...state, action.layer]
|
||||||
...state,
|
|
||||||
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':
|
||||||
@ -114,29 +102,26 @@ function useFilterManager (initialTags: Tag[]): {
|
|||||||
}
|
}
|
||||||
}, initialLayers)
|
}, initialLayers)
|
||||||
|
|
||||||
const [visibleGroupTypes, dispatchGroupTypes] = useReducer((state: string[], action: ActionType) => {
|
const [visibleGroupTypes, dispatchGroupTypes] = useReducer(
|
||||||
switch (action.type) {
|
(state: string[], action: ActionType) => {
|
||||||
case 'ADD_GROUP_TYPE':
|
switch (action.type) {
|
||||||
const exist1 = state.find((groupType) =>
|
case 'ADD_GROUP_TYPE':
|
||||||
groupType === action.groupType
|
const exist1 = state.find((groupType) => groupType === action.groupType)
|
||||||
)
|
if (!exist1) {
|
||||||
if (!exist1) {
|
return [...state, action.groupType]
|
||||||
return [
|
} else return state
|
||||||
...state,
|
case 'TOGGLE_GROUP_TYPE':
|
||||||
action.groupType
|
const exist2 = state.some((groupType) => groupType === action.groupType)
|
||||||
]
|
if (exist2) return state.filter((groupType) => groupType !== action.groupType)
|
||||||
} else return state
|
else return [...state, action.groupType]
|
||||||
case 'TOGGLE_GROUP_TYPE':
|
case 'RESET_GROUP_TYPE':
|
||||||
const exist2 = state.some((groupType) =>
|
return []
|
||||||
groupType === action.groupType)
|
default:
|
||||||
if (exist2) return state.filter((groupType) => groupType !== action.groupType)
|
throw new Error()
|
||||||
else return [...state, action.groupType]
|
}
|
||||||
case 'RESET_GROUP_TYPE':
|
},
|
||||||
return []
|
[],
|
||||||
default:
|
)
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const [searchPhrase, searchPhraseSet] = React.useState<string>('')
|
const [searchPhrase, searchPhraseSet] = React.useState<string>('')
|
||||||
|
|
||||||
@ -145,16 +130,19 @@ function useFilterManager (initialTags: Tag[]): {
|
|||||||
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 ? ';' : ''}${tag.name}`) }
|
if (!decodedTags?.includes(tag.name)) {
|
||||||
if (windowDimensions.width < 786 && location.pathname.split('/').length > 2) navigate('/' + `${params ? `?${params}` : ''}`)
|
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}` : ''}`)
|
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) => {
|
||||||
@ -163,8 +151,10 @@ function useFilterManager (initialTags: Tag[]): {
|
|||||||
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())) { newUrlTags = newUrlTags + `${newUrlTags === '' ? urlTag : `;${urlTag}`}` }
|
if (!(urlTag.toLocaleLowerCase() === name.toLocaleLowerCase())) {
|
||||||
|
newUrlTags = newUrlTags + `${newUrlTags === '' ? urlTag : `;${urlTag}`}`
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
if (newUrlTags !== '') {
|
if (newUrlTags !== '') {
|
||||||
@ -177,72 +167,93 @@ function useFilterManager (initialTags: Tag[]): {
|
|||||||
|
|
||||||
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(
|
||||||
return visibleLayers.some(l => l.name === layer.name)
|
(layer: LayerProps) => {
|
||||||
}, [visibleLayers])
|
return visibleLayers.some((l) => l.name === layer.name)
|
||||||
|
},
|
||||||
|
[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(
|
||||||
return visibleGroupTypes.some(gt => gt === groupType)
|
(groupType: string) => {
|
||||||
}, [visibleGroupTypes])
|
return visibleGroupTypes.some((gt) => gt === groupType)
|
||||||
|
},
|
||||||
|
[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<{
|
||||||
initialTags: Tag[], children?: React.ReactNode
|
initialTags: Tag[]
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialTags, children }) => (
|
}> = ({ initialTags, children }) => (
|
||||||
<FilterContext.Provider value={useFilterManager(initialTags)}>
|
<FilterContext.Provider value={useFilterManager(initialTags)}>{children}</FilterContext.Provider>
|
||||||
{children}
|
|
||||||
</FilterContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useFilterTags = (): Tag[] => {
|
export const useFilterTags = (): Tag[] => {
|
||||||
|
|||||||
@ -9,37 +9,35 @@ type ActionType =
|
|||||||
| { 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>({
|
||||||
items: [],
|
items: [],
|
||||||
addItem: () => { },
|
addItem: () => {},
|
||||||
updateItem: () => { },
|
updateItem: () => {},
|
||||||
removeItem: () => { },
|
removeItem: () => {},
|
||||||
resetItems: () => { },
|
resetItems: () => {},
|
||||||
setItemsApi: () => { },
|
setItemsApi: () => {},
|
||||||
setItemsData: () => { },
|
setItemsData: () => {},
|
||||||
allItemsLoaded: false
|
allItemsLoaded: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
function useItemsManager (initialItems: Item[]): {
|
function useItemsManager(initialItems: Item[]): {
|
||||||
items: Item[];
|
items: Item[]
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
addItem: (item: Item) => void;
|
addItem: (item: Item) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
updateItem: (item: Item) => void;
|
updateItem: (item: Item) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
removeItem: (item: Item) => void;
|
removeItem: (item: Item) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
resetItems: (layer: LayerProps) => void;
|
resetItems: (layer: LayerProps) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
setItemsApi: (layer: LayerProps) => void;
|
setItemsApi: (layer: LayerProps) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
setItemsData: (layer: LayerProps) => void;
|
setItemsData: (layer: LayerProps) => void
|
||||||
allItemsLoaded: boolean;
|
allItemsLoaded: boolean
|
||||||
|
|
||||||
} {
|
} {
|
||||||
const addLayer = useAddLayer()
|
const addLayer = useAddLayer()
|
||||||
|
|
||||||
@ -49,14 +47,9 @@ function useItemsManager (initialItems: Item[]): {
|
|||||||
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)
|
||||||
item.id === action.item.id
|
|
||||||
)
|
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
return [
|
return [...state, action.item]
|
||||||
...state,
|
|
||||||
action.item
|
|
||||||
]
|
|
||||||
} else return state
|
} else return state
|
||||||
case 'UPDATE':
|
case 'UPDATE':
|
||||||
return state.map((item) => {
|
return state.map((item) => {
|
||||||
@ -66,9 +59,9 @@ function useItemsManager (initialItems: 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()
|
||||||
}
|
}
|
||||||
@ -76,75 +69,80 @@ function useItemsManager (initialItems: Item[]): {
|
|||||||
|
|
||||||
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(),
|
pending: `loading ${layer.name} ...`,
|
||||||
{
|
success: `${layer.name} loaded`,
|
||||||
pending: `loading ${layer.name} ...`,
|
error: {
|
||||||
success: `${layer.name} loaded`,
|
render({ data }) {
|
||||||
error: {
|
return `${data}`
|
||||||
render ({ data }) {
|
},
|
||||||
return `${data}`
|
},
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (result) {
|
if (result) {
|
||||||
result.map(item => {
|
result.map((item) => {
|
||||||
dispatch({ type: 'ADD', item: { ...item, layer } })
|
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||||
return null
|
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 } })
|
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||||
return null
|
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<{
|
||||||
initialItems: Item[], children?: React.ReactNode
|
initialItems: Item[]
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialItems, children }) => (
|
}> = ({ initialItems, children }) => (
|
||||||
<ItemContext.Provider value={useItemsManager(initialItems)}>
|
<ItemContext.Provider value={useItemsManager(initialItems)}>{children}</ItemContext.Provider>
|
||||||
{children}
|
|
||||||
</ItemContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useItems = (): Item[] => {
|
export const useItems = (): Item[] => {
|
||||||
|
|||||||
@ -2,33 +2,27 @@ 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[]
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
addLayer: (layer: LayerProps) => void;
|
addLayer: (layer: LayerProps) => void
|
||||||
} {
|
} {
|
||||||
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)
|
||||||
layer.name === action.layer.name
|
|
||||||
)
|
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
return [
|
return [...state, action.layer]
|
||||||
...state,
|
|
||||||
action.layer
|
|
||||||
]
|
|
||||||
} else return state
|
} else return state
|
||||||
default:
|
default:
|
||||||
throw new Error()
|
throw new Error()
|
||||||
@ -38,7 +32,7 @@ function useLayerManager (initialLayers: LayerProps[]): {
|
|||||||
const addLayer = useCallback((layer: LayerProps) => {
|
const addLayer = useCallback((layer: LayerProps) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'ADD LAYER',
|
type: 'ADD LAYER',
|
||||||
layer
|
layer,
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -46,11 +40,10 @@ function useLayerManager (initialLayers: LayerProps[]): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LayersProvider: React.FunctionComponent<{
|
export const LayersProvider: React.FunctionComponent<{
|
||||||
initialLayers: LayerProps[], children?: React.ReactNode
|
initialLayers: LayerProps[]
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialLayers, children }) => (
|
}> = ({ initialLayers, children }) => (
|
||||||
<LayerContext.Provider value={useLayerManager(initialLayers)}>
|
<LayerContext.Provider value={useLayerManager(initialLayers)}>{children}</LayerContext.Provider>
|
||||||
{children}
|
|
||||||
</LayerContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useLayers = (): LayerProps[] => {
|
export const useLayers = (): LayerProps[] => {
|
||||||
|
|||||||
@ -4,53 +4,59 @@ import { Item } from '../../../types'
|
|||||||
import { Marker, Popup } from 'leaflet'
|
import { Marker, Popup } from 'leaflet'
|
||||||
|
|
||||||
type LeafletRef = {
|
type LeafletRef = {
|
||||||
item: Item,
|
item: Item
|
||||||
marker: Marker,
|
marker: Marker
|
||||||
popup: Popup
|
popup: Popup
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
addMarker: (item: Item, marker: Marker) => void;
|
addMarker: (item: Item, marker: Marker) => void
|
||||||
// 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(
|
||||||
switch (action.type) {
|
(state: Record<string, LeafletRef>, action: ActionType) => {
|
||||||
case 'ADD_MARKER':
|
switch (action.type) {
|
||||||
return {
|
case 'ADD_MARKER':
|
||||||
...state,
|
return {
|
||||||
[action.item.id]: { ...state[action.item.id], marker: action.marker, item: action.item }
|
...state,
|
||||||
}
|
[action.item.id]: {
|
||||||
case 'ADD_POPUP':
|
...state[action.item.id],
|
||||||
return {
|
marker: action.marker,
|
||||||
...state,
|
item: action.item,
|
||||||
[action.item.id]: { ...state[action.item.id], popup: action.popup, item: action.item }
|
},
|
||||||
}
|
}
|
||||||
default:
|
case 'ADD_POPUP':
|
||||||
throw new Error()
|
return {
|
||||||
}
|
...state,
|
||||||
}, initialLeafletRefs)
|
[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,
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -58,7 +64,7 @@ function useLeafletRefsManager (initialLeafletRefs: {}): {
|
|||||||
dispatch({
|
dispatch({
|
||||||
type: 'ADD_POPUP',
|
type: 'ADD_POPUP',
|
||||||
item,
|
item,
|
||||||
popup
|
popup,
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -66,7 +72,8 @@ function useLeafletRefsManager (initialLeafletRefs: {}): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LeafletRefsProvider: React.FunctionComponent<{
|
export const LeafletRefsProvider: React.FunctionComponent<{
|
||||||
initialLeafletRefs: {}, children?: React.ReactNode
|
initialLeafletRefs: {}
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialLeafletRefs, children }) => (
|
}> = ({ initialLeafletRefs, children }) => (
|
||||||
<LeafletRefsContext.Provider value={useLeafletRefsManager(initialLeafletRefs)}>
|
<LeafletRefsContext.Provider value={useLeafletRefsManager(initialLeafletRefs)}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -3,43 +3,41 @@ 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: 'REMOVE'; id: string }
|
||||||
| { type: 'ADD'; permission: Permission }
|
|
||||||
| { type: 'REMOVE'; id: string };
|
|
||||||
|
|
||||||
type UsePermissionManagerResult = ReturnType<typeof usePermissionsManager>;
|
type UsePermissionManagerResult = ReturnType<typeof usePermissionsManager>
|
||||||
|
|
||||||
const PermissionContext = createContext<UsePermissionManagerResult>({
|
const PermissionContext = createContext<UsePermissionManagerResult>({
|
||||||
permissions: [],
|
permissions: [],
|
||||||
setPermissionApi: () => { },
|
setPermissionApi: () => {},
|
||||||
setPermissionData: () => { },
|
setPermissionData: () => {},
|
||||||
setAdminRole: () => { },
|
setAdminRole: () => {},
|
||||||
hasUserPermission: () => true
|
hasUserPermission: () => true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function usePermissionsManager (initialPermissions: Permission[]): {
|
function usePermissionsManager(initialPermissions: Permission[]): {
|
||||||
permissions: Permission[];
|
permissions: Permission[]
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
setPermissionApi: (api: ItemsApi<any>) => void;
|
setPermissionApi: (api: ItemsApi<any>) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
setPermissionData: (data: Permission[]) => void;
|
setPermissionData: (data: Permission[]) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
setAdminRole: (adminRole: string) => void;
|
setAdminRole: (adminRole: string) => void
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
hasUserPermission: (collectionName: string, action: PermissionAction, item?: Item, layer?: LayerProps) => boolean;
|
hasUserPermission: (
|
||||||
|
collectionName: string,
|
||||||
|
action: PermissionAction,
|
||||||
|
item?: Item,
|
||||||
|
layer?: LayerProps,
|
||||||
|
) => boolean
|
||||||
} {
|
} {
|
||||||
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)
|
||||||
permission.id === action.permission.id
|
|
||||||
)
|
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
return [
|
return [...state, action.permission]
|
||||||
...state,
|
|
||||||
action.permission
|
|
||||||
]
|
|
||||||
} else return state
|
} else return state
|
||||||
|
|
||||||
case 'REMOVE':
|
case 'REMOVE':
|
||||||
@ -55,7 +53,7 @@ function usePermissionsManager (initialPermissions: Permission[]): {
|
|||||||
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
|
return null
|
||||||
})
|
})
|
||||||
@ -63,19 +61,14 @@ function usePermissionsManager (initialPermissions: Permission[]): {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
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
|
return null
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const hasUserPermission = useCallback(
|
const hasUserPermission = useCallback(
|
||||||
(
|
(collectionName: string, action: PermissionAction, item?: Item, layer?: LayerProps) => {
|
||||||
collectionName: string,
|
|
||||||
action: PermissionAction,
|
|
||||||
item?: Item,
|
|
||||||
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
|
||||||
@ -94,7 +87,7 @@ function usePermissionsManager (initialPermissions: Permission[]): {
|
|||||||
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
|
||||||
@ -102,31 +95,27 @@ function usePermissionsManager (initialPermissions: Permission[]): {
|
|||||||
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.action === action &&
|
(p) =>
|
||||||
p.collection === collectionName &&
|
p.action === action &&
|
||||||
(
|
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<{
|
||||||
initialPermissions: Permission[], children?: React.ReactNode
|
initialPermissions: Permission[]
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialPermissions, children }) => (
|
}> = ({ initialPermissions, children }) => (
|
||||||
<PermissionContext.Provider value={usePermissionsManager(initialPermissions)}>
|
<PermissionContext.Provider value={usePermissionsManager(initialPermissions)}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -8,25 +8,25 @@ import { LatLng } from 'leaflet'
|
|||||||
import { ItemFormPopupProps } from '../Subcomponents/ItemFormPopup'
|
import { ItemFormPopupProps } from '../Subcomponents/ItemFormPopup'
|
||||||
|
|
||||||
type PolygonClickedProps = {
|
type PolygonClickedProps = {
|
||||||
position: LatLng
|
position: LatLng
|
||||||
setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
|
setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>;
|
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>
|
||||||
|
|
||||||
const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
|
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
|
||||||
setSelectPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>;
|
setSelectPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>
|
||||||
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>()
|
||||||
@ -34,7 +34,12 @@ function useSelectPositionManager (): {
|
|||||||
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
|
||||||
|
) {
|
||||||
itemUpdateParent({ ...selectPosition, parent: markerClicked.id })
|
itemUpdateParent({ ...selectPosition, parent: markerClicked.id })
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -43,12 +48,18 @@ function useSelectPositionManager (): {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectPosition != null) {
|
if (selectPosition != null) {
|
||||||
if ('menuIcon' in selectPosition) {
|
if ('menuIcon' in selectPosition) {
|
||||||
mapClicked && mapClicked.setItemFormPopup({ layer: selectPosition as LayerProps, position: mapClicked?.position })
|
mapClicked &&
|
||||||
|
mapClicked.setItemFormPopup({
|
||||||
|
layer: selectPosition as LayerProps,
|
||||||
|
position: mapClicked?.position,
|
||||||
|
})
|
||||||
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 =
|
||||||
position && itemUpdatePosition({ ...selectPosition as Item, position })
|
mapClicked?.position.lng &&
|
||||||
|
new Geometry(mapClicked?.position.lng, mapClicked?.position.lat)
|
||||||
|
position && itemUpdatePosition({ ...(selectPosition as Item), position })
|
||||||
setSelectPosition(null)
|
setSelectPosition(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,10 +68,17 @@ function useSelectPositionManager (): {
|
|||||||
}, [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())
|
||||||
@ -81,7 +99,10 @@ function useSelectPositionManager (): {
|
|||||||
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())
|
||||||
@ -96,7 +117,7 @@ function useSelectPositionManager (): {
|
|||||||
if (markerClicked) {
|
if (markerClicked) {
|
||||||
const newRelations = markerClicked.relations || []
|
const newRelations = markerClicked.relations || []
|
||||||
|
|
||||||
if (!newRelations.some(r => r.related_items_id === id)) {
|
if (!newRelations.some((r) => r.related_items_id === id)) {
|
||||||
newRelations?.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: newRelations }
|
const updatedItem = { id: markerClicked.id, relations: newRelations }
|
||||||
|
|
||||||
@ -118,11 +139,11 @@ function useSelectPositionManager (): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SelectPositionProvider: React.FunctionComponent<{
|
export const SelectPositionProvider: React.FunctionComponent<{
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
}> = ({ children }) => (
|
}> = ({ children }) => (
|
||||||
<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 => {
|
||||||
|
|||||||
@ -5,27 +5,25 @@ 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: 'REMOVE'; id: string }
|
||||||
| { type: 'ADD'; tag: Tag }
|
|
||||||
| { type: 'REMOVE'; id: string };
|
|
||||||
|
|
||||||
type UseTagManagerResult = ReturnType<typeof useTagsManager>;
|
type UseTagManagerResult = ReturnType<typeof useTagsManager>
|
||||||
|
|
||||||
const TagContext = createContext<UseTagManagerResult>({
|
const TagContext = createContext<UseTagManagerResult>({
|
||||||
tags: [],
|
tags: [],
|
||||||
addTag: () => { },
|
addTag: () => {},
|
||||||
setTagApi: () => { },
|
setTagApi: () => {},
|
||||||
setTagData: () => { },
|
setTagData: () => {},
|
||||||
getItemTags: () => [],
|
getItemTags: () => [],
|
||||||
allTagsLoaded: false
|
allTagsLoaded: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
function useTagsManager (initialTags: Tag[]): {
|
function useTagsManager(initialTags: Tag[]): {
|
||||||
tags: Tag[];
|
tags: Tag[]
|
||||||
addTag: (tag: Tag) => void;
|
addTag: (tag: Tag) => void
|
||||||
setTagApi: (api: ItemsApi<Tag>) => void;
|
setTagApi: (api: ItemsApi<Tag>) => void
|
||||||
setTagData: (data: Tag[]) => void;
|
setTagData: (data: Tag[]) => void
|
||||||
getItemTags: (item: Item) => Tag[];
|
getItemTags: (item: Item) => Tag[]
|
||||||
allTagsLoaded: boolean
|
allTagsLoaded: boolean
|
||||||
} {
|
} {
|
||||||
const [allTagsLoaded, setallTagsLoaded] = useState<boolean>(false)
|
const [allTagsLoaded, setallTagsLoaded] = useState<boolean>(false)
|
||||||
@ -35,14 +33,11 @@ function useTagsManager (initialTags: Tag[]): {
|
|||||||
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.name.toLocaleLowerCase() === action.tag.name.toLocaleLowerCase()
|
(tag) => tag.name.toLocaleLowerCase() === action.tag.name.toLocaleLowerCase(),
|
||||||
)
|
)
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
const newState = [
|
const newState = [...state, { ...action.tag }]
|
||||||
...state,
|
|
||||||
{ ...action.tag }
|
|
||||||
]
|
|
||||||
if (tagCount === newState.length) setallTagsLoaded(true)
|
if (tagCount === newState.length) setallTagsLoaded(true)
|
||||||
return newState
|
return newState
|
||||||
} else return state
|
} else return state
|
||||||
@ -59,18 +54,18 @@ function useTagsManager (initialTags: Tag[]): {
|
|||||||
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
|
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
|
return null
|
||||||
@ -80,46 +75,53 @@ function useTagsManager (initialTags: Tag[]): {
|
|||||||
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(
|
||||||
const text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined
|
(item: Item) => {
|
||||||
const itemTagStrings = text?.match(hashTagRegex)
|
const text =
|
||||||
const itemTags: Tag[] = []
|
item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined
|
||||||
itemTagStrings?.map(tag => {
|
const itemTagStrings = text?.match(hashTagRegex)
|
||||||
if (tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
const itemTags: Tag[] = []
|
||||||
itemTags.push(tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())!)
|
itemTagStrings?.map((tag) => {
|
||||||
}
|
if (tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
return null
|
itemTags.push(
|
||||||
})
|
tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())!,
|
||||||
item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => {
|
)
|
||||||
const offer = tags.find(t => t.id === o.tags_id)
|
}
|
||||||
offer && itemTags.push(offer)
|
return null
|
||||||
return null
|
})
|
||||||
})
|
item.layer?.itemOffersField &&
|
||||||
item.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
|
getValue(item, item.layer.itemOffersField)?.map((o) => {
|
||||||
const need = tags.find(t => t.id === n.tags_id)
|
const offer = tags.find((t) => t.id === o.tags_id)
|
||||||
need && itemTags.push(need)
|
offer && itemTags.push(offer)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
item.layer?.itemNeedsField &&
|
||||||
|
getValue(item, item.layer.itemNeedsField)?.map((n) => {
|
||||||
|
const need = tags.find((t) => t.id === n.tags_id)
|
||||||
|
need && itemTags.push(need)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
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<{
|
||||||
initialTags: Tag[], children?: React.ReactNode
|
initialTags: Tag[]
|
||||||
|
children?: React.ReactNode
|
||||||
}> = ({ initialTags, children }) => (
|
}> = ({ initialTags, children }) => (
|
||||||
<TagContext.Provider value={useTagsManager(initialTags)}>
|
<TagContext.Provider value={useTagsManager(initialTags)}>{children}</TagContext.Provider>
|
||||||
{children}
|
|
||||||
</TagContext.Provider>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const useTags = (): Tag[] => {
|
export const useTags = (): Tag[] => {
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,5 @@ 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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ 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: '',
|
||||||
@ -33,7 +33,7 @@ export function ProfileForm ({ userType }: { userType: string }) {
|
|||||||
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)
|
||||||
@ -61,37 +61,45 @@ export function ProfileForm ({ userType }: { userType: string }) {
|
|||||||
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, 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])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newColor = item.layer?.itemColorField && getValue(item, item.layer?.itemColorField)
|
const newColor =
|
||||||
? getValue(item, item.layer?.itemColorField)
|
item.layer?.itemColorField && getValue(item, item.layer?.itemColorField)
|
||||||
: (getItemTags(item) && getItemTags(item)[0]?.color)
|
? getValue(item, item.layer?.itemColorField)
|
||||||
|
: 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
|
||||||
}, [])
|
}, [])
|
||||||
@ -113,7 +121,7 @@ export function ProfileForm ({ userType }: { userType: string }) {
|
|||||||
needs,
|
needs,
|
||||||
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])
|
||||||
@ -125,32 +133,64 @@ export function ProfileForm ({ userType }: { userType: string }) {
|
|||||||
}, [userType, item])
|
}, [userType, item])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MapOverlayPage backdrop className='tw-mx-4 tw-mt-4 tw-mb-4 tw-overflow-x-hidden tw-w-[calc(100%-32px)] md:tw-w-[calc(50%-32px)] tw-max-w-3xl !tw-left-auto tw-top-0 tw-bottom-0'>
|
<MapOverlayPage
|
||||||
|
backdrop
|
||||||
|
className='tw-mx-4 tw-mt-4 tw-mb-4 tw-overflow-x-hidden tw-w-[calc(100%-32px)] md:tw-w-[calc(50%-32px)] tw-max-w-3xl !tw-left-auto tw-top-0 tw-bottom-0'
|
||||||
|
>
|
||||||
|
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||||
|
<FormHeader item={item} state={state} setState={setState} />
|
||||||
|
|
||||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
{template === 'onepager' && (
|
||||||
|
<OnepagerForm item={item} state={state} setState={setState}></OnepagerForm>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormHeader item={item} state={state} setState={setState} />
|
{template === 'simple' && <SimpleForm state={state} setState={setState}></SimpleForm>}
|
||||||
|
|
||||||
{template === 'onepager' && (
|
{template === 'tabs' && (
|
||||||
<OnepagerForm item={item} state={state} setState={setState}></OnepagerForm>
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
{template === 'simple' &&
|
<div className='tw-mt-4 tw-mb-4'>
|
||||||
<SimpleForm state={state} setState={setState}></SimpleForm>
|
<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' }
|
||||||
{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>
|
>
|
||||||
}
|
Update
|
||||||
|
</button>
|
||||||
<div className="tw-mt-4 tw-mb-4">
|
</div>
|
||||||
<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>
|
</MapOverlayPage>
|
||||||
|
</>
|
||||||
</div>
|
|
||||||
|
|
||||||
</MapOverlayPage>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,13 @@ 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>>([])
|
||||||
@ -43,13 +49,14 @@ export function ProfileView ({ userType, attestationApi }: { userType: string, a
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (attestationApi) {
|
if (attestationApi) {
|
||||||
attestationApi.getItems()
|
attestationApi
|
||||||
.then(value => {
|
.getItems()
|
||||||
|
.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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -57,7 +64,7 @@ export function ProfileView ({ userType, attestationApi }: { userType: string, a
|
|||||||
|
|
||||||
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])
|
||||||
|
|
||||||
@ -66,19 +73,21 @@ export function ProfileView ({ userType, attestationApi }: { userType: string, a
|
|||||||
setNeeds([])
|
setNeeds([])
|
||||||
setRelations([])
|
setRelations([])
|
||||||
|
|
||||||
item?.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => {
|
item?.layer?.itemOffersField &&
|
||||||
const tag = tags.find(t => t.id === o.tags_id)
|
getValue(item, item.layer.itemOffersField)?.map((o) => {
|
||||||
tag && setOffers(current => [...current, tag])
|
const tag = tags.find((t) => t.id === o.tags_id)
|
||||||
return null
|
tag && setOffers((current) => [...current, tag])
|
||||||
})
|
return null
|
||||||
item?.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
|
})
|
||||||
const tag = tags.find(t => t.id === n.tags_id)
|
item?.layer?.itemNeedsField &&
|
||||||
tag && setNeeds(current => [...current, tag])
|
getValue(item, item.layer.itemNeedsField)?.map((n) => {
|
||||||
return null
|
const tag = tags.find((t) => t.id === n.tags_id)
|
||||||
})
|
tag && setNeeds((current) => [...current, tag])
|
||||||
item?.relations?.map(r => {
|
return null
|
||||||
const item = items.find(i => i.id === r.related_items_id)
|
})
|
||||||
item && setRelations(current => [...current, item])
|
item?.relations?.map((r) => {
|
||||||
|
const item = items.find((i) => i.id === r.related_items_id)
|
||||||
|
item && setRelations((current) => [...current, item])
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -87,36 +96,41 @@ export function ProfileView ({ userType, attestationApi }: { userType: string, a
|
|||||||
|
|
||||||
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 &&
|
||||||
const bounds = map.getBounds()
|
clusterRef.hasLayer(marker) &&
|
||||||
const x = bounds.getEast() - bounds.getWest()
|
clusterRef?.zoomToShowLayer(marker, () => {
|
||||||
setMap(marker, x)
|
const bounds = map.getBounds()
|
||||||
}
|
const x = bounds.getEast() - bounds.getWest()
|
||||||
)
|
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 &&
|
||||||
const bounds = map.getBounds()
|
clusterRef.hasLayer(marker) &&
|
||||||
const x = bounds.getEast() - bounds.getWest()
|
clusterRef?.zoomToShowLayer(marker, () => {
|
||||||
setMap(marker, x)
|
const bounds = map.getBounds()
|
||||||
}
|
const x = bounds.getEast() - bounds.getWest()
|
||||||
)
|
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 {
|
||||||
@ -139,29 +153,50 @@ export function ProfileView ({ userType, attestationApi }: { userType: string, a
|
|||||||
}, [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}
|
||||||
<div className={'tw-px-6 tw-pt-6'}>
|
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'}`}
|
||||||
<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 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}
|
||||||
|
/>
|
||||||
|
</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}
|
||||||
</MapOverlayPage >
|
loading={loading}
|
||||||
}
|
offers={offers}
|
||||||
</>
|
needs={needs}
|
||||||
|
relations={relations}
|
||||||
|
updatePermission={updatePermission}
|
||||||
|
linkItem={(id) => linkItem(id, item, updateItem)}
|
||||||
|
unlinkItem={(id) => unlinkItem(id, item, updateItem)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</MapOverlayPage>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,15 +8,24 @@ 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({
|
||||||
triggerAddButton?: any,
|
item,
|
||||||
triggerItemSelected?: any,
|
triggerAddButton,
|
||||||
existingRelations: Item[],
|
triggerItemSelected,
|
||||||
itemType?: string;
|
existingRelations,
|
||||||
colorField?: string,
|
itemType,
|
||||||
collection?: string,
|
colorField,
|
||||||
customStyle?:string,
|
collection = 'items',
|
||||||
item: Item
|
customStyle,
|
||||||
|
}: {
|
||||||
|
triggerAddButton?: any
|
||||||
|
triggerItemSelected?: any
|
||||||
|
existingRelations: Item[]
|
||||||
|
itemType?: string
|
||||||
|
colorField?: string
|
||||||
|
collection?: string
|
||||||
|
customStyle?: string
|
||||||
|
item: Item
|
||||||
}) {
|
}) {
|
||||||
const hasUserPermission = useHasUserPermission()
|
const hasUserPermission = useHasUserPermission()
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||||
@ -25,37 +34,102 @@ export function ActionButton ({ item, triggerAddButton, triggerItemSelected, exi
|
|||||||
|
|
||||||
const items = useItems()
|
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 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}`} >
|
<>
|
||||||
{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' }}>
|
<div className={`tw-absolute tw-right-4 tw-bottom-4 tw-flex tw-flex-col ${customStyle}`}>
|
||||||
<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>
|
{triggerItemSelected && (
|
||||||
|
<button
|
||||||
</button>}
|
tabIndex={0}
|
||||||
{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' }}>
|
className='tw-z-500 tw-btn tw-btn-circle tw-shadow'
|
||||||
<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">
|
onClick={() => {
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
setModalOpen(true)
|
||||||
</svg>
|
}}
|
||||||
</button>}
|
style={{
|
||||||
</div>
|
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor}`,
|
||||||
<DialogModal title={'Select'} isOpened={modalOpen} onClose={() => (setModalOpen(false))} className="tw-w-xl sm:tw-w-2xl tw-min-h-80 tw-bg-base-200">
|
color: '#fff',
|
||||||
<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'>
|
>
|
||||||
{filterdItems.filter(item => {
|
<svg
|
||||||
return search === ''
|
className='tw-h-5 tw-w-5'
|
||||||
? item
|
stroke='currentColor'
|
||||||
: item.name.toLowerCase().includes(search.toLowerCase())
|
fill='currentColor'
|
||||||
}).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) }}>
|
strokeWidth='0'
|
||||||
<HeaderView item={i} hideMenu></HeaderView>
|
viewBox='0 0 512 512'
|
||||||
</div>)}
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
</div>
|
>
|
||||||
</DialogModal>
|
<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>
|
||||||
|
)}
|
||||||
|
{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'
|
||||||
|
>
|
||||||
|
<path strokeLinecap='round' strokeLinejoin='round' d='M12 4.5v15m7.5-7.5h-15' />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</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'
|
||||||
|
>
|
||||||
|
<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'>
|
||||||
|
{filterdItems
|
||||||
|
.filter((item) => {
|
||||||
|
return search === ''
|
||||||
|
? item
|
||||||
|
: 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)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderView item={i} hideMenu></HeaderView>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DialogModal>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ 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
|
||||||
setAvatar: React.Dispatch<React.SetStateAction<any>>;
|
setAvatar: React.Dispatch<React.SetStateAction<any>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar }) => {
|
export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar }) => {
|
||||||
@ -54,18 +54,22 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
|
|||||||
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')
|
||||||
|
|
||||||
@ -102,10 +106,7 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
|
|||||||
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.height * scaleY)
|
||||||
crop.width * scaleX,
|
|
||||||
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
|
||||||
@ -122,7 +123,7 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
crop.width * scaleX,
|
crop.width * scaleX,
|
||||||
crop.height * scaleY
|
crop.height * scaleY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,63 +135,102 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
|
|||||||
// 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(
|
||||||
const img = new Image()
|
async (blob: Blob) => {
|
||||||
img.src = URL.createObjectURL(blob)
|
const img = new Image()
|
||||||
await img.decode()
|
img.src = URL.createObjectURL(blob)
|
||||||
|
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
|
||||||
<div className='button tw-btn tw-btn-lg tw-btn-circle tw-animate-none'>
|
type='file'
|
||||||
<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">
|
accept='image/*'
|
||||||
<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" />
|
className='tw-file-input tw-w-full tw-max-w-xs'
|
||||||
</svg>
|
onChange={onImageChange}
|
||||||
</div>
|
/>
|
||||||
{avatar
|
<div className='button tw-btn tw-btn-lg tw-btn-circle tw-animate-none'>
|
||||||
? <div className='tw-h-20 tw-w-20'>
|
<svg
|
||||||
<img src={assetsApi.url + avatar} className='tw-h-20 tw-w-20 tw-rounded-full' />
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
</div>
|
fill='none'
|
||||||
: <div className='tw-h-20 tw-w-20'>
|
viewBox='0 0 24 24'
|
||||||
<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' }}>
|
strokeWidth='1.5'
|
||||||
<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" />
|
stroke='currentColor'
|
||||||
<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" />
|
className='tw-w-6 tw-h-6'
|
||||||
</svg>
|
>
|
||||||
</div>
|
<path
|
||||||
}
|
strokeLinecap='round'
|
||||||
</label>
|
strokeLinejoin='round'
|
||||||
: <div className='tw-w-20 tw-flex tw-items-center tw-justify-center'>
|
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'
|
||||||
<span className="tw-loading tw-loading-spinner"></span>
|
/>
|
||||||
</div>
|
</svg>
|
||||||
}
|
</div>
|
||||||
<DialogModal
|
{avatar ? (
|
||||||
title=""
|
<div className='tw-h-20 tw-w-20'>
|
||||||
isOpened={cropModalOpen}
|
<img src={assetsApi.url + avatar} className='tw-h-20 tw-w-20 tw-rounded-full' />
|
||||||
onClose={() => {
|
</div>
|
||||||
setCropModalOpen(false)
|
) : (
|
||||||
setImage('')
|
<div className='tw-h-20 tw-w-20'>
|
||||||
}}
|
<svg
|
||||||
closeOnClickOutside={false}>
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
<ReactCrop crop={crop} onChange={(c) => setCrop(c)} aspect={1} >
|
version='1.0'
|
||||||
<img src={image} ref={imgRef} onLoad={onImageLoad} />
|
viewBox='0 0 150 150'
|
||||||
</ReactCrop>
|
className='tw-w-20 tw-h-20 tw-rounded-full'
|
||||||
<button className={'tw-btn tw-btn-primary'} onClick={() => {
|
style={{ backgroundColor: '#eee' }}
|
||||||
setCropping(true)
|
>
|
||||||
setCropModalOpen(false)
|
<path
|
||||||
renderCrop()
|
fill='#ccc'
|
||||||
}}>Select</button>
|
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'
|
||||||
</DialogModal>
|
/>
|
||||||
</>
|
<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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
) : (
|
||||||
|
<div className='tw-w-20 tw-flex tw-items-center tw-justify-center'>
|
||||||
|
<span className='tw-loading tw-loading-spinner'></span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DialogModal
|
||||||
|
title=''
|
||||||
|
isOpened={cropModalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setCropModalOpen(false)
|
||||||
|
setImage('')
|
||||||
|
}}
|
||||||
|
closeOnClickOutside={false}
|
||||||
|
>
|
||||||
|
<ReactCrop crop={crop} onChange={(c) => setCrop(c)} aspect={1}>
|
||||||
|
<img src={image} ref={imgRef} onLoad={onImageLoad} />
|
||||||
|
</ReactCrop>
|
||||||
|
<button
|
||||||
|
className={'tw-btn tw-btn-primary'}
|
||||||
|
onClick={() => {
|
||||||
|
setCropping(true)
|
||||||
|
setCropModalOpen(false)
|
||||||
|
renderCrop()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
</DialogModal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,10 +21,10 @@ export const ColorPicker = ({ color, onChange, className }) => {
|
|||||||
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -32,15 +32,11 @@ export const ColorPicker = ({ color, onChange, className }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={colorPickerRef} className={`picker ${className}`}>
|
<div ref={colorPickerRef} className={`picker ${className}`}>
|
||||||
<div
|
<div className='swatch' style={{ backgroundColor: color }} onClick={() => toggle(true)} />
|
||||||
className="swatch"
|
|
||||||
style={{ backgroundColor: color }}
|
|
||||||
onClick={() => toggle(true)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="popover tw-z-[10000]" ref={popover}>
|
<div className='popover tw-z-[10000]' ref={popover}>
|
||||||
<HexColorPicker color={color} onChange={onChange} onClick={() => toggle(false)}/>
|
<HexColorPicker color={color} onChange={onChange} onClick={() => toggle(false)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,59 +1,90 @@
|
|||||||
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'>
|
||||||
<h2 className="tw-text-lg tw-font-semibold">Du hast Fragen?</h2>
|
<h2 className='tw-text-lg tw-font-semibold'>Du hast Fragen?</h2>
|
||||||
<div className="tw-mt-4 tw-flex tw-items-center">
|
<div className='tw-mt-4 tw-flex tw-items-center'>
|
||||||
{avatar && (
|
{avatar && (
|
||||||
<ConditionalLink url={link}>
|
<ConditionalLink url={link}>
|
||||||
<div className="tw-mr-5 tw-flex tw-items-center tw-justify-center">
|
<div className='tw-mr-5 tw-flex tw-items-center tw-justify-center'>
|
||||||
<div className="tw-avatar">
|
<div className='tw-avatar'>
|
||||||
<div className="tw-w-20 tw-h-20 tw-bg-gray-200 rounded-full tw-flex tw-items-center tw-justify-center overflow-hidden">
|
<div className='tw-w-20 tw-h-20 tw-bg-gray-200 rounded-full tw-flex tw-items-center tw-justify-center overflow-hidden'>
|
||||||
<img src={assetsApi.url + avatar} alt={name}
|
<img
|
||||||
className="tw-w-full tw-h-full tw-object-cover" />
|
src={assetsApi.url + avatar}
|
||||||
</div>
|
alt={name}
|
||||||
</div>
|
className='tw-w-full tw-h-full tw-object-cover'
|
||||||
</div>
|
/>
|
||||||
</ConditionalLink>
|
|
||||||
)}
|
|
||||||
<div className="tw-text-sm tw-flex-grow">
|
|
||||||
<p className="tw-font-semibold">{name}</p>
|
|
||||||
{email && (
|
|
||||||
<p>
|
|
||||||
<a href={`mailto:${email}`}
|
|
||||||
className="tw-mt-2 tw-text-green-500 tw-inline-flex tw-items-center">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
||||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
||||||
className="tw-w-4 tw-h-4 tw-mr-1">
|
|
||||||
<path
|
|
||||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
||||||
<polyline points="22,6 12,13 2,6"></polyline>
|
|
||||||
</svg>
|
|
||||||
{email}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{telephone && (
|
|
||||||
<p>
|
|
||||||
<a href={`tel:${telephone}`}
|
|
||||||
className="tw-mt-2 tw-text-green-500 tw-inline-flex tw-items-center tw-whitespace-nowrap">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
||||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
||||||
className="tw-w-4 tw-h-4 tw-mr-1">
|
|
||||||
<path
|
|
||||||
d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" />
|
|
||||||
</svg>
|
|
||||||
{telephone}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ConditionalLink>
|
||||||
|
)}
|
||||||
|
<div className='tw-text-sm tw-flex-grow'>
|
||||||
|
<p className='tw-font-semibold'>{name}</p>
|
||||||
|
{email && (
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href={`mailto:${email}`}
|
||||||
|
className='tw-mt-2 tw-text-green-500 tw-inline-flex tw-items-center'
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
fill='none'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeWidth='2'
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
className='tw-w-4 tw-h-4 tw-mr-1'
|
||||||
|
>
|
||||||
|
<path d='M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z'></path>
|
||||||
|
<polyline points='22,6 12,13 2,6'></polyline>
|
||||||
|
</svg>
|
||||||
|
{email}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{telephone && (
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href={`tel:${telephone}`}
|
||||||
|
className='tw-mt-2 tw-text-green-500 tw-inline-flex tw-items-center tw-whitespace-nowrap'
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
fill='none'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeWidth='2'
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
className='tw-w-4 tw-h-4 tw-mr-1'
|
||||||
|
>
|
||||||
|
<path d='M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z' />
|
||||||
|
</svg>
|
||||||
|
{telephone}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +95,7 @@ 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}>{children}</Link>
|
||||||
<Link to={url + '?' + params}>
|
|
||||||
{children}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,25 +5,50 @@ import { ColorPicker } from './ColorPicker'
|
|||||||
|
|
||||||
export const FormHeader = ({ item, state, setState }) => {
|
export const FormHeader = ({ item, state, setState }) => {
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex">
|
<div className='tw-flex'>
|
||||||
<AvatarWidget avatar={state.image} setAvatar={(i) => setState(prevState => ({
|
<AvatarWidget
|
||||||
...prevState,
|
avatar={state.image}
|
||||||
image: i
|
setAvatar={(i) =>
|
||||||
}))} />
|
setState((prevState) => ({
|
||||||
<ColorPicker color={state.color} onChange={(c) => setState(prevState => ({
|
...prevState,
|
||||||
...prevState,
|
image: i,
|
||||||
color: c
|
}))
|
||||||
}))} className={'-tw-left-6 tw-top-14 -tw-mr-6'} />
|
}
|
||||||
<div className='tw-grow tw-mr-4'>
|
/>
|
||||||
<TextInput placeholder="Name" defaultValue={item?.name ? item.name : ''} updateFormValue={(v) => setState(prevState => ({
|
<ColorPicker
|
||||||
...prevState,
|
color={state.color}
|
||||||
name: v
|
onChange={(c) =>
|
||||||
}))} containerStyle='tw-grow tw-input-md' />
|
setState((prevState) => ({
|
||||||
<TextInput placeholder="Subtitle" defaultValue={item?.subname ? item.subname : ''} updateFormValue={(v) => setState(prevState => ({
|
...prevState,
|
||||||
...prevState,
|
color: c,
|
||||||
subname: v
|
}))
|
||||||
}))} containerStyle='tw-grow tw-input-sm tw-px-4 tw-mt-1' />
|
}
|
||||||
|
className={'-tw-left-6 tw-top-14 -tw-mr-6'}
|
||||||
|
/>
|
||||||
|
<div className='tw-grow tw-mr-4'>
|
||||||
|
<TextInput
|
||||||
|
placeholder='Name'
|
||||||
|
defaultValue={item?.name ? item.name : ''}
|
||||||
|
updateFormValue={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
name: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
containerStyle='tw-grow tw-input-md'
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder='Subtitle'
|
||||||
|
defaultValue={item?.subname ? item.subname : ''}
|
||||||
|
updateFormValue={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
subname: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
containerStyle='tw-grow tw-input-sm tw-px-4 tw-mt-1'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,30 +3,46 @@ 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: Item,
|
item,
|
||||||
unlinkCallback?: any,
|
unlinkCallback,
|
||||||
itemNameField?: string,
|
itemNameField,
|
||||||
itemAvatarField?: string,
|
itemAvatarField,
|
||||||
itemSubnameField?: string,
|
loading,
|
||||||
loading?: boolean,
|
unlinkPermission,
|
||||||
|
itemSubnameField,
|
||||||
|
}: {
|
||||||
|
item: Item
|
||||||
|
unlinkCallback?: any
|
||||||
|
itemNameField?: string
|
||||||
|
itemAvatarField?: string
|
||||||
|
itemSubnameField?: string
|
||||||
|
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 avatar =
|
||||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField)
|
itemAvatarField && getValue(item, itemAvatarField)
|
||||||
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField)
|
? 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'}
|
||||||
@ -35,33 +51,59 @@ export function LinkedItemsHeaderView ({ item, unlinkCallback, itemNameField, it
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<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}</div>
|
||||||
{title}
|
{subtitle && (
|
||||||
</div>
|
<div className='tw-text-xs tw-truncate tw-text-gray-500 '>{subtitle}</div>
|
||||||
{subtitle && <div className="tw-text-xs tw-truncate tw-text-gray-500 ">
|
)}
|
||||||
{subtitle}
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='tw-col-span-1' onClick={(e) => e.stopPropagation()}>
|
<div className='tw-col-span-1' onClick={(e) => e.stopPropagation()}>
|
||||||
{unlinkPermission &&
|
{unlinkPermission && (
|
||||||
<div className="tw-dropdown tw-dropdown-bottom">
|
<div className='tw-dropdown tw-dropdown-bottom'>
|
||||||
<label tabIndex={0} className=" tw-btn tw-m-1 tw-leading-3 tw-border-none tw-min-h-0 tw-h-6">
|
<label
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
tabIndex={0}
|
||||||
<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" />
|
className=' 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'
|
||||||
|
>
|
||||||
|
<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={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000">
|
<ul
|
||||||
{true && <li>
|
tabIndex={0}
|
||||||
<a className='tw-cursor-pointer !tw-text-error' onClick={() => unlinkCallback(item.id)}>
|
className='tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000'
|
||||||
{loading
|
>
|
||||||
? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
|
{true && (
|
||||||
: <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>}
|
<li>
|
||||||
</a>
|
<a
|
||||||
</li>}
|
className='tw-cursor-pointer !tw-text-error'
|
||||||
|
onClick={() => unlinkCallback(item.id)}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,19 +1,43 @@
|
|||||||
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) &&
|
<>
|
||||||
<div className="tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-3000 tw-absolute tw-right-4 tw-bottom-4" >
|
{hasUserPermission(collection, 'create', undefined, layer) && (
|
||||||
<button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { triggerAction() }} style={{ backgroundColor: color, color: '#fff' }}>
|
<div className='tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-3000 tw-absolute tw-right-4 tw-bottom-4'>
|
||||||
<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">
|
<button
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
tabIndex={0}
|
||||||
</svg>
|
className='tw-z-500 tw-btn tw-btn-circle tw-shadow'
|
||||||
</button>
|
onClick={() => {
|
||||||
</div>
|
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'
|
||||||
|
>
|
||||||
|
<path strokeLinecap='round' strokeLinejoin='round' d='M12 4.5v15m7.5-7.5h-15' />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,25 +20,35 @@ 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 }) => (
|
||||||
<div>
|
<div>
|
||||||
<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 && (
|
||||||
{status && <div className="tw-mt-1.5">
|
<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'>
|
||||||
</div>}
|
<span
|
||||||
{type && <div className="tw-mt-1.5">
|
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 className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-1 tw-px-2">{type}</span>
|
></span>
|
||||||
</div>}
|
{statusMapping[status]}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
)}
|
||||||
<SocialShareBar url={url} title={title} />
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<SocialShareBar url={url} title={title} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default SubHeader
|
export default SubHeader
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
// 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' : ''}`}>
|
||||||
{imageSrc && (
|
{imageSrc && (
|
||||||
<div className="md:tw-w-1/2 tw-mb-4 md:tw-mb-0">
|
<div className='md:tw-w-1/2 tw-mb-4 md:tw-mb-0'>
|
||||||
<img src={imageSrc} alt={title} className="tw-w-full tw-h-32 tw-object-cover" />
|
<img src={imageSrc} alt={title} className='tw-w-full tw-h-32 tw-object-cover' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={imageSrc ? 'md:tw-w-1/2' : 'tw-w-full'}>
|
<div className={imageSrc ? 'md:tw-w-1/2' : 'tw-w-full'}>
|
||||||
<h3 className="tw-text-lg tw-font-semibold">{title}</h3>
|
<h3 className='tw-text-lg tw-font-semibold'>{title}</h3>
|
||||||
<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
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
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'],
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-place-content-end tw-justify-end tw-space-x-2 tw-grow tw-min-w-fit tw-pl-2">
|
<div className='tw-flex tw-place-content-end tw-justify-end tw-space-x-2 tw-grow tw-min-w-fit tw-pl-2'>
|
||||||
{platforms.map((platform) => (
|
{platforms.map((platform) => (
|
||||||
<SocialShareButton
|
<SocialShareButton key={platform} platform={platform} url={url} title={title} />
|
||||||
key={platform}
|
))}
|
||||||
platform={platform}
|
</div>
|
||||||
url={url}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,49 +4,49 @@ const platformConfigs = {
|
|||||||
facebook: {
|
facebook: {
|
||||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||||
icon: (
|
icon: (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'>
|
||||||
<path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z" />
|
<path d='M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z' />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
bgColor: '#3b5998'
|
bgColor: '#3b5998',
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||||
icon: (
|
icon: (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'>
|
||||||
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z" />
|
<path d='M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z' />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
bgColor: '#55acee'
|
bgColor: '#55acee',
|
||||||
},
|
},
|
||||||
linkedin: {
|
linkedin: {
|
||||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||||
icon: (
|
icon: (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'>
|
||||||
<path d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z" />
|
<path d='M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z' />
|
||||||
<circle cx="4" cy="4" r="2" />
|
<circle cx='4' cy='4' r='2' />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
bgColor: '#4875b4'
|
bgColor: '#4875b4',
|
||||||
},
|
},
|
||||||
xing: {
|
xing: {
|
||||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||||
icon: (
|
icon: (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'>
|
||||||
<path d="M18.188 0c-.517 0-.741.325-.927.66 0 0-7.455 13.224-7.702 13.657.015.024 4.919 9.023 4.919 9.023.17.308.436.66.967.66h3.454c.211 0 .375-.078.463-.22.089-.151.089-.346-.009-.536l-4.879-8.916c-.004-.006-.004-.016 0-.022L22.139.756c.095-.191.097-.387.006-.535C22.056.078 21.894 0 21.686 0h-3.498zM3.648 4.74c-.211 0-.385.074-.473.216-.09.149-.078.339.02.531l2.34 4.05c.004.01.004.016 0 .021L1.86 16.051c-.099.188-.093.381 0 .529.085.142.239.234.45.234h3.461c.518 0 .766-.348.945-.667l3.734-6.609-2.378-4.155c-.172-.315-.434-.659-.962-.659H3.648v.016z" />
|
<path d='M18.188 0c-.517 0-.741.325-.927.66 0 0-7.455 13.224-7.702 13.657.015.024 4.919 9.023 4.919 9.023.17.308.436.66.967.66h3.454c.211 0 .375-.078.463-.22.089-.151.089-.346-.009-.536l-4.879-8.916c-.004-.006-.004-.016 0-.022L22.139.756c.095-.191.097-.387.006-.535C22.056.078 21.894 0 21.686 0h-3.498zM3.648 4.74c-.211 0-.385.074-.473.216-.09.149-.078.339.02.531l2.34 4.05c.004.01.004.016 0 .021L1.86 16.051c-.099.188-.093.381 0 .529.085.142.239.234.45.234h3.461c.518 0 .766-.348.945-.667l3.734-6.609-2.378-4.155c-.172-.315-.434-.659-.962-.659H3.648v.016z' />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
bgColor: '#026466'
|
bgColor: '#026466',
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
shareUrl: 'mailto:?subject={title}&body={url}',
|
shareUrl: 'mailto:?subject={title}&body={url}',
|
||||||
icon: (
|
icon: (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'>
|
||||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
<path d='M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z' />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
bgColor: '#444444'
|
bgColor: '#444444',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
@ -63,18 +63,18 @@ const SocialShareButton = ({ platform, url, title }) => {
|
|||||||
.replace('{title}', encodeURIComponent(title))
|
.replace('{title}', encodeURIComponent(title))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={finalShareUrl}
|
href={finalShareUrl}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
className='tw-w-8 tw-h-8 tw-mt-2 tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-white'
|
className='tw-w-8 tw-h-8 tw-mt-2 tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-white'
|
||||||
style={{
|
style={{
|
||||||
color: 'white',
|
color: 'white',
|
||||||
backgroundColor: bgColor
|
backgroundColor: bgColor,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,11 +30,19 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
|
|||||||
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([])
|
||||||
}
|
}
|
||||||
@ -61,10 +69,14 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
|
|||||||
|
|
||||||
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([])
|
||||||
}
|
}
|
||||||
@ -76,27 +88,45 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
|
|||||||
onKeyDown,
|
onKeyDown,
|
||||||
onKeyUp,
|
onKeyUp,
|
||||||
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
|
||||||
setFocusInput(true)
|
onClick={() => {
|
||||||
setTimeout(() => {
|
setFocusInput(true)
|
||||||
setFocusInput(false)
|
setTimeout(() => {
|
||||||
}, 200)
|
setFocusInput(false)
|
||||||
}} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}>
|
}, 200)
|
||||||
|
}}
|
||||||
|
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
|
||||||
<div className="tw-card-actions tw-justify-end">
|
key={tag.name}
|
||||||
<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>
|
className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4'
|
||||||
</div><b>{decodeTag(tag.name)}</b>
|
style={{ backgroundColor: tag.color ? tag.color : '#666' }}
|
||||||
</div>
|
>
|
||||||
|
<div className='tw-card-actions tw-justify-end'>
|
||||||
))}
|
<label
|
||||||
<Autocomplete suggestions={tags} pushFilteredSuggestions={pushFilteredSuggestions} setFocus={focusInput} inputProps={inputProps} onSelected={(tag) => onSelected(tag)}/>
|
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>
|
||||||
|
))}
|
||||||
|
<Autocomplete
|
||||||
|
suggestions={tags}
|
||||||
|
pushFilteredSuggestions={pushFilteredSuggestions}
|
||||||
|
setFocus={focusInput}
|
||||||
|
inputProps={inputProps}
|
||||||
|
onSelected={(tag) => onSelected(tag)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,51 +4,55 @@ 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 = ({
|
||||||
state: {
|
item,
|
||||||
color: string;
|
state,
|
||||||
id: string;
|
setState,
|
||||||
groupType: string;
|
}: {
|
||||||
status: string;
|
state: {
|
||||||
name: string;
|
color: string
|
||||||
subname: string;
|
id: string
|
||||||
text: string;
|
groupType: string
|
||||||
contact: string;
|
status: string
|
||||||
telephone: string;
|
name: string
|
||||||
nextAppointment: string;
|
subname: string
|
||||||
image: string;
|
text: string
|
||||||
markerIcon: string;
|
contact: string
|
||||||
offers: Tag[];
|
telephone: string
|
||||||
needs: Tag[];
|
nextAppointment: string
|
||||||
relations: Item[];
|
image: string
|
||||||
},
|
markerIcon: string
|
||||||
setState: React.Dispatch<React.SetStateAction<any>>,
|
offers: Tag[]
|
||||||
item: Item
|
needs: Tag[]
|
||||||
|
relations: Item[]
|
||||||
|
}
|
||||||
|
setState: React.Dispatch<React.SetStateAction<any>>
|
||||||
|
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
|
||||||
@ -61,104 +65,134 @@ export const OnepagerForm = ({ item, state, setState }: {
|
|||||||
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'>
|
||||||
<div className="tw-grid tw-grid-cols-1 md:tw-grid-cols-2 tw-gap-6">
|
<div className='tw-grid tw-grid-cols-1 md:tw-grid-cols-2 tw-gap-6'>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="groupType" className="tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1">
|
<label
|
||||||
Gruppenart:
|
htmlFor='groupType'
|
||||||
</label>
|
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||||
<ComboBoxInput
|
>
|
||||||
id="groupType"
|
Gruppenart:
|
||||||
options={typeMapping}
|
</label>
|
||||||
value={state.groupType}
|
<ComboBoxInput
|
||||||
onValueChange={(v) => setState(prevState => ({
|
id='groupType'
|
||||||
...prevState,
|
options={typeMapping}
|
||||||
groupType: v
|
value={state.groupType}
|
||||||
}))}
|
onValueChange={(v) =>
|
||||||
/>
|
setState((prevState) => ({
|
||||||
</div>
|
...prevState,
|
||||||
<div>
|
groupType: v,
|
||||||
<label htmlFor="status" className="tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1">
|
}))
|
||||||
Gruppenstatus:
|
}
|
||||||
</label>
|
/>
|
||||||
<ComboBoxInput
|
|
||||||
id="status"
|
|
||||||
options={statusMapping}
|
|
||||||
value={state.status}
|
|
||||||
onValueChange={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
status: v
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="email" className="tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1">
|
|
||||||
Email-Adresse (Kontakt):
|
|
||||||
</label>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Email"
|
|
||||||
defaultValue={state.contact}
|
|
||||||
updateFormValue={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
contact: v
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="telephone" className="tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1">
|
|
||||||
Telefonnummer (Kontakt):
|
|
||||||
</label>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Telefonnummer"
|
|
||||||
defaultValue={state.telephone}
|
|
||||||
updateFormValue={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
telephone: v
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="nextAppointment" className="tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1">
|
|
||||||
Nächste Termine:
|
|
||||||
</label>
|
|
||||||
<TextAreaInput
|
|
||||||
placeholder="Nächste Termine"
|
|
||||||
defaultValue={state.nextAppointment}
|
|
||||||
updateFormValue={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
nextAppointment: v
|
|
||||||
}))}
|
|
||||||
inputStyle="tw-h-24"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="description" className="tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1">
|
|
||||||
Gruppenbeschreibung:
|
|
||||||
</label>
|
|
||||||
<TextAreaInput
|
|
||||||
placeholder="Beschreibung"
|
|
||||||
defaultValue={state.text || ''}
|
|
||||||
updateFormValue={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
text: v
|
|
||||||
}))}
|
|
||||||
inputStyle="tw-h-48"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor='status'
|
||||||
|
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||||
|
>
|
||||||
|
Gruppenstatus:
|
||||||
|
</label>
|
||||||
|
<ComboBoxInput
|
||||||
|
id='status'
|
||||||
|
options={statusMapping}
|
||||||
|
value={state.status}
|
||||||
|
onValueChange={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
status: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor='email'
|
||||||
|
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||||
|
>
|
||||||
|
Email-Adresse (Kontakt):
|
||||||
|
</label>
|
||||||
|
<TextInput
|
||||||
|
placeholder='Email'
|
||||||
|
defaultValue={state.contact}
|
||||||
|
updateFormValue={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
contact: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor='telephone'
|
||||||
|
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||||
|
>
|
||||||
|
Telefonnummer (Kontakt):
|
||||||
|
</label>
|
||||||
|
<TextInput
|
||||||
|
placeholder='Telefonnummer'
|
||||||
|
defaultValue={state.telephone}
|
||||||
|
updateFormValue={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
telephone: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor='nextAppointment'
|
||||||
|
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||||
|
>
|
||||||
|
Nächste Termine:
|
||||||
|
</label>
|
||||||
|
<TextAreaInput
|
||||||
|
placeholder='Nächste Termine'
|
||||||
|
defaultValue={state.nextAppointment}
|
||||||
|
updateFormValue={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
nextAppointment: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
inputStyle='tw-h-24'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor='description'
|
||||||
|
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||||
|
>
|
||||||
|
Gruppenbeschreibung:
|
||||||
|
</label>
|
||||||
|
<TextAreaInput
|
||||||
|
placeholder='Beschreibung'
|
||||||
|
defaultValue={state.text || ''}
|
||||||
|
updateFormValue={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
text: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
inputStyle='tw-h-48'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,19 +5,23 @@ 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 [profileOwner, 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(
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
items.find(
|
||||||
|
(i) => i.user_created?.id === item.user_created?.id && i.layer?.itemType.name === userType,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// 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'
|
||||||
@ -25,47 +29,50 @@ export const OnepagerView = ({ item, userType }:{item: Item, userType: string})
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='tw-h-full tw-overflow-y-auto fade'>
|
<div className='tw-h-full tw-overflow-y-auto fade'>
|
||||||
<div className="tw-px-6">
|
<div className='tw-px-6'>
|
||||||
<ProfileSubHeader
|
<ProfileSubHeader
|
||||||
type={groupTypeText}
|
type={groupTypeText}
|
||||||
status={item.status}
|
status={item.status}
|
||||||
url={`https://www.wuerdekompass.org/aktivitaeten/gruppensuche/#/gruppe/${item.slug}`}
|
url={`https://www.wuerdekompass.org/aktivitaeten/gruppensuche/#/gruppe/${item.slug}`}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{item.user_created.first_name && (
|
{item.user_created.first_name && (
|
||||||
<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} />
|
<ContactInfo
|
||||||
)}
|
link={`/item/${profileOwner?.id}`}
|
||||||
|
name={profileOwner?.name ? profileOwner.name : item.user_created.first_name}
|
||||||
{/* Description Section */}
|
avatar={profileOwner?.image ? profileOwner.image : item.user_created.avatar}
|
||||||
<div className="tw-my-10 tw-mt-2 tw-px-6 tw-text-sm ">
|
email={item.contact}
|
||||||
|
telephone={item.telephone}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Description Section */}
|
||||||
|
<div className='tw-my-10 tw-mt-2 tw-px-6 tw-text-sm '>
|
||||||
<TextView rawText={item.text || 'Keine Beschreibung vorhanden'} />
|
<TextView rawText={item.text || 'Keine Beschreibung vorhanden'} />
|
||||||
</div>
|
</div>
|
||||||
|
{/* Next Appointment Section */}
|
||||||
{/* Next Appointment Section */}
|
{item.next_appointment && (
|
||||||
{item.next_appointment && (
|
<div className='tw-my-10 tw-px-6'>
|
||||||
<div className="tw-my-10 tw-px-6">
|
<h2 className='tw-text-lg tw-font-semibold'>Nächste Termine</h2>
|
||||||
<h2 className="tw-text-lg tw-font-semibold">Nächste Termine</h2>
|
<div className='tw-mt-2 tw-text-sm'>
|
||||||
<div className="tw-mt-2 tw-text-sm">
|
<TextView rawText={item.next_appointment} />
|
||||||
<TextView rawText={item.next_appointment} />
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)};
|
)}
|
||||||
|
;{/* Relations Section */}
|
||||||
{/* Relations Section */}
|
{/* {d.relations && ( */}
|
||||||
{/* {d.relations && ( */}
|
{/* <div className="tw-my-10 tw-px-6"> */}
|
||||||
{/* <div className="tw-my-10 tw-px-6"> */}
|
{/* <h2 className="tw-text-lg tw-font-semibold tw-mb-4">Projekte</h2> */}
|
||||||
{/* <h2 className="tw-text-lg tw-font-semibold tw-mb-4">Projekte</h2> */}
|
{/* {d.relations.map((project, index) => ( */}
|
||||||
{/* {d.relations.map((project, index) => ( */}
|
{/* <RelationCard */}
|
||||||
{/* <RelationCard */}
|
{/* key={index} */}
|
||||||
{/* key={index} */}
|
{/* title={project.title} */}
|
||||||
{/* title={project.title} */}
|
{/* description={project.description} */}
|
||||||
{/* description={project.description} */}
|
{/* imageSrc={project.imageSrc} */}
|
||||||
{/* imageSrc={project.imageSrc} */}
|
{/* /> */}
|
||||||
{/* /> */}
|
{/* ))} */}
|
||||||
{/* ))} */}
|
{/* </div> */}
|
||||||
{/* </div> */}
|
{/* )} */}
|
||||||
{/* )} */}
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,15 @@ import { TextAreaInput } from '../../Input'
|
|||||||
export const SimpleForm = ({ state, setState }) => {
|
export const SimpleForm = ({ state, setState }) => {
|
||||||
return (
|
return (
|
||||||
<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) =>
|
||||||
...prevState,
|
setState((prevState) => ({
|
||||||
text: v
|
...prevState,
|
||||||
}))}
|
text: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
containerStyle='tw-mt-8 tw-h-full'
|
containerStyle='tw-mt-8 tw-h-full'
|
||||||
inputStyle='tw-h-full'
|
inputStyle='tw-h-full'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import * as React from 'react'
|
|||||||
import { TextView } from '../../Map'
|
import { TextView } from '../../Map'
|
||||||
import { Item } from '../../../types'
|
import { Item } from '../../../types'
|
||||||
|
|
||||||
export const SimpleView = ({ item }:{item: Item}) => {
|
export const SimpleView = ({ item }: { item: Item }) => {
|
||||||
return (
|
return (
|
||||||
<div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'>
|
<div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'>
|
||||||
<TextView item={item} />
|
<TextView item={item} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,22 +9,34 @@ 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(
|
||||||
setActiveTab(id)
|
(id: number) => {
|
||||||
|
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)
|
||||||
@ -34,90 +46,168 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
|
|||||||
}, [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
|
||||||
<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">
|
type='radio'
|
||||||
<div className={`tw-flex tw-flex-col tw-h-full ${item.layer.itemType.show_start_end_input && 'tw-pt-4'}`}>
|
name='my_tabs_2'
|
||||||
{item.layer.itemType.show_start_end_input &&
|
role='tab'
|
||||||
<PopupStartEndInput
|
className={'tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
|
||||||
item={item}
|
aria-label='Info'
|
||||||
showLabels={false}
|
checked={activeTab === 1 && true}
|
||||||
updateEndValue={(e) => setState(prevState => ({
|
onChange={() => updateActiveTab(1)}
|
||||||
...prevState,
|
/>
|
||||||
end: e
|
<div
|
||||||
}))}
|
role='tabpanel'
|
||||||
updateStartValue={(s) => setState(prevState => ({
|
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'
|
||||||
...prevState,
|
>
|
||||||
start: s
|
<div
|
||||||
}))}></PopupStartEndInput>
|
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 && (
|
||||||
|
<PopupStartEndInput
|
||||||
|
item={item}
|
||||||
|
showLabels={false}
|
||||||
|
updateEndValue={(e) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
end: e,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
updateStartValue={(s) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
start: s,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
></PopupStartEndInput>
|
||||||
|
)}
|
||||||
|
|
||||||
<TextAreaInput placeholder="about ..."
|
<TextAreaInput
|
||||||
defaultValue={item?.text ? item.text : ''}
|
placeholder='about ...'
|
||||||
updateFormValue={(v) => setState(prevState => ({
|
defaultValue={item?.text ? item.text : ''}
|
||||||
...prevState,
|
updateFormValue={(v) =>
|
||||||
text: v
|
setState((prevState) => ({
|
||||||
}))}
|
...prevState,
|
||||||
containerStyle='tw-grow'
|
text: v,
|
||||||
inputStyle={`tw-h-full ${!item.layer.itemType.show_start_end_input && 'tw-border-t-0 tw-rounded-tl-none'}`} />
|
}))
|
||||||
<div>
|
|
||||||
<TextAreaInput
|
|
||||||
placeholder="contact info ..."
|
|
||||||
defaultValue={state.contact || ''}
|
|
||||||
updateFormValue={(c) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
contact: c
|
|
||||||
}))}
|
|
||||||
inputStyle="tw-h-24"
|
|
||||||
containerStyle="tw-pt-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{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)} />
|
|
||||||
<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-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'>
|
|
||||||
<TagsWidget defaultTags={state.offers} onUpdate={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
offers: v
|
|
||||||
}))} placeholder="enter your offers" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto' />
|
|
||||||
</div>
|
|
||||||
<div className='tw-w-full tw-h-[calc(50%-1.5em)]'>
|
|
||||||
<TagsWidget defaultTags={state.needs} onUpdate={(v) => setState(prevState => ({
|
|
||||||
...prevState,
|
|
||||||
needs: v
|
|
||||||
}))} placeholder="enter your needs" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
{item.layer?.itemType.relations &&
|
containerStyle='tw-grow'
|
||||||
<>
|
inputStyle={`tw-h-full ${!item.layer.itemType.show_start_end_input && 'tw-border-t-0 tw-rounded-tl-none'}`}
|
||||||
<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>
|
||||||
<div className='tw-h-full'>
|
<TextAreaInput
|
||||||
<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'>
|
placeholder='contact info ...'
|
||||||
{state.relations && state.relations.map(i =>
|
defaultValue={state.contact || ''}
|
||||||
|
updateFormValue={(c) =>
|
||||||
<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)}>
|
setState((prevState) => ({
|
||||||
<LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={(id) => unlinkItem(id, item, updateItem)} loading={loading} />
|
...prevState,
|
||||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
contact: c,
|
||||||
<TextView truncate item={i} />
|
}))
|
||||||
</div>
|
}
|
||||||
</div>
|
inputStyle='tw-h-24'
|
||||||
)}
|
containerStyle='tw-pt-4'
|
||||||
{updatePermission && <ActionButton customStyle="!tw-bottom-24" collection="items" item={item} existingRelations={state.relations} triggerItemSelected={(id) => linkItem(id, item, updateItem)} colorField={item.layer.itemColorField}></ActionButton>}
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{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)}
|
||||||
|
/>
|
||||||
|
<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-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'>
|
||||||
|
<TagsWidget
|
||||||
|
defaultTags={state.offers}
|
||||||
|
onUpdate={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
offers: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
placeholder='enter your offers'
|
||||||
|
containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='tw-w-full tw-h-[calc(50%-1.5em)]'>
|
||||||
|
<TagsWidget
|
||||||
|
defaultTags={state.needs}
|
||||||
|
onUpdate={(v) =>
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
needs: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
placeholder='enter your needs'
|
||||||
|
containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{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)}
|
||||||
|
/>
|
||||||
|
<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-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) => (
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||||
|
<TextView truncate item={i} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{updatePermission && (
|
||||||
|
<ActionButton
|
||||||
|
customStyle='!tw-bottom-24'
|
||||||
|
collection='items'
|
||||||
|
item={item}
|
||||||
|
existingRelations={state.relations}
|
||||||
|
triggerItemSelected={(id) => linkItem(id, item, updateItem)}
|
||||||
|
colorField={item.layer.itemColorField}
|
||||||
|
></ActionButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,29 @@ import { useAssetApi } from '../../AppShell/hooks/useAssets'
|
|||||||
import { timeAgo } from '../../../Utils/TimeAgo'
|
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 addFilterTag = useAddFilterTag()
|
||||||
const [activeTab, setActiveTab] = useState<number>()
|
const [activeTab, setActiveTab] = useState<number>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -21,88 +43,122 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
|
|||||||
const items = useItems()
|
const items = useItems()
|
||||||
const assetsApi = useAssetApi()
|
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(
|
||||||
setActiveTab(id)
|
(id: number) => {
|
||||||
|
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
|
||||||
className={'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
|
type='radio'
|
||||||
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 1 ? '📝' : '📝\u00A0Info'}`} checked={activeTab === 1 && true}
|
name='my_tabs_2'
|
||||||
onChange={() => updateActiveTab(1)} />
|
role='tab'
|
||||||
<div role="tabpanel"
|
className={
|
||||||
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">
|
'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
||||||
{item.layer?.itemType.show_start_end &&
|
|
||||||
<div className='tw-max-w-xs'><StartEndView item={item}></StartEndView></div>
|
|
||||||
}
|
}
|
||||||
|
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 1 ? '📝' : '📝\u00A0Info'}`}
|
||||||
|
checked={activeTab === 1 && true}
|
||||||
|
onChange={() => updateActiveTab(1)}
|
||||||
|
/>
|
||||||
|
<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'
|
||||||
|
>
|
||||||
|
{item.layer?.itemType.show_start_end && (
|
||||||
|
<div className='tw-max-w-xs'>
|
||||||
|
<StartEndView item={item}></StartEndView>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<TextView item={item} />
|
<TextView item={item} />
|
||||||
<div className='tw-h-4'></div>
|
<div className='tw-h-4'></div>
|
||||||
<TextView item={item} itemTextField='contact' />
|
<TextView item={item} itemTextField='contact' />
|
||||||
</div>
|
</div>
|
||||||
{item.layer?.itemType.questlog &&
|
{item.layer?.itemType.questlog && (
|
||||||
<>
|
<>
|
||||||
<input type="radio" name="my_tabs_2" role="tab"
|
<input
|
||||||
className={'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
|
type='radio'
|
||||||
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Credibility'}`} checked={activeTab === 2 && true}
|
name='my_tabs_2'
|
||||||
onChange={() => updateActiveTab(2)} />
|
role='tab'
|
||||||
<div role="tabpanel"
|
className={
|
||||||
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">
|
'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
||||||
<table className="sm:tw-table-sm md:tw-table-md">
|
}
|
||||||
|
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Credibility'}`}
|
||||||
|
checked={activeTab === 2 && true}
|
||||||
|
onChange={() => updateActiveTab(2)}
|
||||||
|
/>
|
||||||
|
<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'
|
||||||
|
>
|
||||||
|
<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}>
|
||||||
<td>
|
<td>
|
||||||
<div className={`tw-cursor-pointer tw-text-3xl tw-mask tw-mask-${a.shape} tw-p-3 tw-mr-2 tw-shadow-xl tw-bg-[${a.color}]`}>
|
<div
|
||||||
|
className={`tw-cursor-pointer tw-text-3xl tw-mask tw-mask-${a.shape} tw-p-3 tw-mr-2 tw-shadow-xl tw-bg-[${a.color}]`}
|
||||||
|
>
|
||||||
{a.emoji}
|
{a.emoji}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<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'>
|
||||||
<img
|
<img
|
||||||
src={assetsApi.url + getUserProfile(a.user_created.id)?.image}
|
src={assetsApi.url + getUserProfile(a.user_created.id)?.image}
|
||||||
alt="Avatar Tailwind CSS Component"
|
alt='Avatar Tailwind CSS Component'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-bold">{getUserProfile(a.user_created.id)?.name}</div>
|
<div className='font-bold'>
|
||||||
<div className="tw-text-xs opacity-50 tw-text-zinc-500">{timeAgo(a.date_created)}</div>
|
{getUserProfile(a.user_created.id)?.name}
|
||||||
|
</div>
|
||||||
|
<div className='tw-text-xs opacity-50 tw-text-zinc-500'>
|
||||||
|
{timeAgo(a.date_created)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -111,73 +167,111 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
{item.layer?.itemType.offers_and_needs &&
|
{item.layer?.itemType.offers_and_needs && (
|
||||||
|
|
||||||
<>
|
<>
|
||||||
|
<input
|
||||||
<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)} />
|
type='radio'
|
||||||
<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" >
|
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 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'>
|
{offers.map((o) => (
|
||||||
{
|
<TagView
|
||||||
offers.map(o => <TagView key={o?.id} tag={o} onClick={() => {
|
key={o?.id}
|
||||||
|
tag={o}
|
||||||
|
onClick={() => {
|
||||||
addFilterTag(o)
|
addFilterTag(o)
|
||||||
}} />)
|
}}
|
||||||
}
|
/>
|
||||||
</div>
|
))}
|
||||||
</div>
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
{
|
|
||||||
needs.length > 0
|
|
||||||
? <div className='tw-col-span-1'>
|
|
||||||
<h3 className='-tw-mb-2 tw-col-span-1'>Needs</h3>
|
|
||||||
< div className='tw-flex tw-flex-wrap tw-mb-4'>
|
|
||||||
{
|
|
||||||
needs.map(n => <TagView key={n?.id} tag={n} onClick={() => addFilterTag(n)} />)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
{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)} />
|
|
||||||
<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-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 =>
|
|
||||||
|
|
||||||
<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} />
|
|
||||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
|
||||||
<TextView truncate item={i} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{needs.length > 0 ? (
|
||||||
|
<div className='tw-col-span-1'>
|
||||||
|
<h3 className='-tw-mb-2 tw-col-span-1'>Needs</h3>
|
||||||
|
<div className='tw-flex tw-flex-wrap tw-mb-4'>
|
||||||
|
{needs.map((n) => (
|
||||||
|
<TagView key={n?.id} tag={n} onClick={() => addFilterTag(n)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
)}
|
)}
|
||||||
{updatePermission && <ActionButton collection="items" item={item} existingRelations={relations} triggerItemSelected={linkItem} colorField={item.layer.itemColorField}></ActionButton>}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
|
|
||||||
|
{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)}
|
||||||
|
/>
|
||||||
|
<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-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) => (
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||||
|
<TextView truncate item={i} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{updatePermission && (
|
||||||
|
<ActionButton
|
||||||
|
collection='items'
|
||||||
|
item={item}
|
||||||
|
existingRelations={relations}
|
||||||
|
triggerItemSelected={linkItem}
|
||||||
|
colorField={item.layer.itemColorField}
|
||||||
|
></ActionButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ 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>('')
|
||||||
@ -27,38 +27,59 @@ export function UserSettings () {
|
|||||||
const onUpdateUser = () => {
|
const onUpdateUser = () => {
|
||||||
let changedUser = {} as UserItem
|
let changedUser = {} as UserItem
|
||||||
|
|
||||||
changedUser = { id, email, ...passwordChanged && { password } }
|
changedUser = { id, email, ...(passwordChanged && { password }) }
|
||||||
|
|
||||||
toast.promise(
|
toast
|
||||||
|
.promise(updateUser(changedUser), {
|
||||||
updateUser(changedUser),
|
|
||||||
{
|
|
||||||
pending: 'updating Profile ...',
|
pending: 'updating Profile ...',
|
||||||
success: 'Profile updated',
|
success: 'Profile updated',
|
||||||
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
|
||||||
<div className={'tw-text-xl tw-font-semibold'}>Settings</div>
|
backdrop
|
||||||
<div className="tw-divider tw-mt-2"></div>
|
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-grid tw-grid-cols-1 tw-gap-6">
|
>
|
||||||
<TextInput type='email' placeholder="new E-Mail" defaultValue={user?.email ? user.email : ''} updateFormValue={(v) => setEmail(v)} />
|
<div className={'tw-text-xl tw-font-semibold'}>Settings</div>
|
||||||
<TextInput type='password' placeholder="new Password" defaultValue={user?.password ? user.password : ''} updateFormValue={(v) => {
|
<div className='tw-divider tw-mt-2'></div>
|
||||||
setPassword(v)
|
<div className='tw-grid tw-grid-cols-1 tw-gap-6'>
|
||||||
setPasswordChanged(true)
|
<TextInput
|
||||||
}} />
|
type='email'
|
||||||
{/* <ToogleInput updateType="syncData" labelTitle="Sync Data" defaultValue={true} updateFormValue={updateFormValue}/> */}
|
placeholder='new E-Mail'
|
||||||
</div>
|
defaultValue={user?.email ? user.email : ''}
|
||||||
|
updateFormValue={(v) => setEmail(v)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type='password'
|
||||||
|
placeholder='new Password'
|
||||||
|
defaultValue={user?.password ? user.password : ''}
|
||||||
|
updateFormValue={(v) => {
|
||||||
|
setPassword(v)
|
||||||
|
setPasswordChanged(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <ToogleInput updateType="syncData" labelTitle="Sync Data" defaultValue={true} updateFormValue={updateFormValue}/> */}
|
||||||
|
</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
|
||||||
</MapOverlayPage>
|
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,21 @@ 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) => {
|
||||||
@ -16,15 +30,21 @@ export const submitNewItem = async (evt: any, type: string, item, user, setLoadi
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
formItem.text &&
|
||||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
formItem.text
|
||||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
.toLocaleLowerCase()
|
||||||
}
|
.match(hashTagRegex)
|
||||||
return null
|
?.map((tag) => {
|
||||||
})
|
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
|
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
const uuid = crypto.randomUUID()
|
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 {
|
||||||
@ -62,7 +82,7 @@ export const linkItem = async (id: string, item, updateItem) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const unlinkItem = async (id: string, item, updateItem) => {
|
export const unlinkItem = async (id: string, item, updateItem) => {
|
||||||
const newRelations = 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: newRelations }
|
const updatedItem = { id: item.id, relations: newRelations }
|
||||||
|
|
||||||
let success = false
|
let success = false
|
||||||
@ -78,7 +98,14 @@ export const unlinkItem = async (id: string, item, updateItem) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -99,26 +126,37 @@ export const handleDelete = async (event: React.MouseEvent<HTMLElement>, item, s
|
|||||||
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 offerUpdates: 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 && offerUpdates.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 && offerUpdates.push({ items_id: item?.id, tags_id: o.id })
|
!existingOffer && offerUpdates.push({ items_id: item?.id, tags_id: o.id })
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
const needsUpdates: 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 && needsUpdates.push(existingNeed.id)
|
existingNeed && needsUpdates.push(existingNeed.id)
|
||||||
!existingNeed && needsUpdates.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
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -128,30 +166,30 @@ export const onUpdateItem = async (state, item, tags, addTag, setLoading, naviga
|
|||||||
name: state.name,
|
name: state.name,
|
||||||
subname: state.subname,
|
subname: state.subname,
|
||||||
text: state.text,
|
text: state.text,
|
||||||
...state.color && { color: state.color },
|
...(state.color && { color: state.color }),
|
||||||
position: item.position,
|
position: item.position,
|
||||||
...state.groupType && { group_type: state.groupType },
|
...(state.groupType && { group_type: state.groupType }),
|
||||||
...state.status && { status: state.status },
|
...(state.status && { status: state.status }),
|
||||||
contact: state.contact,
|
contact: state.contact,
|
||||||
telephone: state.telephone,
|
telephone: state.telephone,
|
||||||
...state.end && { end: state.end },
|
...(state.end && { end: state.end }),
|
||||||
...state.start && { start: state.start },
|
...(state.start && { start: state.start }),
|
||||||
...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: offerUpdates },
|
...(state.offers.length > 0 && { offers: offerUpdates }),
|
||||||
...state.needs.length > 0 && { needs: needsUpdates }
|
...(state.needs.length > 0 && { needs: needsUpdates }),
|
||||||
}
|
}
|
||||||
|
|
||||||
const offersState: Array<any> = []
|
const offersState: Array<any> = []
|
||||||
const needsState: Array<any> = []
|
const needsState: Array<any> = []
|
||||||
|
|
||||||
state.offers.map(o => {
|
state.offers.map((o) => {
|
||||||
offersState.push({ items_id: item?.id, tags_id: o.id })
|
offersState.push({ items_id: item?.id, tags_id: o.id })
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
state.needs.map(n => {
|
state.needs.map((n) => {
|
||||||
needsState.push({ items_id: item?.id, tags_id: n.id })
|
needsState.push({ items_id: item?.id, tags_id: n.id })
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
@ -160,52 +198,69 @@ export const onUpdateItem = async (state, item, tags, addTag, setLoading, naviga
|
|||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
await state.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
await state.text
|
||||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
.toLocaleLowerCase()
|
||||||
addTag({ id: crypto.randomUUID(), name: encodeTag(tag.slice(1).toLocaleLowerCase()), color: randomColor() })
|
.match(hashTagRegex)
|
||||||
}
|
?.map((tag) => {
|
||||||
return null
|
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
})
|
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 &&
|
||||||
item?.layer?.api?.updateItem(changedItem),
|
toast
|
||||||
{
|
.promise(item?.layer?.api?.updateItem(changedItem), {
|
||||||
pending: 'updating Item ...',
|
pending: 'updating Item ...',
|
||||||
success: 'Item updated',
|
success: 'Item updated',
|
||||||
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 {
|
} else {
|
||||||
item.new = false
|
item.new = false
|
||||||
item.layer?.api?.createItem && toast.promise(
|
item.layer?.api?.createItem &&
|
||||||
item.layer?.api?.createItem(changedItem),
|
toast
|
||||||
{
|
.promise(item.layer?.api?.createItem(changedItem), {
|
||||||
pending: 'updating Item ...',
|
pending: 'updating Item ...',
|
||||||
success: 'Item updated',
|
success: 'Item updated',
|
||||||
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(
|
||||||
.then(() => {
|
() =>
|
||||||
setLoading(false)
|
item &&
|
||||||
navigate(`/${params && '?' + params}`)
|
addItem({
|
||||||
})
|
...item,
|
||||||
|
...changedItem,
|
||||||
|
layer: item.layer,
|
||||||
|
user_created: user,
|
||||||
|
type: item.layer?.itemType,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
setLoading(false)
|
||||||
|
navigate(`/${params && '?' + params}`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { useRef, useState, useEffect } from 'react'
|
|||||||
import { Item, ItemsApi } from '../../types'
|
import { Item, ItemsApi } from '../../types'
|
||||||
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>>()
|
||||||
@ -17,7 +17,7 @@ export const AttestationForm = ({ api }:{api?:ItemsApi<any>}) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(location.search)
|
const params = new URLSearchParams(location.search)
|
||||||
const toUserIds = params.get('to')
|
const toUserIds = params.get('to')
|
||||||
setUsers(items.filter(i => toUserIds?.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])
|
||||||
|
|
||||||
@ -36,27 +36,40 @@ export const AttestationForm = ({ api }:{api?:ItemsApi<any>}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 &&
|
||||||
api.createItem({
|
toast
|
||||||
text: inputValue,
|
.promise(
|
||||||
emoji: selectedEmoji,
|
api.createItem({
|
||||||
color: selectedColor,
|
text: inputValue,
|
||||||
shape: selectedShape,
|
emoji: selectedEmoji,
|
||||||
to
|
color: selectedColor,
|
||||||
}), {
|
shape: selectedShape,
|
||||||
pending: 'creating attestation ...',
|
to,
|
||||||
success: 'Attestation created',
|
}),
|
||||||
error: {
|
{
|
||||||
render ({ data }) {
|
pending: 'creating attestation ...',
|
||||||
return `${data}`
|
success: 'Attestation created',
|
||||||
}
|
error: {
|
||||||
}
|
render({ 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')
|
||||||
@ -64,42 +77,61 @@ export const AttestationForm = ({ api }:{api?:ItemsApi<any>}) => {
|
|||||||
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'>
|
||||||
<div className='tw-text-center tw-text-xl tw-font-bold'>Gratitude</div>
|
<div className='tw-text-center tw-text-xl tw-font-bold'>Gratitude</div>
|
||||||
<div className='tw-text-center tw-text-base tw-text-gray-400'>to</div>
|
<div className='tw-text-center tw-text-base tw-text-gray-400'>to</div>
|
||||||
<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 &&
|
||||||
<div key={k} className="tw-flex tw-items-center tw-space-x-3 tw-mx-2 tw-my-1">
|
users.map(
|
||||||
{u.image
|
(u, k) => (
|
||||||
? <div className="tw-avatar">
|
<div key={k} className='tw-flex tw-items-center tw-space-x-3 tw-mx-2 tw-my-1'>
|
||||||
<div className="tw-mask tw-mask-circle tw-w-8 tw-h-8">
|
{u.image ? (
|
||||||
<img src={assetsApi.url + u.image + '?width=40&heigth=40'} alt="Avatar" />
|
<div className='tw-avatar'>
|
||||||
</div>
|
<div className='tw-mask tw-mask-circle tw-w-8 tw-h-8'>
|
||||||
</div>
|
<img src={assetsApi.url + u.image + '?width=40&heigth=40'} alt='Avatar' />
|
||||||
: <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 className="tw-font-bold">{u.name}</div>
|
|
||||||
</div>
|
|
||||||
</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>
|
||||||
|
<div className='tw-font-bold'>{u.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
', ',
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='tw-w-full'>
|
<div className='tw-w-full'>
|
||||||
<div className='tw-flex tw-justify-center tw-items-center'>
|
<div className='tw-flex tw-justify-center tw-items-center'>
|
||||||
<div className=' tw-flex tw-justify-center tw-items-center tw-w-28 tw-h-28 tw-m-4'>
|
<div className=' tw-flex tw-justify-center tw-items-center tw-w-28 tw-h-28 tw-m-4'>
|
||||||
<EmojiPicker selectedEmoji={selectedEmoji} selectedColor={selectedColor} selectedShape={selectedShape} setSelectedEmoji={setSelectedEmoji} setSelectedColor={setSelectedColor} setSelectedShape={setSelectedShape} />
|
<EmojiPicker
|
||||||
</div>
|
selectedEmoji={selectedEmoji}
|
||||||
</div>
|
selectedColor={selectedColor}
|
||||||
<div className='tw-flex tw-justify-center tw-items-center'>
|
selectedShape={selectedShape}
|
||||||
<input ref={inputRef}
|
setSelectedEmoji={setSelectedEmoji}
|
||||||
value={inputValue}
|
setSelectedColor={setSelectedColor}
|
||||||
onChange={handleChange}
|
setSelectedShape={setSelectedShape}
|
||||||
type="text"
|
/>
|
||||||
placeholder="... and say some words"
|
</div>
|
||||||
className="tw-input tw-min-w-0 tw-w-fit tw-resize-none tw-overflow-hidden tw-text-center " />
|
</div>
|
||||||
</div>
|
<div className='tw-flex tw-justify-center tw-items-center'>
|
||||||
</div>
|
<input
|
||||||
<div className='tw-w-full tw-grid tw-mt-4'><button onClick={sendAttestation} className="tw-btn tw-place-self-center tw-px-8">Next</button></div>
|
ref={inputRef}
|
||||||
</MapOverlayPage>
|
value={inputValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
type='text'
|
||||||
|
placeholder='... and say some words'
|
||||||
|
className='tw-input tw-min-w-0 tw-w-fit tw-resize-none tw-overflow-hidden tw-text-center '
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='tw-w-full tw-grid tw-mt-4'>
|
||||||
|
<button onClick={sendAttestation} className='tw-btn tw-place-self-center tw-px-8'>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,34 @@ 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: string,
|
title,
|
||||||
hideTitle?: boolean,
|
hideTitle,
|
||||||
children?: React.ReactNode,
|
children,
|
||||||
parents?: Array<{name: string, path: string}>
|
parents,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
hideTitle?: boolean
|
||||||
|
children?: React.ReactNode
|
||||||
|
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 '>
|
||||||
<div className="tw-text-sm tw-breadcrumbs">
|
<div className='tw-text-sm tw-breadcrumbs'>
|
||||||
<ul>
|
<ul>
|
||||||
<li><Link to={'/'} >Home</Link></li>
|
<li>
|
||||||
{parents?.map((b, i) => <li key={i}><Link to={b.path} >{b.name}</Link></li>)}
|
<Link to={'/'}>Home</Link>
|
||||||
|
</li>
|
||||||
|
{parents?.map((b, i) => (
|
||||||
|
<li key={i}>
|
||||||
|
<Link to={b.path}>{b.name}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
<li>{title}</li>
|
<li>{title}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<TitleCard hideTitle={hideTitle} title={title} topMargin="tw-my-2" className=" tw-mb-4">
|
<TitleCard hideTitle={hideTitle} title={title} topMargin='tw-my-2' className=' tw-mb-4'>
|
||||||
{children}
|
{children}
|
||||||
</TitleCard>
|
</TitleCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
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(() => {
|
||||||
@ -9,7 +17,7 @@ export const CircleLayout = ({ items, radius, fontSize } : {items: any, radius:
|
|||||||
|
|
||||||
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)
|
||||||
@ -17,13 +25,16 @@ export const CircleLayout = ({ items, radius, fontSize } : {items: any, radius:
|
|||||||
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 }}>
|
<div key={item} className='tw-absolute' style={{ fontSize }}>
|
||||||
{item}
|
{item}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -5,14 +5,24 @@ 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
|
>
|
||||||
? <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>
|
{infoExpanded ? (
|
||||||
: <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-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'}
|
||||||
<div className='tw-grow '></div>
|
onClick={() => setInfoExpanded(false)}
|
||||||
</div>
|
>{`${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>
|
||||||
|
)}
|
||||||
|
<div className='tw-grow '></div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,23 +4,18 @@ 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.right && e.clientY > r.top && e.clientY < r.bottom
|
||||||
e.clientX > r.left &&
|
|
||||||
e.clientX < r.right &&
|
|
||||||
e.clientY > r.top &&
|
|
||||||
e.clientY < r.bottom
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string
|
||||||
isOpened: boolean;
|
isOpened: boolean
|
||||||
onClose: () => void;
|
onClose: () => void
|
||||||
children: React.ReactNode;
|
children: React.ReactNode
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean
|
||||||
closeOnClickOutside?:boolean;
|
closeOnClickOutside?: boolean
|
||||||
className?: string;
|
className?: string
|
||||||
};
|
}
|
||||||
|
|
||||||
const DialogModal = ({
|
const DialogModal = ({
|
||||||
title,
|
title,
|
||||||
@ -29,7 +24,7 @@ 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)
|
||||||
|
|
||||||
@ -47,26 +42,31 @@ const DialogModal = ({
|
|||||||
|
|
||||||
if (isOpened) {
|
if (isOpened) {
|
||||||
return (
|
return (
|
||||||
<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`}
|
<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}
|
||||||
|
// eslint-disable-next-line react/no-unknown-property
|
||||||
|
onCancel={onClose}
|
||||||
|
onClick={(e) =>
|
||||||
|
ref.current && !isClickInsideRectangle(e, ref.current) && closeOnClickOutside && onClose()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='tw-card-body tw-p-2'>
|
||||||
|
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>{title}</h2>
|
||||||
|
|
||||||
ref={ref}
|
{children}
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
{showCloseButton && (
|
||||||
onCancel={onClose}
|
<button
|
||||||
onClick={(e) =>
|
className='tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2'
|
||||||
ref.current && !isClickInsideRectangle(e, ref.current) && closeOnClickOutside && onClose()
|
onClick={onClose}
|
||||||
}
|
>
|
||||||
>
|
✕
|
||||||
<div className="tw-card-body tw-p-2">
|
</button>
|
||||||
|
)}
|
||||||
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>{title}</h2>
|
</div>
|
||||||
|
</dialog>
|
||||||
{children}
|
|
||||||
{showCloseButton && <button className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2" onClick={onClose}>✕</button>}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
)
|
)
|
||||||
} else return (<></>)
|
} else return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DialogModal
|
export default DialogModal
|
||||||
|
|||||||
@ -1,20 +1,70 @@
|
|||||||
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 = [
|
||||||
'❤️', '🙏', '👍', '🌻', '✨', '☀️',
|
'❤️',
|
||||||
'🔥', '🪵', '💧', '🎶', '🎨', '🍄',
|
'🙏',
|
||||||
'📝', '✉️', '🧩', '💡', '🎓', '💬',
|
'👍',
|
||||||
'🛠', '💻', '🕹', '🖨', '🚐', '🛒',
|
'🌻',
|
||||||
'⚽️', '🧵', '👀', '🌱',
|
'✨',
|
||||||
'🏕', '💪', '🎁', '🏹',
|
'☀️',
|
||||||
'🥕', '🥇', '🥈', '🥉']
|
'🔥',
|
||||||
|
'🪵',
|
||||||
|
'💧',
|
||||||
|
'🎶',
|
||||||
|
'🎨',
|
||||||
|
'🍄',
|
||||||
|
'📝',
|
||||||
|
'✉️',
|
||||||
|
'🧩',
|
||||||
|
'💡',
|
||||||
|
'🎓',
|
||||||
|
'💬',
|
||||||
|
'🛠',
|
||||||
|
'💻',
|
||||||
|
'🕹',
|
||||||
|
'🖨',
|
||||||
|
'🚐',
|
||||||
|
'🛒',
|
||||||
|
'⚽️',
|
||||||
|
'🧵',
|
||||||
|
'👀',
|
||||||
|
'🌱',
|
||||||
|
'🏕',
|
||||||
|
'💪',
|
||||||
|
'🎁',
|
||||||
|
'🏹',
|
||||||
|
'🥕',
|
||||||
|
'🥇',
|
||||||
|
'🥈',
|
||||||
|
'🥉',
|
||||||
|
]
|
||||||
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)
|
||||||
@ -45,7 +95,7 @@ export const EmojiPicker = ({ selectedEmoji, selectedColor, selectedShape, setSe
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="tw-absolute tw-z-3000 tw-top-0 tw-left-1/2 tw-transform tw--translate-x-1/2 tw-mt-12 tw-bg-base-100 tw-rounded-2xl tw-shadow-lg tw-p-2 tw-w-full">
|
<div className='tw-absolute tw-z-3000 tw-top-0 tw-left-1/2 tw-transform tw--translate-x-1/2 tw-mt-12 tw-bg-base-100 tw-rounded-2xl tw-shadow-lg tw-p-2 tw-w-full'>
|
||||||
<div className='tw-grid tw-grid-cols-6 tw-gap-2 tw-pb-2'>
|
<div className='tw-grid tw-grid-cols-6 tw-gap-2 tw-pb-2'>
|
||||||
{emojis.map((emoji) => (
|
{emojis.map((emoji) => (
|
||||||
<button
|
<button
|
||||||
@ -59,28 +109,29 @@ export const EmojiPicker = ({ selectedEmoji, selectedColor, selectedShape, setSe
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className='tw-grid tw-grid-cols-3 tw-gap-2 tw-py-2'>
|
<div className='tw-grid tw-grid-cols-3 tw-gap-2 tw-py-2'>
|
||||||
{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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className='tw-grid tw-grid-cols-6 tw-gap-2 tw-py-2 tw-px-6'>
|
<div className='tw-grid tw-grid-cols-6 tw-gap-2 tw-py-2 tw-px-6'>
|
||||||
{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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,24 +6,47 @@ import { useNavigate } from 'react-router-dom'
|
|||||||
import { getValue } from '../../Utils/GetValue'
|
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
|
||||||
const params = new URLSearchParams(window.location.search)
|
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'
|
||||||
if (windowDimensions.width < 786 && i.position) navigate('/' + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
|
onClick={() => {
|
||||||
else navigate(url + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
|
const params = new URLSearchParams(window.location.search)
|
||||||
}}>
|
if (windowDimensions.width < 786 && i.position)
|
||||||
<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>
|
navigate('/' + 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>
|
||||||
<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>
|
{i.layer?.itemType.show_text && (
|
||||||
}
|
|
||||||
{i.layer?.itemType.show_text &&
|
|
||||||
<TextView truncate item={i} itemTextField={i.layer?.itemTextField} />
|
<TextView truncate item={i} itemTextField={i.layer?.itemTextField} />
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DateUserInfo item={i}></DateUserInfo>
|
<DateUserInfo item={i}></DateUserInfo>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,17 @@ import * as L from 'leaflet'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
export function MapOverlayPage ({ children, className, backdrop, card = true }: { children: React.ReactNode, className?: string, backdrop?: boolean, card?:boolean }) {
|
export function MapOverlayPage({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
backdrop,
|
||||||
|
card = true,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
backdrop?: boolean
|
||||||
|
card?: boolean
|
||||||
|
}) {
|
||||||
const closeScreen = () => {
|
const closeScreen = () => {
|
||||||
navigate(`/${window.location.search ? window.location.search : ''}`)
|
navigate(`/${window.location.search ? window.location.search : ''}`)
|
||||||
}
|
}
|
||||||
@ -25,13 +35,24 @@ export function MapOverlayPage ({ children, className, backdrop, card = true }:
|
|||||||
}, [overlayRef, backdropRef])
|
}, [overlayRef, backdropRef])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && 'tw-z-[2000]'}`}>
|
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && 'tw-z-[2000]'}`}>
|
||||||
<div ref={backdropRef} className={`${backdrop && 'tw-backdrop-brightness-75'} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`} >
|
<div
|
||||||
<div ref={overlayRef} className={`${card && 'tw-card tw-card-body'} tw-shadow-xl tw-bg-base-100 tw-p-6 ${className && className} ${!backdrop && 'tw-z-[2000]'} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
|
ref={backdropRef}
|
||||||
{children}
|
className={`${backdrop && 'tw-backdrop-brightness-75'} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`}
|
||||||
<button className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2" onClick={() => closeScreen()}>✕</button>
|
>
|
||||||
</div>
|
<div
|
||||||
</div>
|
ref={overlayRef}
|
||||||
|
className={`${card && 'tw-card tw-card-body'} tw-shadow-xl tw-bg-base-100 tw-p-6 ${className && className} ${!backdrop && 'tw-z-[2000]'} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<button
|
||||||
|
className='tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2'
|
||||||
|
onClick={() => closeScreen()}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { MapOverlayPage } from './MapOverlayPage'
|
|||||||
import { TagView } from './TagView'
|
import { TagView } from './TagView'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
function groupAndCount (arr) {
|
function groupAndCount(arr) {
|
||||||
const grouped = arr.reduce((acc, obj) => {
|
const grouped = arr.reduce((acc, obj) => {
|
||||||
const found = acc.find(item => JSON.stringify(item.object) === JSON.stringify(obj))
|
const found = acc.find((item) => JSON.stringify(item.object) === JSON.stringify(obj))
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
found.count += 1
|
found.count += 1
|
||||||
@ -34,45 +34,56 @@ export const MarketView = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOffers([])
|
setOffers([])
|
||||||
setNeeds([])
|
setNeeds([])
|
||||||
items.map(i => {
|
items.map((i) => {
|
||||||
i?.layer?.itemOffersField && getValue(i, i.layer.itemOffersField)?.map(o => {
|
i?.layer?.itemOffersField &&
|
||||||
const tag = tags.find(t => t.id === o.tags_id)
|
getValue(i, i.layer.itemOffersField)?.map((o) => {
|
||||||
tag && setOffers(current => [...current, tag])
|
const tag = tags.find((t) => t.id === o.tags_id)
|
||||||
return null
|
tag && setOffers((current) => [...current, tag])
|
||||||
})
|
return null
|
||||||
i?.layer?.itemNeedsField && getValue(i, i.layer.itemNeedsField)?.map(n => {
|
})
|
||||||
const tag = tags.find(t => t.id === n.tags_id)
|
i?.layer?.itemNeedsField &&
|
||||||
tag && setNeeds(current => [...current, tag])
|
getValue(i, i.layer.itemNeedsField)?.map((n) => {
|
||||||
return null
|
const tag = tags.find((t) => t.id === n.tags_id)
|
||||||
})
|
tag && setNeeds((current) => [...current, tag])
|
||||||
|
return null
|
||||||
|
})
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
console.log(offers)
|
console.log(offers)
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [items])
|
}, [items])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage className='tw-rounded-none tw-overflow-y-auto tw-bg-base-200 !tw-p-4'>
|
<MapOverlayPage className='tw-rounded-none tw-overflow-y-auto tw-bg-base-200 !tw-p-4'>
|
||||||
<div className="tw-grid tw-grid-cols-1 md:tw-grid-cols-2">
|
<div className='tw-grid tw-grid-cols-1 md:tw-grid-cols-2'>
|
||||||
<div>
|
<div>
|
||||||
<p className="tw-text-lg tw-font-bold">Offers</p>
|
<p className='tw-text-lg tw-font-bold'>Offers</p>
|
||||||
<div className="tw-flex tw-flex-wrap">
|
<div className='tw-flex tw-flex-wrap'>
|
||||||
{groupAndCount(offers).map(o => (
|
{groupAndCount(offers).map((o) => (
|
||||||
<TagView onClick={() => navigate(`/?tags=${o.object.name}`)} key={o.object.id} tag={o.object} count={o.count}></TagView>
|
<TagView
|
||||||
))}
|
onClick={() => navigate(`/?tags=${o.object.name}`)}
|
||||||
</div>
|
key={o.object.id}
|
||||||
|
tag={o.object}
|
||||||
|
count={o.count}
|
||||||
|
></TagView>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="tw-text-lg tw-font-bold">Needs</p>
|
<p className='tw-text-lg tw-font-bold'>Needs</p>
|
||||||
<div className="tw-flex tw-flex-wrap">
|
<div className='tw-flex tw-flex-wrap'>
|
||||||
{groupAndCount(needs).map(o => (
|
{groupAndCount(needs).map((o) => (
|
||||||
<TagView onClick={() => navigate(`/?tags=${o.object.name}`)} key={o.object.id} tag={o.object} count={o.count}></TagView>
|
<TagView
|
||||||
))}
|
onClick={() => navigate(`/?tags=${o.object.name}`)}
|
||||||
</div>
|
key={o.object.id}
|
||||||
|
tag={o.object}
|
||||||
|
count={o.count}
|
||||||
|
></TagView>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</MapOverlayPage>
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import {
|
import { add, format, parse, startOfToday, sub } from 'date-fns'
|
||||||
add,
|
|
||||||
format,
|
|
||||||
parse,
|
|
||||||
startOfToday,
|
|
||||||
sub
|
|
||||||
} from 'date-fns'
|
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
|
||||||
import { MapOverlayPage } from './MapOverlayPage'
|
import { MapOverlayPage } from './MapOverlayPage'
|
||||||
import { CircleLayout } from './CircleLayout'
|
import { CircleLayout } from './CircleLayout'
|
||||||
@ -34,27 +28,33 @@ export const MoonCalendar = () => {
|
|||||||
<MapOverlayPage backdrop className='tw-h-96 tw-w-80'>
|
<MapOverlayPage backdrop className='tw-h-96 tw-w-80'>
|
||||||
<p className='tw-self-center tw-text-lg tw-font-bold'>Moon Cycle</p>
|
<p className='tw-self-center tw-text-lg tw-font-bold'>Moon Cycle</p>
|
||||||
<div className='tw-relative tw-h-full'>
|
<div className='tw-relative tw-h-full'>
|
||||||
<CircleLayout items={['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘']} radius={80} fontSize={'3em'} />
|
<CircleLayout
|
||||||
<CircleLayout items={ [
|
items={['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘']}
|
||||||
format(getLastNewMoon(), 'dd.MM hh:mm'),
|
radius={80}
|
||||||
format(sub(getNextNewMoon(), { seconds: LUNAR_MONTH * 86400 / 4 * 3 }), 'dd.MM hh:mm'),
|
fontSize={'3em'}
|
||||||
format(sub(getNextNewMoon(), { seconds: LUNAR_MONTH * 86400 / 2 }), 'dd.MM hh:mm'),
|
/>
|
||||||
format(sub(getNextNewMoon(), { seconds: LUNAR_MONTH * 86400 / 4 }), 'dd.MM hh:mm')
|
<CircleLayout
|
||||||
]} radius={120} fontSize={'0.8em'}/>
|
items={[
|
||||||
|
format(getLastNewMoon(), 'dd.MM hh:mm'),
|
||||||
|
format(
|
||||||
|
sub(getNextNewMoon(), { seconds: ((LUNAR_MONTH * 86400) / 4) * 3 }),
|
||||||
|
'dd.MM hh:mm',
|
||||||
|
),
|
||||||
|
format(sub(getNextNewMoon(), { seconds: (LUNAR_MONTH * 86400) / 2 }), 'dd.MM hh:mm'),
|
||||||
|
format(sub(getNextNewMoon(), { seconds: (LUNAR_MONTH * 86400) / 4 }), 'dd.MM hh:mm'),
|
||||||
|
]}
|
||||||
|
radius={120}
|
||||||
|
fontSize={'0.8em'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='tw-flex tw-flex-row'>
|
<div className='tw-flex tw-flex-row'>
|
||||||
<ChevronLeftIcon
|
<ChevronLeftIcon className='tw-w-6 tw-h-6 tw-cursor-pointer' onClick={getPrevMonth} />
|
||||||
className="tw-w-6 tw-h-6 tw-cursor-pointer"
|
<p className='tw-text-center tw-p-1 tw-h-full tw-grow'>
|
||||||
onClick={getPrevMonth}
|
from {format(getLastNewMoon(), 'dd.MM')} - to {format(getNextNewMoon(), 'dd.MM')}
|
||||||
/>
|
</p>
|
||||||
<p className='tw-text-center tw-p-1 tw-h-full tw-grow'>from {format(getLastNewMoon(), 'dd.MM')} - to {format(getNextNewMoon(), 'dd.MM')}</p>
|
|
||||||
|
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon className='tw-w-6 tw-h-6 tw-cursor-pointer' onClick={getNextMonth} />
|
||||||
className="tw-w-6 tw-h-6 tw-cursor-pointer"
|
|
||||||
onClick={getNextMonth}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</MapOverlayPage>
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,13 +17,23 @@ import { SearchControl } from '../Map/Subcomponents/Controls/SearchControl'
|
|||||||
import { TagsControl } from '../Map/Subcomponents/Controls/TagsControl'
|
import { TagsControl } from '../Map/Subcomponents/Controls/TagsControl'
|
||||||
import { useFilterTags } from '../Map/hooks/useFilter'
|
import { useFilterTags } from '../Map/hooks/useFilter'
|
||||||
|
|
||||||
export const OverlayItemsIndexPage = ({ url, layerName, parameterField, plusButton = true }: { layerName: string, url: string, parameterField: string, plusButton?: boolean }) => {
|
export const OverlayItemsIndexPage = ({
|
||||||
|
url,
|
||||||
|
layerName,
|
||||||
|
parameterField,
|
||||||
|
plusButton = true,
|
||||||
|
}: {
|
||||||
|
layerName: string
|
||||||
|
url: string
|
||||||
|
parameterField: string
|
||||||
|
plusButton?: boolean
|
||||||
|
}) => {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [addItemPopupType, setAddItemPopupType] = useState<string>('')
|
const [addItemPopupType, setAddItemPopupType] = useState<string>('')
|
||||||
|
|
||||||
const tabRef = useRef<HTMLFormElement>(null)
|
const tabRef = useRef<HTMLFormElement>(null)
|
||||||
|
|
||||||
function scroll () {
|
function scroll() {
|
||||||
tabRef.current?.scrollIntoView()
|
tabRef.current?.scrollIntoView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +56,7 @@ export const OverlayItemsIndexPage = ({ url, layerName, parameterField, plusButt
|
|||||||
const filterTags = useFilterTags()
|
const filterTags = useFilterTags()
|
||||||
const getItemTags = useGetItemTags()
|
const getItemTags = useGetItemTags()
|
||||||
|
|
||||||
const layer = layers.find(l => l.name === layerName)
|
const layer = layers.find((l) => l.name === layerName)
|
||||||
|
|
||||||
const submitNewItem = async (evt: any) => {
|
const submitNewItem = async (evt: any) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
@ -57,12 +67,16 @@ export const OverlayItemsIndexPage = ({ url, layerName, parameterField, plusButt
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
formItem.text &&
|
||||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
formItem.text
|
||||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
.toLocaleLowerCase()
|
||||||
}
|
.match(hashTagRegex)
|
||||||
return null
|
?.map((tag) => {
|
||||||
})
|
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
|
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
const uuid = crypto.randomUUID()
|
const uuid = crypto.randomUUID()
|
||||||
let success = false
|
let success = false
|
||||||
try {
|
try {
|
||||||
@ -96,57 +110,108 @@ export const OverlayItemsIndexPage = ({ url, layerName, parameterField, plusButt
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MapOverlayPage className='tw-rounded-none tw-overflow-y-auto tw-bg-base-200 !tw-p-4'>
|
<MapOverlayPage className='tw-rounded-none tw-overflow-y-auto tw-bg-base-200 !tw-p-4'>
|
||||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||||
<div className='tw-flex-none'>
|
<div className='tw-flex-none'>
|
||||||
<Control position='topLeft' zIndex="1000" absolute={false}>
|
<Control position='topLeft' zIndex='1000' absolute={false}>
|
||||||
<SearchControl />
|
<SearchControl />
|
||||||
<TagsControl />
|
<TagsControl />
|
||||||
</Control>
|
</Control>
|
||||||
|
</div>
|
||||||
|
<div className='tw-overflow-scroll fade tw-flex-1'>
|
||||||
|
<div className='tw-columns-1 md:tw-columns-2 lg:tw-columns-3 2xl:tw-columns-4 tw-gap-6 tw-pt-4'>
|
||||||
|
{items
|
||||||
|
?.filter((i) => i.layer?.name === layerName)
|
||||||
|
.filter((item) =>
|
||||||
|
filterTags.length === 0
|
||||||
|
? item
|
||||||
|
: filterTags.some((tag) =>
|
||||||
|
getItemTags(item).some(
|
||||||
|
(filterTag) =>
|
||||||
|
filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
?.sort((a, b) => {
|
||||||
|
// Convert date_created to milliseconds, handle undefined by converting to lowest possible date (0 milliseconds)
|
||||||
|
const dateA = a.date_updated
|
||||||
|
? new Date(a.date_updated).getTime()
|
||||||
|
: a.date_created
|
||||||
|
? new Date(a.date_created).getTime()
|
||||||
|
: 0
|
||||||
|
const dateB = b.date_updated
|
||||||
|
? new Date(b.date_updated).getTime()
|
||||||
|
: b.date_created
|
||||||
|
? new Date(b.date_created).getTime()
|
||||||
|
: 0
|
||||||
|
return dateB - dateA // Subtracts milliseconds which are numbers
|
||||||
|
})
|
||||||
|
?.map((i, k) => (
|
||||||
|
<div key={k} className='tw-break-inside-avoid tw-mb-6'>
|
||||||
|
<ItemCard
|
||||||
|
i={i}
|
||||||
|
loading={loading}
|
||||||
|
url={url}
|
||||||
|
parameterField={parameterField}
|
||||||
|
deleteCallback={() => deleteItem(i)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{addItemPopupType === 'place' && (
|
||||||
|
<form ref={tabRef} autoComplete='off' onSubmit={(e) => submitNewItem(e)}>
|
||||||
|
<div className='tw-cursor-pointer tw-break-inside-avoid 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-6 tw-mb-10'>
|
||||||
|
<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={() => setAddItemPopupType('')}
|
||||||
|
>
|
||||||
|
<p className='tw-text-center'>✕</p>
|
||||||
|
</label>
|
||||||
|
<TextInput
|
||||||
|
type='text'
|
||||||
|
placeholder='Name'
|
||||||
|
dataField='name'
|
||||||
|
defaultValue={''}
|
||||||
|
inputStyle=''
|
||||||
|
/>
|
||||||
|
{layer?.itemType.show_start_end_input && <PopupStartEndInput />}
|
||||||
|
<TextAreaInput
|
||||||
|
placeholder='Text'
|
||||||
|
dataField='text'
|
||||||
|
defaultValue={''}
|
||||||
|
inputStyle='tw-h-40 tw-mt-5'
|
||||||
|
/>
|
||||||
|
<div className='tw-flex tw-justify-center'>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
loading
|
||||||
|
? 'tw-btn tw-btn-disabled tw-mt-5 tw-place-self-center'
|
||||||
|
: 'tw-btn tw-mt-5 tw-place-self-center'
|
||||||
|
}
|
||||||
|
type='submit'
|
||||||
|
>
|
||||||
|
{loading ? <span className='tw-loading tw-loading-spinner'></span> : 'Save'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-overflow-scroll fade tw-flex-1">
|
</div>
|
||||||
<div className='tw-columns-1 md:tw-columns-2 lg:tw-columns-3 2xl:tw-columns-4 tw-gap-6 tw-pt-4'>
|
</form>
|
||||||
{
|
)}
|
||||||
items?.filter(i => i.layer?.name === layerName)
|
</div>
|
||||||
.filter(item =>
|
</div>
|
||||||
filterTags.length === 0 ? item : filterTags.some(tag => getItemTags(item).some(filterTag => filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())))
|
</div>
|
||||||
?.sort((a, b) => {
|
</MapOverlayPage>
|
||||||
// Convert date_created to milliseconds, handle undefined by converting to lowest possible date (0 milliseconds)
|
|
||||||
const dateA = a.date_updated ? new Date(a.date_updated).getTime() : a.date_created ? new Date(a.date_created).getTime() : 0
|
|
||||||
const dateB = b.date_updated ? new Date(b.date_updated).getTime() : b.date_created ? new Date(b.date_created).getTime() : 0
|
|
||||||
return dateB - dateA // Subtracts milliseconds which are numbers
|
|
||||||
})
|
|
||||||
?.map((i, k) => (
|
|
||||||
<div key={k} className="tw-break-inside-avoid tw-mb-6">
|
|
||||||
<ItemCard i={i} loading={loading} url={url} parameterField={parameterField} deleteCallback={() => deleteItem(i)} />
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{addItemPopupType === 'place' && (
|
|
||||||
<form ref={tabRef} autoComplete='off' onSubmit={e => submitNewItem(e)}>
|
|
||||||
<div className='tw-cursor-pointer tw-break-inside-avoid 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-6 tw-mb-10'>
|
|
||||||
<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={() => setAddItemPopupType('')}>
|
|
||||||
<p className='tw-text-center'>✕</p>
|
|
||||||
</label>
|
|
||||||
<TextInput type="text" placeholder="Name" dataField="name" defaultValue={''} inputStyle='' />
|
|
||||||
{layer?.itemType.show_start_end_input && <PopupStartEndInput />}
|
|
||||||
<TextAreaInput placeholder="Text" dataField="text" defaultValue={''} inputStyle='tw-h-40 tw-mt-5' />
|
|
||||||
<div className='tw-flex tw-justify-center'>
|
|
||||||
<button className={loading ? 'tw-btn tw-btn-disabled tw-mt-5 tw-place-self-center' : 'tw-btn tw-mt-5 tw-place-self-center'} type='submit'>
|
|
||||||
{loading ? <span className="tw-loading tw-loading-spinner"></span> : 'Save'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
{plusButton && (
|
||||||
</div>
|
<PlusButton
|
||||||
</MapOverlayPage>
|
layer={layer}
|
||||||
|
triggerAction={() => {
|
||||||
{plusButton && <PlusButton layer={layer} triggerAction={() => { setAddItemPopupType('place'); scroll() }} color={'#777'} collection='items' />}
|
setAddItemPopupType('place')
|
||||||
</>
|
scroll()
|
||||||
|
}}
|
||||||
|
color={'#777'}
|
||||||
|
collection='items'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,49 +6,62 @@ import { Link } from 'react-router-dom'
|
|||||||
|
|
||||||
export const SelectUser = ({ userType }: { userType: string }) => {
|
export const SelectUser = ({ userType }: { userType: string }) => {
|
||||||
const items = useItems()
|
const items = useItems()
|
||||||
const users = items.filter(i => i.layer?.itemType.name === userType)
|
const users = items.filter((i) => i.layer?.itemType.name === userType)
|
||||||
const assetsApi = useAssetApi()
|
const assetsApi = useAssetApi()
|
||||||
|
|
||||||
const [selectedUsers, setSelectedUsers] = useState<Array<string>>([])
|
const [selectedUsers, setSelectedUsers] = useState<Array<string>>([])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage backdrop className='tw-h-3/4 tw-w-80'>
|
<MapOverlayPage backdrop className='tw-h-3/4 tw-w-80'>
|
||||||
|
<div className='tw-text-center tw-text-xl tw-font-bold tw-mb-4'>Gratitude to ...</div>
|
||||||
|
|
||||||
<div className='tw-text-center tw-text-xl tw-font-bold tw-mb-4'>Gratitude to ...</div>
|
{/* Team Member list in table format loaded constant */}
|
||||||
|
<div className='tw-overflow-x-auto tw-w-full fade'>
|
||||||
{/* Team Member list in table format loaded constant */}
|
<table className='tw-table tw-w-full'>
|
||||||
<div className="tw-overflow-x-auto tw-w-full fade">
|
<tbody>
|
||||||
<table className="tw-table tw-w-full">
|
{users.map((u, k) => {
|
||||||
<tbody>
|
return (
|
||||||
{
|
<tr key={k}>
|
||||||
users.map((u, k) => {
|
<td>
|
||||||
return (
|
<input
|
||||||
<tr key={k}>
|
type='checkbox'
|
||||||
<td>
|
onChange={() => setSelectedUsers((prev) => [...prev, u.id])}
|
||||||
<input type="checkbox" onChange={() => setSelectedUsers(prev => [...prev, u.id])} className="tw-checkbox tw-checkbox-sm" />
|
className='tw-checkbox tw-checkbox-sm'
|
||||||
</td>
|
/>
|
||||||
<td>
|
</td>
|
||||||
<div className="tw-flex tw-items-center tw-space-x-3">
|
<td>
|
||||||
{u.image
|
<div className='tw-flex tw-items-center tw-space-x-3'>
|
||||||
? <div className="tw-avatar">
|
{u.image ? (
|
||||||
<div className="tw-mask tw-mask-circle tw-w-8 tw-h-8">
|
<div className='tw-avatar'>
|
||||||
<img src={assetsApi.url + u.image + '?width=40&heigth=40'} alt="Avatar" />
|
<div className='tw-mask tw-mask-circle tw-w-8 tw-h-8'>
|
||||||
</div>
|
<img
|
||||||
</div>
|
src={assetsApi.url + u.image + '?width=40&heigth=40'}
|
||||||
: <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>}
|
alt='Avatar'
|
||||||
<div>
|
/>
|
||||||
<div className="tw-font-bold">{u.name}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
</td>
|
<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>
|
||||||
</tr>
|
)}
|
||||||
)
|
<div>
|
||||||
})
|
<div className='tw-font-bold'>{u.name}</div>
|
||||||
}
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
<div className='tw-w-full tw-grid tw-mt-4'><Link className="tw-place-self-center " to={'/attestation-form' + '?to=' + selectedUsers.map(u => u, ',')}><button className="tw-btn tw-px-8">Next</button></Link></div>
|
)
|
||||||
</MapOverlayPage>
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className='tw-w-full tw-grid tw-mt-4'>
|
||||||
|
<Link
|
||||||
|
className='tw-place-self-center '
|
||||||
|
to={'/attestation-form' + '?to=' + selectedUsers.map((u) => u, ',')}
|
||||||
|
>
|
||||||
|
<button className='tw-btn tw-px-8'>Next</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</MapOverlayPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,19 +2,28 @@ import * as React from 'react'
|
|||||||
import { decodeTag } from '../../Utils/FormatTags'
|
import { decodeTag } from '../../Utils/FormatTags'
|
||||||
import { Tag } from '../../types'
|
import { Tag } from '../../types'
|
||||||
|
|
||||||
export const TagView = ({ tag, heighlight, onClick, count }: { tag: Tag, heighlight?: boolean, onClick?: (/* e */) => void, count?: number }) => {
|
export const TagView = ({
|
||||||
|
tag,
|
||||||
|
heighlight,
|
||||||
|
onClick,
|
||||||
|
count,
|
||||||
|
}: {
|
||||||
|
tag: Tag
|
||||||
|
heighlight?: boolean
|
||||||
|
onClick?: (/* e */) => void
|
||||||
|
count?: number
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
// Use your imagination to render suggestions.
|
// Use your imagination to render suggestions.
|
||||||
|
|
||||||
<div
|
|
||||||
key={tag.name}
|
|
||||||
onClick={onClick}
|
|
||||||
className={`tw-flex tw-items-center tw-flex-row tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit ${heighlight && 'tw-border-4 tw-border-base-200 tw-shadow-lg'}`}
|
|
||||||
style={{ backgroundColor: tag.color ? tag.color : '#666' }}
|
|
||||||
>
|
|
||||||
<b>{decodeTag(tag.name)}</b>
|
|
||||||
{count && <span className="tw-ml-2">({count})</span>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
key={tag.name}
|
||||||
|
onClick={onClick}
|
||||||
|
className={`tw-flex tw-items-center tw-flex-row tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit ${heighlight && 'tw-border-4 tw-border-base-200 tw-shadow-lg'}`}
|
||||||
|
style={{ backgroundColor: tag.color ? tag.color : '#666' }}
|
||||||
|
>
|
||||||
|
<b>{decodeTag(tag.name)}</b>
|
||||||
|
{count && <span className='tw-ml-2'>({count})</span>}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,39 +2,47 @@ import Subtitle from '../Typography/Subtitle'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
interface TitleCardProps {
|
interface TitleCardProps {
|
||||||
|
title?: string
|
||||||
title?: string,
|
hideTitle?: boolean
|
||||||
hideTitle?: boolean,
|
children?: React.ReactNode
|
||||||
children?: React.ReactNode,
|
topMargin?: string
|
||||||
topMargin?: string,
|
className?: string
|
||||||
className?: string,
|
|
||||||
TopSideButtons?: any
|
TopSideButtons?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TitleCard ({ title, hideTitle, children, topMargin, TopSideButtons, className }: TitleCardProps) {
|
export function TitleCard({
|
||||||
|
title,
|
||||||
|
hideTitle,
|
||||||
|
children,
|
||||||
|
topMargin,
|
||||||
|
TopSideButtons,
|
||||||
|
className,
|
||||||
|
}: TitleCardProps) {
|
||||||
return (
|
return (
|
||||||
<div className={'tw-card tw-w-full tw-p-6 tw-bg-base-100 tw-shadow-xl tw-h-fit tw-mb-4 ' + (className || '') + ' ' + (topMargin || 'tw-mt-6')}>
|
<div
|
||||||
|
className={
|
||||||
{!hideTitle &&
|
'tw-card tw-w-full tw-p-6 tw-bg-base-100 tw-shadow-xl tw-h-fit tw-mb-4 ' +
|
||||||
|
(className || '') +
|
||||||
|
' ' +
|
||||||
|
(topMargin || 'tw-mt-6')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!hideTitle && (
|
||||||
<>
|
<>
|
||||||
|
<Subtitle styleClass={TopSideButtons ? 'tw-inline-block' : ''}>
|
||||||
< Subtitle styleClass={TopSideButtons ? 'tw-inline-block' : ''}>
|
|
||||||
{title}
|
{title}
|
||||||
|
|
||||||
{/* Top side button, show only if present */}
|
{/* Top side button, show only if present */}
|
||||||
{
|
{TopSideButtons && (
|
||||||
TopSideButtons && <div className="tw-inline-block tw-float-right">{TopSideButtons}</div>
|
<div className='tw-inline-block tw-float-right'>{TopSideButtons}</div>
|
||||||
}
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<div className="tw-divider tw-mt-2"></div>
|
<div className='tw-divider tw-mt-2'></div>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
|
|
||||||
{/** Card Body */}
|
{/** Card Body */}
|
||||||
<div className='tw-h-full tw-bg-transparent tw-w-full tw-pb-6 tw-bg-base-100'>
|
<div className='tw-h-full tw-bg-transparent tw-w-full tw-pb-6 tw-bg-base-100'>{children}</div>
|
||||||
{children}
|
</div>
|
||||||
</div>
|
|
||||||
</div >
|
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
function ErrorText ({ styleClass, children }) {
|
function ErrorText({ styleClass, children }) {
|
||||||
return (
|
return <p className={`tw-text-center tw-text-error ${styleClass}`}>{children}</p>
|
||||||
<p className={`tw-text-center tw-text-error ${styleClass}`}>{children}</p>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ErrorText
|
export default ErrorText
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user