mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Merge pull request #21 from utopia-os/eslint-standart
fix(other): eslint standard
This commit is contained in:
commit
3464eadc89
230
.eslintrc.js
230
.eslintrc.js
@ -1,47 +1,47 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true
|
||||
},
|
||||
extends: [
|
||||
// 'standard',
|
||||
'eslint:recommended',
|
||||
//'plugin:@eslint-community/eslint-comments/recommended',
|
||||
//'plugin:@typescript-eslint/recommended',
|
||||
//'plugin:import/recommended',
|
||||
//'plugin:import/typescript',
|
||||
//'plugin:promise/recommended',
|
||||
//'plugin:security/recommended-legacy',
|
||||
'plugin:react/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
//'@typescript-eslint',
|
||||
//'import',
|
||||
//'promise',
|
||||
//'security',
|
||||
//'no-catch-all',
|
||||
'react',
|
||||
'react-hooks',
|
||||
],
|
||||
settings: {
|
||||
//'import/resolver': {
|
||||
// typescript: true,
|
||||
// node: true,
|
||||
//},
|
||||
'react': {
|
||||
'version': 'detect'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
||||
'react/react-in-jsx-scope': 'off' // Disable requirement for React import
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true
|
||||
},
|
||||
extends: [
|
||||
'standard',
|
||||
'eslint:recommended',
|
||||
// 'plugin:@eslint-community/eslint-comments/recommended',
|
||||
// 'plugin:@typescript-eslint/recommended',
|
||||
// 'plugin:import/recommended',
|
||||
// 'plugin:import/typescript',
|
||||
// 'plugin:promise/recommended',
|
||||
// 'plugin:security/recommended-legacy',
|
||||
'plugin:react/recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: [
|
||||
// '@typescript-eslint',
|
||||
// 'import',
|
||||
// 'promise',
|
||||
// 'security',
|
||||
// 'no-catch-all',
|
||||
'react',
|
||||
'react-hooks'
|
||||
],
|
||||
settings: {
|
||||
// 'import/resolver': {
|
||||
// typescript: true,
|
||||
// node: true,
|
||||
// },
|
||||
react: {
|
||||
version: 'detect'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
||||
'react/react-in-jsx-scope': 'off' // Disable requirement for React import
|
||||
// 'no-catch-all/no-catch-all': 'error',
|
||||
// 'no-console': 'error',
|
||||
// 'no-debugger': 'error',
|
||||
@ -135,78 +135,78 @@ module.exports = {
|
||||
// 'promise/valid-params': 'warn',
|
||||
// 'promise/prefer-await-to-callbacks': 'error',
|
||||
// 'promise/no-multiple-resolved': 'error',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
parser: '@typescript-eslint/parser'
|
||||
// parserOptions: {
|
||||
// tsconfigRootDir: __dirname,
|
||||
// project: ['./tsconfig.json', '**/tsconfig.json'],
|
||||
// ecmaVersion: 'latest',
|
||||
// parser: '@typescript-eslint/parser',
|
||||
// sourceType: 'module',
|
||||
// },
|
||||
// plugins: ['@typescript-eslint'],
|
||||
// extends: [
|
||||
// 'plugin:@typescript-eslint/recommended',
|
||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
// 'plugin:@typescript-eslint/strict',
|
||||
// ],
|
||||
// rules: {
|
||||
// // allow explicitly defined dangling promises
|
||||
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
// 'no-void': ['error', { allowAsStatement: true }],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// files: ['!*.json'],
|
||||
// plugins: ['prettier'],
|
||||
// extends: ['plugin:prettier/recommended'],
|
||||
// rules: {
|
||||
// 'prettier/prettier': 'error',
|
||||
// },
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
// parserOptions: {
|
||||
// tsconfigRootDir: __dirname,
|
||||
// project: ['./tsconfig.json', '**/tsconfig.json'],
|
||||
// ecmaVersion: 'latest',
|
||||
// parser: '@typescript-eslint/parser',
|
||||
// sourceType: 'module',
|
||||
// },
|
||||
// plugins: ['@typescript-eslint'],
|
||||
// extends: [
|
||||
// 'plugin:@typescript-eslint/recommended',
|
||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
// 'plugin:@typescript-eslint/strict',
|
||||
// ],
|
||||
// rules: {
|
||||
// // allow explicitly defined dangling promises
|
||||
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
// 'no-void': ['error', { allowAsStatement: true }],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// files: ['!*.json'],
|
||||
// plugins: ['prettier'],
|
||||
// extends: ['plugin:prettier/recommended'],
|
||||
// rules: {
|
||||
// 'prettier/prettier': 'error',
|
||||
// },
|
||||
},
|
||||
{
|
||||
files: ['*.json'],
|
||||
plugins: ['json'],
|
||||
extends: ['plugin:json/recommended-with-comments'],
|
||||
},
|
||||
// {
|
||||
// files: ['*.{test,spec}.[tj]s'],
|
||||
// plugins: ['vitest'],
|
||||
// extends: ['plugin:vitest/all'],
|
||||
// rules: {
|
||||
// 'vitest/prefer-lowercase-title': 'off',
|
||||
// 'vitest/no-hooks': 'off',
|
||||
// 'vitest/consistent-test-filename': 'off',
|
||||
// 'vitest/prefer-expect-assertions': [
|
||||
// 'off',
|
||||
// {
|
||||
// onlyFunctionsWithExpectInLoop: true,
|
||||
// onlyFunctionsWithExpectInCallback: true,
|
||||
// },
|
||||
// ],
|
||||
// 'vitest/prefer-strict-equal': 'off',
|
||||
// 'vitest/prefer-to-be-falsy': 'off',
|
||||
// 'vitest/prefer-to-be-truthy': 'off',
|
||||
// 'vitest/require-hook': [
|
||||
// 'error',
|
||||
// {
|
||||
// allowedFunctionCalls: [
|
||||
// 'mockClient.setRequestHandler',
|
||||
// 'setActivePinia',
|
||||
// 'provideApolloClient',
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
{
|
||||
files: ['*.yaml', '*.yml'],
|
||||
parser: 'yaml-eslint-parser',
|
||||
plugins: ['yml'],
|
||||
extends: ['plugin:yml/prettier'],
|
||||
},
|
||||
],
|
||||
{
|
||||
files: ['*.json'],
|
||||
plugins: ['json'],
|
||||
extends: ['plugin:json/recommended-with-comments']
|
||||
},
|
||||
// {
|
||||
// files: ['*.{test,spec}.[tj]s'],
|
||||
// plugins: ['vitest'],
|
||||
// extends: ['plugin:vitest/all'],
|
||||
// rules: {
|
||||
// 'vitest/prefer-lowercase-title': 'off',
|
||||
// 'vitest/no-hooks': 'off',
|
||||
// 'vitest/consistent-test-filename': 'off',
|
||||
// 'vitest/prefer-expect-assertions': [
|
||||
// 'off',
|
||||
// {
|
||||
// onlyFunctionsWithExpectInLoop: true,
|
||||
// onlyFunctionsWithExpectInCallback: true,
|
||||
// },
|
||||
// ],
|
||||
// 'vitest/prefer-strict-equal': 'off',
|
||||
// 'vitest/prefer-to-be-falsy': 'off',
|
||||
// 'vitest/prefer-to-be-truthy': 'off',
|
||||
// 'vitest/require-hook': [
|
||||
// 'error',
|
||||
// {
|
||||
// allowedFunctionCalls: [
|
||||
// 'mockClient.setRequestHandler',
|
||||
// 'setActivePinia',
|
||||
// 'provideApolloClient',
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
{
|
||||
files: ['*.yaml', '*.yml'],
|
||||
parser: 'yaml-eslint-parser',
|
||||
plugins: ['yml'],
|
||||
extends: ['plugin:yml/prettier']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1396
package-lock.json
generated
1396
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,7 @@
|
||||
"autoprefixer": "^10.4.14",
|
||||
"daisyui": "^4.6.1",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,21 +2,21 @@ import postcss from 'rollup-plugin-postcss'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
|
||||
export default {
|
||||
input: 'src/index.tsx',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist/',
|
||||
format: 'esm',
|
||||
exports: 'named',
|
||||
sourcemap: true,
|
||||
strict: false
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
postcss({
|
||||
plugins: []
|
||||
}),
|
||||
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']
|
||||
}
|
||||
input: 'src/index.tsx',
|
||||
output: [
|
||||
{
|
||||
dir: 'dist/',
|
||||
format: 'esm',
|
||||
exports: 'named',
|
||||
sourcemap: true,
|
||||
strict: false
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
postcss({
|
||||
plugins: []
|
||||
}),
|
||||
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']
|
||||
}
|
||||
|
||||
@ -2,11 +2,10 @@ import * as React from 'react'
|
||||
import NavBar from './NavBar'
|
||||
import { SetAssetsApi } from './SetAssetsApi'
|
||||
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 }) {
|
||||
return (
|
||||
export function AppShell ({ appName, children, assetsApi, userType }: { appName: string, children: React.ReactNode, assetsApi: AssetsApi, userType: string }) {
|
||||
return (
|
||||
<ContextWrapper>
|
||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||
<SetAssetsApi assetsApi={assetsApi} />
|
||||
@ -16,5 +15,5 @@ export function AppShell({ appName, children, assetsApi, userType }: { appName:
|
||||
</div>
|
||||
</div>
|
||||
</ContextWrapper>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ type ContentProps = {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Content({children} : ContentProps) {
|
||||
export function Content ({ children } : ContentProps) {
|
||||
return (
|
||||
<div className='tw-flex tw-flex-col tw-w-full tw-h-full tw-bg-base-200 tw-relative'>
|
||||
{children}
|
||||
|
||||
@ -10,24 +10,23 @@ import { PermissionsProvider } from '../Map/hooks/usePermissions'
|
||||
import { SelectPositionProvider } from '../Map/hooks/useSelectPosition'
|
||||
import { TagsProvider } from '../Map/hooks/useTags'
|
||||
import { AssetsProvider } from './hooks/useAssets'
|
||||
import { useContext, createContext } from 'react';
|
||||
import { BrowserRouter as Router, useLocation } from 'react-router-dom';
|
||||
|
||||
import { useContext, createContext } from 'react'
|
||||
import { BrowserRouter as Router, useLocation } from 'react-router-dom'
|
||||
|
||||
// Helper context to determine if the ContextWrapper is already present.
|
||||
const ContextCheckContext = createContext(false);
|
||||
const ContextCheckContext = createContext(false)
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const ContextWrapper = ({ children }) => {
|
||||
const isWrapped = useContext(ContextCheckContext);
|
||||
const isWrapped = useContext(ContextCheckContext)
|
||||
|
||||
// Check if we are already inside a Router
|
||||
let location;
|
||||
let location
|
||||
try {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
location = useLocation();
|
||||
location = useLocation()
|
||||
} catch (e) {
|
||||
location = null;
|
||||
location = null
|
||||
}
|
||||
|
||||
// Case 1: Only the Router is missing, but ContextWrapper is already provided
|
||||
@ -36,7 +35,7 @@ export const ContextWrapper = ({ children }) => {
|
||||
<Router>
|
||||
{children}
|
||||
</Router>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Case 2: Neither Router nor ContextWrapper is present
|
||||
@ -49,7 +48,7 @@ export const ContextWrapper = ({ children }) => {
|
||||
</Wrappers>
|
||||
</ContextCheckContext.Provider>
|
||||
</Router>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Case 3: Only ContextWrapper is missing
|
||||
@ -58,16 +57,16 @@ export const ContextWrapper = ({ children }) => {
|
||||
<ContextCheckContext.Provider value={true}>
|
||||
<Wrappers>{children}</Wrappers>
|
||||
</ContextCheckContext.Provider>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Case 4: Both Router and ContextWrapper are already present
|
||||
return children;
|
||||
};
|
||||
return children
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const Wrappers = ({ children }) => {
|
||||
const queryClient = new QueryClient();
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
return (
|
||||
<PermissionsProvider initialPermissions={[]}>
|
||||
@ -103,5 +102,5 @@ export const Wrappers = ({ children }) => {
|
||||
</LayersProvider>
|
||||
</TagsProvider>
|
||||
</PermissionsProvider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,72 +1,64 @@
|
||||
import { useAuth } from "../Auth"
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { useAuth } from '../Auth'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import QuestionMarkIcon from '@heroicons/react/24/outline/QuestionMarkCircleIcon'
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useItems } from "../Map/hooks/useItems";
|
||||
import { Item } from "../../types";
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useItems } from '../Map/hooks/useItems'
|
||||
import { Item } from '../../types'
|
||||
|
||||
export default function NavBar ({ appName, userType }: { appName: string, userType: string }) {
|
||||
const { isAuthenticated, user, logout } = useAuth()
|
||||
|
||||
export default function NavBar({ appName, userType}: { appName: string, userType: string }) {
|
||||
|
||||
|
||||
const { isAuthenticated, user, logout } = useAuth();
|
||||
|
||||
const [userProfile, setUserProfile] = useState<Item>({}as Item);
|
||||
const items = useItems();
|
||||
const [userProfile, setUserProfile] = useState<Item>({}as Item)
|
||||
const items = useItems()
|
||||
|
||||
useEffect(() => {
|
||||
const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === userType);
|
||||
profile ? setUserProfile(profile) : setUserProfile({id: crypto.randomUUID(), name: user?.first_name, text: ""});
|
||||
const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === userType)
|
||||
profile ? setUserProfile(profile) : setUserProfile({ id: crypto.randomUUID(), name: user?.first_name, text: '' })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [user, items])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
}, [userProfile])
|
||||
|
||||
const nameRef = useRef<any>(null);
|
||||
const [nameWidth, setNameWidth] = useState<number>(0);
|
||||
const location = useLocation();
|
||||
|
||||
const nameRef = useRef<any>(null)
|
||||
const [nameWidth, setNameWidth] = useState<number>(0)
|
||||
const location = useLocation()
|
||||
const [showNav, setShowNav] = useState<boolean>(false)
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
showNav && nameRef && setNameWidth(nameRef.current.scrollWidth)
|
||||
showNav && nameRef && setNameWidth(nameRef.current.scrollWidth)
|
||||
}, [nameRef, appName, showNav])
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const embedded = params.get("embedded");
|
||||
embedded!="true" && setShowNav(true)
|
||||
}, [location]);
|
||||
|
||||
const params = new URLSearchParams(location.search)
|
||||
const embedded = params.get('embedded')
|
||||
embedded !== 'true' && setShowNav(true)
|
||||
}, [location])
|
||||
|
||||
const onLogout = () => {
|
||||
toast.promise(
|
||||
logout(),
|
||||
{
|
||||
success: {
|
||||
render() {
|
||||
return `Bye bye`
|
||||
render () {
|
||||
return 'Bye bye'
|
||||
},
|
||||
// other options
|
||||
icon: "👋",
|
||||
icon: '👋'
|
||||
},
|
||||
error: {
|
||||
render( {data} ) {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
}
|
||||
},
|
||||
pending: 'logging out ..'
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if(showNav) return (
|
||||
if (showNav) {
|
||||
return (
|
||||
<>
|
||||
<div className="tw-navbar tw-bg-base-100 tw-z-[10000] tw-shadow-xl tw-relative">
|
||||
<button className="tw-btn tw-btn-square tw-btn-ghost"
|
||||
@ -77,23 +69,21 @@ export default function NavBar({ appName, userType}: { appName: string, userType
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="tw-inline-block tw-w-5 tw-h-5 tw-stroke-current"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
{isAuthenticated ?
|
||||
<div className="tw-flex-none">
|
||||
<Link to={`${userProfile.id && "/item/"+userProfile.id}`} className="tw-flex tw-items-center">
|
||||
{isAuthenticated
|
||||
? <div className="tw-flex-none">
|
||||
<Link to={`${userProfile.id && '/item/' + userProfile.id}`} className="tw-flex tw-items-center">
|
||||
{ userProfile?.image && <div className="tw-avatar">
|
||||
<div className="tw-w-10 tw-rounded-full">
|
||||
<img src={"https://api.utopia-lab.org/assets/" + userProfile.image} />
|
||||
<img src={'https://api.utopia-lab.org/assets/' + userProfile.image} />
|
||||
</div>
|
||||
</div>}
|
||||
<div className='tw-ml-2 tw-mr-2'>{userProfile.name||user?.first_name}</div>
|
||||
<div className='tw-ml-2 tw-mr-2'>{userProfile.name || user?.first_name}</div>
|
||||
</Link>
|
||||
<div className="tw-dropdown tw-dropdown-end">
|
||||
<label tabIndex={0} className="tw-btn tw-btn-ghost tw-btn-square">
|
||||
@ -102,21 +92,20 @@ export default function NavBar({ appName, userType}: { appName: string, userType
|
||||
</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><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 className="tw-hidden md:tw-flex">
|
||||
<Link to={"/login"}><div className="tw-btn tw-btn-ghost tw-mr-2">
|
||||
<Link to={'/login'}><div className="tw-btn tw-btn-ghost tw-mr-2">
|
||||
Login
|
||||
</div></Link>
|
||||
|
||||
<Link to={"/signup"}><div className="tw-btn tw-btn-ghost tw-mr-2">
|
||||
<Link to={'/signup'}><div className="tw-btn tw-btn-ghost tw-mr-2">
|
||||
Sign Up
|
||||
</div></Link>
|
||||
</div>
|
||||
@ -126,11 +115,10 @@ export default function NavBar({ appName, userType}: { appName: string, userType
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
||||
</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>
|
||||
<li><Link to={'/login'}>Login</Link></li>
|
||||
<li><Link to={'/signup'}>Sign Up</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -139,6 +127,6 @@ export default function NavBar({ appName, userType}: { appName: string, userType
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
else return (<></>)
|
||||
)
|
||||
} else return (<></>)
|
||||
}
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { useSetAssetApi } from './hooks/useAssets'
|
||||
import { AssetsApi } from '../../types';
|
||||
import { useEffect } from 'react';
|
||||
import { AssetsApi } from '../../types'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export const SetAssetsApi = ({assetsApi}:{assetsApi: AssetsApi}) => {
|
||||
const setAssetsApi = useSetAssetApi();
|
||||
export const SetAssetsApi = ({ assetsApi }:{assetsApi: AssetsApi}) => {
|
||||
const setAssetsApi = useSetAssetApi()
|
||||
|
||||
useEffect(() => {
|
||||
setAssetsApi(assetsApi)
|
||||
useEffect(() => {
|
||||
setAssetsApi(assetsApi)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assetsApi])
|
||||
|
||||
}, [assetsApi])
|
||||
|
||||
return (
|
||||
<></>
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import {
|
||||
Sidenav,
|
||||
initTE,
|
||||
} from "tw-elements";
|
||||
import SidebarSubmenu from './SidebarSubmenu';
|
||||
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon';
|
||||
import * as React from 'react';
|
||||
initTE
|
||||
} from 'tw-elements'
|
||||
import SidebarSubmenu from './SidebarSubmenu'
|
||||
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon'
|
||||
import * as React from 'react'
|
||||
|
||||
type route = {
|
||||
path: string;
|
||||
@ -18,53 +17,46 @@ type route = {
|
||||
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
|
||||
const init = useRef(false)
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const [instance, setInstance] = useState<any>(null);
|
||||
const [slim, setSlim] = useState<boolean>(false);
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
const [instance, setInstance] = useState<any>(null)
|
||||
const [slim, setSlim] = useState<boolean>(false)
|
||||
|
||||
const toggleSlim = () => {
|
||||
setSlim(!slim);
|
||||
instance.toggleSlim();
|
||||
setSlim(!slim)
|
||||
instance.toggleSlim()
|
||||
}
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
initTE({ Sidenav });
|
||||
initTE({ Sidenav })
|
||||
const instance = Sidenav.getInstance(
|
||||
document.getElementById("sidenav")
|
||||
);
|
||||
setInstance(instance);
|
||||
instance.toggleSlim();
|
||||
init.current = true;
|
||||
document.getElementById('sidenav')
|
||||
)
|
||||
setInstance(instance)
|
||||
instance.toggleSlim()
|
||||
init.current = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [embedded, setEmbedded] = useState<boolean>(true)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const embedded = params.get("embedded");
|
||||
embedded != "true" && setEmbedded(false)
|
||||
}, [location]);
|
||||
const params = new URLSearchParams(location.search)
|
||||
const embedded = params.get('embedded')
|
||||
embedded !== 'true' && setEmbedded(false)
|
||||
}, [location])
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
return (
|
||||
<nav
|
||||
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-hidden="true"
|
||||
data-te-sidenav-mode="side"
|
||||
@ -73,26 +65,28 @@ export function SideBar({ routes, bottomRoutes }: { routes: route[], bottomRoute
|
||||
data-te-sidenav-slim-collapsed="true"
|
||||
data-te-sidenav-slim-width="56"
|
||||
data-te-sidenav-width="180">
|
||||
<div className={`tw-flex tw-flex-col ${embedded ? "tw-h-full" :"tw-h-[calc(100dvh-64px)]"}`}>
|
||||
<div className={`tw-flex tw-flex-col ${embedded ? 'tw-h-full' : 'tw-h-[calc(100dvh-64px)]'}`}>
|
||||
<ul className="tw-menu tw-w-full tw-bg-base-100 tw-text-base-content tw-p-0" data-te-sidenav-menu-ref>
|
||||
{
|
||||
routes.map((route, k) => {
|
||||
return (
|
||||
<li className="" key={k}>
|
||||
{
|
||||
route.submenu ?
|
||||
<SidebarSubmenu {...route} /> :
|
||||
(<NavLink
|
||||
route.submenu
|
||||
? <SidebarSubmenu {...route} />
|
||||
: (<NavLink
|
||||
end
|
||||
target={route.blank ? "_blank" : "_self"}
|
||||
to={`${route.path}${params && '?'+params}`}
|
||||
target={route.blank ? '_blank' : '_self'}
|
||||
to={`${route.path}${params && '?' + params}`}
|
||||
className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => {
|
||||
if (screen.width < 640 && !slim) instance.toggle();
|
||||
if (screen.width < 640 && !slim) instance.toggle()
|
||||
}}>
|
||||
{route.icon}<span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden" data-te-sidenav-slim="false">{route.name}</span>
|
||||
{
|
||||
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
|
||||
(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>)
|
||||
}
|
||||
@ -112,19 +106,21 @@ export function SideBar({ routes, bottomRoutes }: { routes: route[], bottomRoute
|
||||
return (
|
||||
<li className="" key={k}>
|
||||
{
|
||||
route.submenu ?
|
||||
<SidebarSubmenu {...route} /> :
|
||||
(<NavLink
|
||||
route.submenu
|
||||
? <SidebarSubmenu {...route} />
|
||||
: (<NavLink
|
||||
end
|
||||
target={route.blank ? "_blank" : "_self"}
|
||||
target={route.blank ? '_blank' : '_self'}
|
||||
to={route.path}
|
||||
className={({ isActive }) => `${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`} onClick={() => {
|
||||
if (screen.width < 640 && !slim) instance.toggle();
|
||||
if (screen.width < 640 && !slim) instance.toggle()
|
||||
}}>
|
||||
{route.icon}<span className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden" data-te-sidenav-slim="false">{route.name}</span>
|
||||
{
|
||||
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
|
||||
(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>)
|
||||
}
|
||||
@ -135,7 +131,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: route[], bottomRoute
|
||||
}
|
||||
</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>
|
||||
|
||||
@ -1,24 +1,22 @@
|
||||
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
|
||||
import {useEffect, useState} from 'react'
|
||||
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
|
||||
function SidebarSubmenu({submenu, name, icon} : { path: string;
|
||||
function SidebarSubmenu ({ submenu, name, icon } : { path: string;
|
||||
// eslint-disable-next-line no-undef
|
||||
icon: JSX.Element;
|
||||
name: string;
|
||||
submenu?: any | undefined}){
|
||||
const location = useLocation()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
submenu?: any | undefined}) {
|
||||
const location = useLocation()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
|
||||
/** Open Submenu list if path found in routes, this is for directly loading submenu routes first time */
|
||||
useEffect(() => {
|
||||
if(submenu.filter(m => {return m.path === location.pathname})[0])setIsExpanded(true)
|
||||
/** Open Submenu list if path found in routes, this is for directly loading submenu routes first time */
|
||||
useEffect(() => {
|
||||
if (submenu.filter(m => { return m.path === location.pathname })[0])setIsExpanded(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className='flex-col'>
|
||||
|
||||
{/** Route header */}
|
||||
@ -28,27 +26,29 @@ function SidebarSubmenu({submenu, name, icon} : { path: string;
|
||||
</div>
|
||||
|
||||
{/** Submenu list */}
|
||||
<div className={` w-full data-[te-collapse-show]:!hidden `+ (isExpanded ? "" : "hidden")} >
|
||||
<ul className={`menu menu-compact`} >
|
||||
<div className={' w-full data-[te-collapse-show]:!hidden ' + (isExpanded ? '' : 'hidden')} >
|
||||
<ul className={'menu menu-compact'} >
|
||||
{
|
||||
submenu.map((m, k) => {
|
||||
return(
|
||||
return (
|
||||
<li key={k} >
|
||||
<Link to={m.path} className='' >
|
||||
{m.icon}<span className="" data-te-sidenav-slim="false">{m.name}</span>
|
||||
{
|
||||
location.pathname == m.path ? (<span className="absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary "
|
||||
aria-hidden="true"></span>) : null
|
||||
location.pathname === m.path
|
||||
? (<span className="absolute mt-1 mb-1 inset-y-0 left-0 w-1 rounded-tr-md rounded-br-md bg-primary "
|
||||
aria-hidden="true"></span>)
|
||||
: null
|
||||
}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarSubmenu
|
||||
export default SidebarSubmenu
|
||||
|
||||
@ -1,37 +1,36 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useItems } from '../Map/hooks/useItems';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useItems } from '../Map/hooks/useItems'
|
||||
|
||||
export const Sitemap = ({url}:{url:string}) => {
|
||||
const [sitemap, setSitemap] = useState('');
|
||||
export const Sitemap = ({ url }:{url:string}) => {
|
||||
const [sitemap, setSitemap] = useState('')
|
||||
|
||||
const items = useItems();
|
||||
const items = useItems()
|
||||
|
||||
useEffect(() => {
|
||||
if (items.length) {
|
||||
const generateSitemap = () => {
|
||||
let sitemapXML = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
||||
sitemapXML += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`;
|
||||
let sitemapXML = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
sitemapXML += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
|
||||
items.forEach(item => {
|
||||
sitemapXML += ` <url>\n`;
|
||||
sitemapXML += ` <loc>${url}/${item.slug}</loc>\n`;
|
||||
sitemapXML += ` </url>\n`;
|
||||
});
|
||||
sitemapXML += ' <url>\n'
|
||||
sitemapXML += ` <loc>${url}/${item.slug}</loc>\n`
|
||||
sitemapXML += ' </url>\n'
|
||||
})
|
||||
|
||||
sitemapXML += `</urlset>`;
|
||||
return sitemapXML;
|
||||
};
|
||||
sitemapXML += '</urlset>'
|
||||
return sitemapXML
|
||||
}
|
||||
|
||||
setSitemap(generateSitemap());
|
||||
setSitemap(generateSitemap())
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [items]);
|
||||
}, [items])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Sitemap</h1>
|
||||
<textarea value={sitemap} readOnly rows={items.length + 4} cols={80} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,31 +1,27 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
import { AssetsApi } from '../../../types';
|
||||
|
||||
import { useCallback, useState, createContext, useContext } from 'react'
|
||||
|
||||
import * as React from 'react'
|
||||
import { AssetsApi } from '../../../types'
|
||||
|
||||
type UseAssetManagerResult = ReturnType<typeof useAssetsManager>;
|
||||
|
||||
const AssetContext = createContext<UseAssetManagerResult>({
|
||||
api: {} as AssetsApi,
|
||||
setAssetsApi: () => { }
|
||||
});
|
||||
})
|
||||
|
||||
function useAssetsManager(): {
|
||||
function useAssetsManager (): {
|
||||
api: AssetsApi;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setAssetsApi: (api: AssetsApi) => void;
|
||||
} {
|
||||
const [api, setApi] = useState<AssetsApi>({} as AssetsApi);
|
||||
} {
|
||||
const [api, setApi] = useState<AssetsApi>({} as AssetsApi)
|
||||
|
||||
const setAssetsApi = useCallback((api: AssetsApi) => {
|
||||
setApi(api);
|
||||
}, []);
|
||||
setApi(api)
|
||||
}, [])
|
||||
|
||||
return { api, setAssetsApi };
|
||||
return { api, setAssetsApi }
|
||||
}
|
||||
|
||||
export const AssetsProvider: React.FunctionComponent<{
|
||||
@ -34,15 +30,14 @@ export const AssetsProvider: React.FunctionComponent<{
|
||||
<AssetContext.Provider value={useAssetsManager()}>
|
||||
{children}
|
||||
</AssetContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useAssetApi = (): AssetsApi => {
|
||||
const { api } = useContext(AssetContext);
|
||||
return api;
|
||||
};
|
||||
|
||||
|
||||
export const useSetAssetApi = (): UseAssetManagerResult["setAssetsApi"] => {
|
||||
const { setAssetsApi } = useContext(AssetContext);
|
||||
return setAssetsApi;
|
||||
const { api } = useContext(AssetContext)
|
||||
return api
|
||||
}
|
||||
|
||||
export const useSetAssetApi = (): UseAssetManagerResult['setAssetsApi'] => {
|
||||
const { setAssetsApi } = useContext(AssetContext)
|
||||
return setAssetsApi
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export {AppShell} from "./AppShell"
|
||||
export {SideBar} from "./SideBar"
|
||||
export {Content} from "./Content"
|
||||
export {Sitemap} from "./Sitemap"
|
||||
export { AppShell } from './AppShell'
|
||||
export { SideBar } from './SideBar'
|
||||
export { Content } from './Content'
|
||||
export { Sitemap } from './Sitemap'
|
||||
|
||||
@ -2,54 +2,53 @@ import { useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useAuth } from './useAuth'
|
||||
import { MapOverlayPage} from '../Templates'
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
|
||||
export function LoginPage() {
|
||||
export function LoginPage () {
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const [password, setPassword] = useState<string>('')
|
||||
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const { login, loading } = useAuth()
|
||||
|
||||
const { login, loading } = useAuth();
|
||||
const navigate = useNavigate()
|
||||
|
||||
const navigate = useNavigate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onLogin = async () => {
|
||||
await toast.promise(
|
||||
login({ email, password }),
|
||||
{
|
||||
success: {
|
||||
render ({ data }) {
|
||||
navigate('/')
|
||||
return `Hi ${data?.first_name}`
|
||||
},
|
||||
// other options
|
||||
icon: '✌️'
|
||||
},
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
autoClose: 10000
|
||||
},
|
||||
pending: 'logging in ...'
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onLogin = async () => {
|
||||
await toast.promise(
|
||||
login({ email: email, password: password }),
|
||||
{
|
||||
success: {
|
||||
render({ data }) {
|
||||
navigate(`/`);
|
||||
return `Hi ${data?.first_name}`
|
||||
},
|
||||
// other options
|
||||
icon: "✌️",
|
||||
},
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
autoClose: 10000,
|
||||
},
|
||||
pending: 'logging in ...',
|
||||
});
|
||||
useEffect(() => {
|
||||
const keyDownHandler = event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
onLogin()
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [onLogin])
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
onLogin();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', keyDownHandler);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler);
|
||||
};
|
||||
}, [onLogin]);
|
||||
|
||||
return (
|
||||
return (
|
||||
<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>
|
||||
<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" />
|
||||
@ -60,6 +59,5 @@ export function LoginPage() {
|
||||
<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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,39 +2,38 @@ import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useAuth } from './useAuth'
|
||||
import { MapOverlayPage} from '../Templates'
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export function RequestPasswordPage({reset_url}) {
|
||||
export function RequestPasswordPage ({ resetUrl }) {
|
||||
const [email, setEmail] = useState<string>('')
|
||||
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const { requestPasswordReset, loading } = useAuth()
|
||||
|
||||
const { requestPasswordReset, loading } = useAuth();
|
||||
const navigate = useNavigate()
|
||||
|
||||
const navigate = useNavigate();
|
||||
const onReset = async () => {
|
||||
await toast.promise(
|
||||
requestPasswordReset(email, resetUrl),
|
||||
{
|
||||
success: {
|
||||
render () {
|
||||
navigate('/')
|
||||
return 'Check your mailbox'
|
||||
},
|
||||
// other options
|
||||
icon: '📬'
|
||||
},
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
}
|
||||
},
|
||||
pending: 'sending email ...'
|
||||
})
|
||||
}
|
||||
|
||||
const onReset = async () => {
|
||||
await toast.promise(
|
||||
requestPasswordReset( email, reset_url),
|
||||
{
|
||||
success: {
|
||||
render() {
|
||||
navigate(`/`);
|
||||
return `Check your mailbox`
|
||||
},
|
||||
// other options
|
||||
icon: "📬",
|
||||
},
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
},
|
||||
pending: 'sending email ...'
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<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>
|
||||
<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" />
|
||||
@ -42,6 +41,5 @@ export function RequestPasswordPage({reset_url}) {
|
||||
<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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,39 +3,38 @@ import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { toast } from 'react-toastify'
|
||||
import { useAuth } from './useAuth'
|
||||
import { MapOverlayPage} from '../Templates'
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
|
||||
export function SetNewPasswordPage() {
|
||||
export function SetNewPasswordPage () {
|
||||
const [password, setPassword] = useState<string>('')
|
||||
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const { passwordReset, loading } = useAuth()
|
||||
|
||||
const { passwordReset, loading } = useAuth();
|
||||
const navigate = useNavigate()
|
||||
|
||||
const navigate = useNavigate();
|
||||
const onReset = async () => {
|
||||
const token = window.location.search.split('token=')[1]
|
||||
console.log(token)
|
||||
|
||||
const onReset = async () => {
|
||||
const token = window.location.search.split("token=")[1];
|
||||
console.log(token);
|
||||
|
||||
await toast.promise(
|
||||
passwordReset(token, password),
|
||||
{
|
||||
success: {
|
||||
render() {
|
||||
navigate(`/`);
|
||||
return `New password set`
|
||||
},
|
||||
},
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
},
|
||||
pending: 'setting password ...'
|
||||
});
|
||||
}
|
||||
await toast.promise(
|
||||
passwordReset(token, password),
|
||||
{
|
||||
success: {
|
||||
render () {
|
||||
navigate('/')
|
||||
return 'New password set'
|
||||
}
|
||||
},
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
}
|
||||
},
|
||||
pending: 'setting password ...'
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<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>
|
||||
<input type="password" placeholder="Password" onChange={e => setPassword(e.target.value)} className="tw-input tw-input-bordered tw-w-full tw-max-w-xs" />
|
||||
@ -43,6 +42,5 @@ export function SetNewPasswordPage() {
|
||||
<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,55 +5,53 @@ import { toast } from 'react-toastify'
|
||||
import { useAuth } from './useAuth'
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
|
||||
export function SignupPage() {
|
||||
export function SignupPage () {
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const [userName, setUserName] = useState<string>('')
|
||||
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>('')
|
||||
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const { register, loading } = useAuth()
|
||||
|
||||
const { register, loading } = useAuth();
|
||||
const navigate = useNavigate()
|
||||
|
||||
const navigate = useNavigate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onRegister = async () => {
|
||||
await toast.promise(
|
||||
register({ email, password }, userName),
|
||||
{
|
||||
success: {
|
||||
render ({ data }) {
|
||||
navigate('/')
|
||||
return `Hi ${data?.first_name}`
|
||||
},
|
||||
// other options
|
||||
icon: '✌️'
|
||||
},
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
autoClose: 10000
|
||||
},
|
||||
pending: 'creating new user ...'
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onRegister = async () => {
|
||||
await toast.promise(
|
||||
register({ email: email, password: password }, userName),
|
||||
{
|
||||
success: {
|
||||
render({ data }) {
|
||||
navigate(`/`);
|
||||
return `Hi ${data?.first_name}`
|
||||
},
|
||||
// other options
|
||||
icon: "✌️",
|
||||
},
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
autoClose: 10000,
|
||||
},
|
||||
pending: 'creating new user ...'
|
||||
});
|
||||
useEffect(() => {
|
||||
const keyDownHandler = event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
onRegister()
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [onRegister])
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
onRegister();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', keyDownHandler);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler);
|
||||
};
|
||||
}, [onRegister]);
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
<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>
|
||||
<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" />
|
||||
@ -63,6 +61,5 @@ export function SignupPage() {
|
||||
<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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export {AuthProvider, useAuth} from "./useAuth"
|
||||
export {LoginPage} from "./LoginPage"
|
||||
export {SignupPage} from './SignupPage'
|
||||
export {RequestPasswordPage} from './RequestPasswordPage'
|
||||
export {SetNewPasswordPage} from './SetNewPasswordPage'
|
||||
export { AuthProvider, useAuth } from './useAuth'
|
||||
export { LoginPage } from './LoginPage'
|
||||
export { SignupPage } from './SignupPage'
|
||||
export { RequestPasswordPage } from './RequestPasswordPage'
|
||||
export { SetNewPasswordPage } from './SetNewPasswordPage'
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { createContext, useState, useContext, useEffect } from "react";
|
||||
import * as React from "react";
|
||||
import { UserApi, UserItem } from "../../types";
|
||||
|
||||
import { createContext, useState, useContext, useEffect } from 'react'
|
||||
import * as React from 'react'
|
||||
import { UserApi, UserItem } from '../../types'
|
||||
|
||||
type AuthProviderProps = {
|
||||
userApi: UserApi,
|
||||
@ -14,8 +13,6 @@ type AuthCredentials = {
|
||||
otp?: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
type AuthContextProps = {
|
||||
isAuthenticated: boolean,
|
||||
user: UserItem | null;
|
||||
@ -37,130 +34,123 @@ type AuthContextProps = {
|
||||
const AuthContext = createContext<AuthContextProps>({
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
login: () => Promise.reject(),
|
||||
register: () => Promise.reject(),
|
||||
login: () => Promise.reject(Error('Unimplemented')),
|
||||
register: () => Promise.reject(Error('Unimplemented')),
|
||||
loading: false,
|
||||
logout: () => Promise.reject(),
|
||||
updateUser: () => Promise.reject(),
|
||||
token: "",
|
||||
requestPasswordReset: () => Promise.reject(),
|
||||
passwordReset: () => Promise.reject()
|
||||
});
|
||||
logout: () => Promise.reject(Error('Unimplemented')),
|
||||
updateUser: () => Promise.reject(Error('Unimplemented')),
|
||||
token: '',
|
||||
requestPasswordReset: () => Promise.reject(Error('Unimplemented')),
|
||||
passwordReset: () => Promise.reject(Error('Unimplemented'))
|
||||
})
|
||||
|
||||
export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
||||
const [user, setUser] = useState<UserItem | null>(null);
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const isAuthenticated = !!user;
|
||||
const [user, setUser] = useState<UserItem | null>(null)
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const isAuthenticated = !!user
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
loadUser();
|
||||
setLoading(true)
|
||||
loadUser()
|
||||
setLoading(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
async function loadUser(): Promise<UserItem | undefined> {
|
||||
async function loadUser (): Promise<UserItem | undefined> {
|
||||
try {
|
||||
const token = await userApi.getToken();
|
||||
setToken(token);
|
||||
if(token){
|
||||
const me = await userApi.getUser();
|
||||
setUser(me as UserItem);
|
||||
setLoading(false);
|
||||
return me as UserItem;
|
||||
}
|
||||
else return undefined;
|
||||
|
||||
const token = await userApi.getToken()
|
||||
setToken(token)
|
||||
if (token) {
|
||||
const me = await userApi.getUser()
|
||||
setUser(me as UserItem)
|
||||
setLoading(false)
|
||||
return me as UserItem
|
||||
} else return undefined
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const login = async (credentials: AuthCredentials): Promise<UserItem | undefined> => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await userApi.login(credentials.email, credentials.password);
|
||||
setToken(res.access_token);
|
||||
return (await loadUser());
|
||||
const res = await userApi.login(credentials.email, credentials.password)
|
||||
setToken(res.access_token)
|
||||
return (await loadUser())
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
setLoading(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const register = async (credentials: AuthCredentials, userName): Promise<UserItem | undefined> => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
try {
|
||||
/* const res = */ await userApi.register(credentials.email, credentials.password, userName)
|
||||
return (await login(credentials));
|
||||
return (await login(credentials))
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
setLoading(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await userApi.logout();
|
||||
setUser(null);
|
||||
await userApi.logout()
|
||||
setUser(null)
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
setLoading(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const updateUser = async (user: UserItem) => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { id, ...userRest } = user;
|
||||
const { id, ...userRest } = user
|
||||
|
||||
try {
|
||||
const res = await userApi.updateUser(userRest);
|
||||
setUser(res as any);
|
||||
loadUser();
|
||||
setLoading(false);
|
||||
return res as any;
|
||||
const res = await userApi.updateUser(userRest)
|
||||
setUser(res as any)
|
||||
loadUser()
|
||||
setLoading(false)
|
||||
return res as any
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
setLoading(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const requestPasswordReset = async (email: string, reset_url?: string): Promise<any> => {
|
||||
setLoading(true);
|
||||
const requestPasswordReset = async (email: string, resetUrl?: string): Promise<any> => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await userApi.requestPasswordReset(email, reset_url);
|
||||
return setLoading(false);
|
||||
await userApi.requestPasswordReset(email, resetUrl)
|
||||
return setLoading(false)
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
setLoading(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const passwordReset = async (token: string, new_password:string): Promise<any> => {
|
||||
setLoading(true);
|
||||
const passwordReset = async (token: string, newPassword:string): Promise<any> => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await userApi.passwordReset(token, new_password);
|
||||
return setLoading(false);
|
||||
await userApi.passwordReset(token, newPassword)
|
||||
return setLoading(false)
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
setLoading(false)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ isAuthenticated, user, login, register, loading, logout, updateUser, token, requestPasswordReset, passwordReset }}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
export const useAuth = () => useContext(AuthContext);
|
||||
)
|
||||
}
|
||||
export const useAuth = () => useContext(AuthContext)
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import * as React from "react"
|
||||
import { useEffect } from "react"
|
||||
import * as React 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(() => {
|
||||
if (showOnStartup) { window.my_modal_3.showModal() }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if(showOnStartup)
|
||||
window.my_modal_3.showModal()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
|
||||
{/* You can open the modal using ID.showModal() method */}
|
||||
@ -24,6 +21,5 @@ export function Modal({children, showOnStartup}:{children : React.ReactNode, sho
|
||||
</form>
|
||||
</dialog>
|
||||
</>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,32 +1,30 @@
|
||||
import { useQuestsOpen, useSetQuestOpen } from './hooks/useQuests';
|
||||
import { useAuth } from '../Auth';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useItems } from '../Map/hooks/useItems';
|
||||
import { Item } from '../../types';
|
||||
import { useQuestsOpen, useSetQuestOpen } from './hooks/useQuests'
|
||||
import { useAuth } from '../Auth'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useItems } from '../Map/hooks/useItems'
|
||||
import { Item } from '../../types'
|
||||
|
||||
export function Quests() {
|
||||
export function Quests () {
|
||||
const questsOpen = useQuestsOpen()
|
||||
const setQuestsOpen = useSetQuestOpen()
|
||||
const { isAuthenticated, user } = useAuth()
|
||||
|
||||
const questsOpen = useQuestsOpen();
|
||||
const setQuestsOpen = useSetQuestOpen();
|
||||
const { isAuthenticated, user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
setQuestsOpen(false);
|
||||
useEffect(() => {
|
||||
setQuestsOpen(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [])
|
||||
|
||||
const [profile, setProfie] = useState<Item>()
|
||||
|
||||
const items = useItems();
|
||||
const [profile, setProfie] = useState<Item>()
|
||||
|
||||
useEffect(() => {
|
||||
setProfie(items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name == "user" && i.user_created?.id != null))
|
||||
}, [items, user])
|
||||
|
||||
const items = useItems()
|
||||
|
||||
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]">
|
||||
useEffect(() => {
|
||||
setProfie(items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name === 'user' && i.user_created?.id != null))
|
||||
}, [items, user])
|
||||
|
||||
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]">
|
||||
<div className="tw-card-body tw-p-4 tw-pt-0">
|
||||
<div className="tw-card-actions tw-justify-end">
|
||||
<label className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-1 tw-top-1" onClick={() => setQuestsOpen(false)}>✕</label>
|
||||
@ -39,17 +37,15 @@ export function Quests() {
|
||||
|
||||
</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 ? isAuthenticated : false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Sign Up</span></label></li>
|
||||
<li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={profile?.text ? true : false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Fill Profile</span></label></li>
|
||||
<li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={profile?.image ? true : false} /><span className='tw-text-sm tw-label-text tw-mx-2'>Upload Avatar</span></label></li>
|
||||
<li><label className="tw-label tw-justify-normal tw-pt-1 tw-pb-0"><input type="checkbox" readOnly={true} className="tw-checkbox tw-checkbox-xs tw-checkbox-success" checked={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,28 +1,25 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
|
||||
|
||||
import { useCallback, useState, createContext, useContext } from 'react'
|
||||
import * as React from 'react'
|
||||
|
||||
type UseQuestManagerResult = ReturnType<typeof useQuestsManager>;
|
||||
|
||||
const QuestContext = createContext<UseQuestManagerResult>({
|
||||
open: false,
|
||||
setQuestsOpen: () => { }
|
||||
});
|
||||
})
|
||||
|
||||
function useQuestsManager(initialOpen: boolean): {
|
||||
function useQuestsManager (initialOpen: boolean): {
|
||||
open: boolean;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setQuestsOpen: (open: boolean) => void;
|
||||
} {
|
||||
const [open, setOpen] = useState<boolean>(initialOpen);
|
||||
const [open, setOpen] = useState<boolean>(initialOpen)
|
||||
|
||||
const setQuestsOpen = useCallback((questOpen: boolean) => {
|
||||
setOpen(questOpen);
|
||||
}, []);
|
||||
setOpen(questOpen)
|
||||
}, [])
|
||||
|
||||
return { open, setQuestsOpen };
|
||||
return { open, setQuestsOpen }
|
||||
}
|
||||
|
||||
export const QuestsProvider: React.FunctionComponent<{
|
||||
@ -31,15 +28,14 @@ export const QuestsProvider: React.FunctionComponent<{
|
||||
<QuestContext.Provider value={useQuestsManager(initialOpen)}>
|
||||
{children}
|
||||
</QuestContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useQuestsOpen = (): boolean => {
|
||||
const { open } = useContext(QuestContext);
|
||||
return open;
|
||||
};
|
||||
|
||||
|
||||
export const useSetQuestOpen = (): UseQuestManagerResult["setQuestsOpen"] => {
|
||||
const { setQuestsOpen } = useContext(QuestContext);
|
||||
return setQuestsOpen;
|
||||
const { open } = useContext(QuestContext)
|
||||
return open
|
||||
}
|
||||
|
||||
export const useSetQuestOpen = (): UseQuestManagerResult['setQuestsOpen'] => {
|
||||
const { setQuestsOpen } = useContext(QuestContext)
|
||||
return setQuestsOpen
|
||||
}
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export {Modal} from './Modal'
|
||||
export {Quests} from './Quests'
|
||||
export { Modal } from './Modal'
|
||||
export { Quests } from './Quests'
|
||||
|
||||
@ -1,78 +1,76 @@
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react';
|
||||
import { TagView } from '../Templates/TagView';
|
||||
import { useEffect } from 'react'
|
||||
import { TagView } from '../Templates/TagView'
|
||||
|
||||
// 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 }) => {
|
||||
|
||||
const [filteredSuggestions, setFilteredSuggestions] = React.useState<Array<any>>([]);
|
||||
const [heighlightedSuggestion, setHeighlightedSuggestion] = React.useState<number>(0);
|
||||
|
||||
const [filteredSuggestions, setFilteredSuggestions] = React.useState<Array<any>>([])
|
||||
const [heighlightedSuggestion, setHeighlightedSuggestion] = React.useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
pushFilteredSuggestions && setFilteredSuggestions(pushFilteredSuggestions)
|
||||
}, [pushFilteredSuggestions])
|
||||
|
||||
useEffect(() => {
|
||||
setFocus && inputRef.current?.focus();
|
||||
setFocus && inputRef.current?.focus()
|
||||
}, [setFocus])
|
||||
|
||||
const inputRef = React.useRef<HTMLInputElement>();
|
||||
|
||||
const inputRef = React.useRef<HTMLInputElement>()
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const getSuggestionValue = suggestion => suggestion.name;
|
||||
const getSuggestionValue = suggestion => suggestion.name
|
||||
|
||||
const getSuggestions = value => {
|
||||
const inputValue = value.trim().toLowerCase();
|
||||
const inputLength = inputValue.length;
|
||||
const inputValue = value.trim().toLowerCase()
|
||||
const inputLength = inputValue.length
|
||||
|
||||
return inputLength === 0 ? [] : suggestions.filter(tag =>
|
||||
tag.name.toLowerCase().slice(0, inputLength) === inputValue
|
||||
);
|
||||
};
|
||||
return inputLength === 0
|
||||
? []
|
||||
: suggestions.filter(tag =>
|
||||
tag.name.toLowerCase().slice(0, inputLength) === inputValue
|
||||
)
|
||||
}
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFilteredSuggestions(getSuggestions(e.target.value))
|
||||
|
||||
// Call the parent's onChange handler, if it exists
|
||||
if (inputProps.onChange) {
|
||||
inputProps.onChange(e);
|
||||
inputProps.onChange(e)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function handleSuggestionClick(suggestion) {
|
||||
function handleSuggestionClick (suggestion) {
|
||||
onSelected(suggestion)
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
const handleKeyDown = (event) => {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
heighlightedSuggestion < filteredSuggestions.length-1 && setHeighlightedSuggestion(current => current +1)
|
||||
break;
|
||||
heighlightedSuggestion < filteredSuggestions.length - 1 && setHeighlightedSuggestion(current => current + 1)
|
||||
break
|
||||
case 'ArrowUp':
|
||||
heighlightedSuggestion>0 && setHeighlightedSuggestion(current => current -1)
|
||||
break;
|
||||
heighlightedSuggestion > 0 && setHeighlightedSuggestion(current => current - 1)
|
||||
break
|
||||
case 'Enter':
|
||||
if(filteredSuggestions.length > 0) {
|
||||
onSelected(filteredSuggestions[heighlightedSuggestion]);
|
||||
setHeighlightedSuggestion(0);
|
||||
if (filteredSuggestions.length > 0) {
|
||||
onSelected(filteredSuggestions[heighlightedSuggestion])
|
||||
setHeighlightedSuggestion(0)
|
||||
}
|
||||
filteredSuggestions.length == 0 && inputProps.onKeyDown(event);
|
||||
break;
|
||||
filteredSuggestions.length === 0 && inputProps.onKeyDown(event)
|
||||
break
|
||||
default:
|
||||
inputProps.onKeyDown(event);
|
||||
break;
|
||||
inputProps.onKeyDown(event)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} tabIndex="-1" onKeyDown={handleKeyDown}/>
|
||||
<ul className={`tw-absolute tw-z-[4000] ${filteredSuggestions.length>0 && 'tw-bg-base-100 tw-rounded-xl tw-p-2'}`}>
|
||||
<ul className={`tw-absolute tw-z-[4000] ${filteredSuggestions.length > 0 && 'tw-bg-base-100 tw-rounded-xl tw-p-2'}`}>
|
||||
{filteredSuggestions.map((suggestion, index) => (
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState } from "react"
|
||||
import * as React from "react"
|
||||
import { useState } from 'react'
|
||||
import * as React from 'react'
|
||||
|
||||
interface ComboBoxProps {
|
||||
id?: string;
|
||||
@ -10,15 +10,14 @@ interface ComboBoxProps {
|
||||
}
|
||||
|
||||
const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => {
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [selectedValue, setSelectedValue] = useState(value);
|
||||
const [selectedValue, setSelectedValue] = useState(value)
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value;
|
||||
setSelectedValue(value);
|
||||
onValueChange(value);
|
||||
};
|
||||
const value = e.target.value
|
||||
setSelectedValue(value)
|
||||
onValueChange(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
@ -27,10 +26,10 @@ const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) =>
|
||||
onChange={handleChange}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default ComboBoxInput;
|
||||
export default ComboBoxInput
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
type SelectBoxProps = {
|
||||
labelTitle?: string;
|
||||
@ -11,42 +10,39 @@ type SelectBoxProps = {
|
||||
defaultValue: string;
|
||||
placeholder?: string;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
updateFormValue: (value: string ) => void;
|
||||
updateFormValue: (value: string) => void;
|
||||
|
||||
options: {name: string, value: string}[];
|
||||
labelDescription?: string
|
||||
}
|
||||
|
||||
export function SelectBox(props : SelectBoxProps){
|
||||
|
||||
const {labelTitle, labelDescription, defaultValue, containerStyle, placeholder, labelStyle, options, updateFormValue} = props
|
||||
export function SelectBox (props : SelectBoxProps) {
|
||||
const { labelTitle, labelDescription, defaultValue, containerStyle, placeholder, labelStyle, options, updateFormValue } = props
|
||||
|
||||
const [value, setValue] = useState(defaultValue || "")
|
||||
const [value, setValue] = useState(defaultValue || '')
|
||||
|
||||
const updateValue = (newValue: string) => {
|
||||
updateFormValue(newValue)
|
||||
setValue(newValue)
|
||||
}
|
||||
|
||||
const updateValue = (newValue: string) =>{
|
||||
updateFormValue(newValue)
|
||||
setValue(newValue)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className={`tw-inline-block ${containerStyle}`}>
|
||||
{labelTitle?
|
||||
<label className={`tw-label ${labelStyle}`}>
|
||||
{labelTitle
|
||||
? <label className={`tw-label ${labelStyle}`}>
|
||||
<div className="tw-label-text">{labelTitle}
|
||||
{labelDescription && <div className="tw-tooltip tw-tooltip-right" data-tip={labelDescription}><InformationCircleIcon className='tw-w-4 tw-h-4'/></div>}
|
||||
</div>
|
||||
</label>
|
||||
: ""}
|
||||
: ''}
|
||||
<select 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>
|
||||
return <option value={o.value || o.name} key={k}>{o.name}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Tribute from "tributejs";
|
||||
import { useTags } from "../Map/hooks/useTags";
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Tribute from 'tributejs'
|
||||
import { useTags } from '../Map/hooks/useTags'
|
||||
|
||||
type TextAreaProps = {
|
||||
labelTitle?: string;
|
||||
@ -19,73 +19,75 @@ interface KeyValue {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export function TextAreaInput({ labelTitle, dataField, labelStyle, containerStyle, inputStyle, defaultValue, placeholder, updateFormValue }: TextAreaProps) {
|
||||
const ref = useRef<HTMLTextAreaElement>(null);
|
||||
const [inputValue, setInputValue] = useState<string>(defaultValue);
|
||||
export function TextAreaInput ({ labelTitle, dataField, labelStyle, containerStyle, inputStyle, defaultValue, placeholder, updateFormValue }: TextAreaProps) {
|
||||
const ref = useRef<HTMLTextAreaElement>(null)
|
||||
const [inputValue, setInputValue] = useState<string>(defaultValue)
|
||||
|
||||
// prevent react18 from calling useEffect twice
|
||||
const init = useRef(false);
|
||||
// prevent react18 from calling useEffect twice
|
||||
const init = useRef(false)
|
||||
|
||||
const tags = useTags();
|
||||
const tags = useTags()
|
||||
|
||||
const values: KeyValue[] = [];
|
||||
const values: KeyValue[] = []
|
||||
|
||||
tags.forEach(tag => {
|
||||
values.push({ key: tag.name, value: tag.name, color: tag.color });
|
||||
});
|
||||
tags.forEach(tag => {
|
||||
values.push({ key: tag.name, value: tag.name, color: tag.color })
|
||||
})
|
||||
|
||||
const tribute = new Tribute({
|
||||
containerClass: 'tw-z-3000 tw-bg-base-100 tw-p-2 tw-rounded-lg tw-shadow',
|
||||
selectClass: 'tw-font-bold',
|
||||
trigger: "#",
|
||||
values: values,
|
||||
menuShowMinLength: 3,
|
||||
noMatchTemplate: () => {
|
||||
return ""
|
||||
},
|
||||
menuItemTemplate: function (item) {
|
||||
return `<span style="color: ${item.original.color}; padding: 5px; border-radius: 3px;">#${item.string}</span>`;
|
||||
}
|
||||
});
|
||||
const tribute = new Tribute({
|
||||
containerClass: 'tw-z-3000 tw-bg-base-100 tw-p-2 tw-rounded-lg tw-shadow',
|
||||
selectClass: 'tw-font-bold',
|
||||
trigger: '#',
|
||||
values,
|
||||
menuShowMinLength: 3,
|
||||
noMatchTemplate: () => {
|
||||
return ''
|
||||
},
|
||||
menuItemTemplate: function (item) {
|
||||
return `<span style="color: ${item.original.color}; padding: 5px; border-radius: 3px;">#${item.string}</span>`
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
if (ref.current) {
|
||||
tribute.attach(ref.current);
|
||||
}
|
||||
init.current = true;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
if (ref.current) {
|
||||
tribute.attach(ref.current)
|
||||
}
|
||||
init.current = true
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ref]);
|
||||
}, [ref])
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(defaultValue);
|
||||
}, [defaultValue]);
|
||||
useEffect(() => {
|
||||
setInputValue(defaultValue)
|
||||
}, [defaultValue])
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setInputValue(newValue);
|
||||
if (updateFormValue) {
|
||||
updateFormValue(newValue);
|
||||
}
|
||||
};
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value
|
||||
setInputValue(newValue)
|
||||
if (updateFormValue) {
|
||||
updateFormValue(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`tw-form-control tw-w-full ${containerStyle ? containerStyle : ""}`}>
|
||||
{labelTitle ? (
|
||||
return (
|
||||
<div className={`tw-form-control tw-w-full ${containerStyle || ''}`}>
|
||||
{labelTitle
|
||||
? (
|
||||
<label className="tw-label">
|
||||
<span className={`tw-label-text tw-text-base-content ${labelStyle}`}>{labelTitle}</span>
|
||||
</label>
|
||||
) : null}
|
||||
)
|
||||
: null}
|
||||
<textarea
|
||||
required
|
||||
ref={ref}
|
||||
value={inputValue}
|
||||
name={dataField}
|
||||
className={`tw-textarea tw-textarea-bordered tw-w-full tw-leading-5 ${inputStyle || ""}`}
|
||||
placeholder={placeholder || ""}
|
||||
className={`tw-textarea tw-textarea-bordered tw-w-full tw-leading-5 ${inputStyle || ''}`}
|
||||
placeholder={placeholder || ''}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import * as React from "react"
|
||||
import { useEffect, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
|
||||
type InputTextProps = {
|
||||
labelTitle?: string;
|
||||
@ -12,42 +12,43 @@ type InputTextProps = {
|
||||
placeholder?: string;
|
||||
autocomplete?: string
|
||||
// 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) {
|
||||
const [inputValue, setInputValue] = useState<string>(defaultValue || '')
|
||||
|
||||
export function TextInput({ labelTitle, labelStyle, type, dataField, containerStyle, inputStyle, defaultValue, placeholder, autocomplete, updateFormValue }: InputTextProps) {
|
||||
const [inputValue, setInputValue] = useState<string>(defaultValue || "");
|
||||
useEffect(() => {
|
||||
setInputValue(defaultValue || '')
|
||||
}, [defaultValue])
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(defaultValue || "");
|
||||
}, [defaultValue]);
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value
|
||||
setInputValue(newValue)
|
||||
if (updateFormValue) {
|
||||
updateFormValue(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setInputValue(newValue);
|
||||
if (updateFormValue) {
|
||||
updateFormValue(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className={`tw-form-control ${containerStyle}`}>
|
||||
{labelTitle ? (
|
||||
{labelTitle
|
||||
? (
|
||||
<label className="tw-label">
|
||||
<span className={`tw-label-text tw-text-base-content ${labelStyle}`}>{labelTitle}</span>
|
||||
</label>
|
||||
) : null}
|
||||
)
|
||||
: null}
|
||||
<input
|
||||
required
|
||||
type={type || "text"}
|
||||
type={type || 'text'}
|
||||
name={dataField}
|
||||
value={inputValue}
|
||||
placeholder={placeholder || ""}
|
||||
placeholder={placeholder || ''}
|
||||
autoComplete={autocomplete}
|
||||
onChange={handleChange}
|
||||
className={`tw-input tw-input-bordered tw-w-full ${inputStyle || ""}`}
|
||||
className={`tw-input tw-input-bordered tw-w-full ${inputStyle || ''}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export {TextAreaInput} from "./TextAreaInput"
|
||||
export {TextInput} from "./TextInput"
|
||||
export {SelectBox} from "./SelectBox"
|
||||
export { TextAreaInput } from './TextAreaInput'
|
||||
export { TextInput } from './TextInput'
|
||||
export { SelectBox } from './SelectBox'
|
||||
|
||||
@ -3,30 +3,31 @@ import { Item } from '../../types'
|
||||
import * as PropTypes from 'prop-types'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
|
||||
export const ItemForm = ({ children, item, title, setPopupTitle }: { children?: React.ReactNode, item?: Item, title?: string, setPopupTitle?: React.Dispatch<React.SetStateAction<string>> }) => {
|
||||
useEffect(() => {
|
||||
setPopupTitle&& title && setPopupTitle(title);
|
||||
useEffect(() => {
|
||||
setPopupTitle && title && setPopupTitle(title)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [title])
|
||||
|
||||
return (
|
||||
}, [title])
|
||||
|
||||
return (
|
||||
<div>{
|
||||
children ?
|
||||
React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ item: Item, test: string }>(child) ?
|
||||
React.cloneElement(child, { item: item, test: "test" }) : ""
|
||||
) : ""
|
||||
children
|
||||
? React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ item: Item, test: string }>(child)
|
||||
? React.cloneElement(child, { item, test: 'test' })
|
||||
: ''
|
||||
)
|
||||
: ''
|
||||
}</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ItemForm.propTypes = {
|
||||
children: PropTypes.node,
|
||||
__TYPE: PropTypes.string,
|
||||
};
|
||||
children: PropTypes.node,
|
||||
__TYPE: PropTypes.string
|
||||
}
|
||||
|
||||
ItemForm.defaultProps = {
|
||||
__TYPE: 'ItemForm',
|
||||
};
|
||||
__TYPE: 'ItemForm'
|
||||
}
|
||||
|
||||
@ -2,24 +2,25 @@ import * as React from 'react'
|
||||
import { Item } from '../../types'
|
||||
import * as PropTypes from 'prop-types'
|
||||
|
||||
|
||||
export const ItemView = ({ children, item }: { children?: React.ReactNode, item?: Item }) => {
|
||||
return (
|
||||
<div>
|
||||
{children ?
|
||||
React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ item: Item }>(child) ?
|
||||
React.cloneElement(child, { item: item }) : ""
|
||||
) : ""}
|
||||
{children
|
||||
? React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ item: Item }>(child)
|
||||
? React.cloneElement(child, { item })
|
||||
: ''
|
||||
)
|
||||
: ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ItemView.propTypes = {
|
||||
children: PropTypes.node,
|
||||
__TYPE: PropTypes.string,
|
||||
};
|
||||
__TYPE: PropTypes.string
|
||||
}
|
||||
|
||||
ItemView.defaultProps = {
|
||||
__TYPE: 'ItemView',
|
||||
};
|
||||
__TYPE: 'ItemView'
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import { useFilterTags, useIsGroupTypeVisible, useIsLayerVisible, useVisibleGrou
|
||||
import { useAddTag, useAllTagsLoaded, useGetItemTags, useTags } from './hooks/useTags'
|
||||
import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs'
|
||||
import { Popup } from 'leaflet'
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { getValue } from '../../Utils/GetValue'
|
||||
import { hashTagRegex } from '../../Utils/HashTagRegex'
|
||||
import { randomColor } from '../../Utils/RandomColor'
|
||||
@ -18,218 +18,209 @@ import { encodeTag } from '../../Utils/FormatTags'
|
||||
import { useSelectPosition, useSetMarkerClicked } from './hooks/useSelectPosition'
|
||||
|
||||
export const Layer = ({
|
||||
data,
|
||||
children,
|
||||
name = 'places',
|
||||
menuIcon = 'MapPinIcon',
|
||||
menuText = 'add new place',
|
||||
menuColor = '#2E7D32',
|
||||
markerIcon = 'circle-solid',
|
||||
markerShape = 'circle',
|
||||
markerDefaultColor = '#777',
|
||||
markerDefaultColor2 = "RGBA(35, 31, 32, 0.2)",
|
||||
api,
|
||||
itemType,
|
||||
itemNameField = 'name',
|
||||
itemSubnameField,
|
||||
itemTextField = 'text',
|
||||
itemAvatarField,
|
||||
itemColorField,
|
||||
itemOwnerField,
|
||||
itemLatitudeField = 'position.coordinates.1',
|
||||
itemLongitudeField = 'position.coordinates.0',
|
||||
itemTagsField,
|
||||
itemOffersField,
|
||||
itemNeedsField,
|
||||
onlyOnePerOwner = false,
|
||||
customEditLink,
|
||||
customEditParameter,
|
||||
public_edit_items,
|
||||
listed = true,
|
||||
setItemFormPopup,
|
||||
itemFormPopup,
|
||||
clusterRef
|
||||
data,
|
||||
children,
|
||||
name = 'places',
|
||||
menuIcon = 'MapPinIcon',
|
||||
menuText = 'add new place',
|
||||
menuColor = '#2E7D32',
|
||||
markerIcon = 'circle-solid',
|
||||
markerShape = 'circle',
|
||||
markerDefaultColor = '#777',
|
||||
markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)',
|
||||
api,
|
||||
itemType,
|
||||
itemNameField = 'name',
|
||||
itemSubnameField,
|
||||
itemTextField = 'text',
|
||||
itemAvatarField,
|
||||
itemColorField,
|
||||
itemOwnerField,
|
||||
itemLatitudeField = 'position.coordinates.1',
|
||||
itemLongitudeField = 'position.coordinates.0',
|
||||
itemTagsField,
|
||||
itemOffersField,
|
||||
itemNeedsField,
|
||||
onlyOnePerOwner = false,
|
||||
customEditLink,
|
||||
customEditParameter,
|
||||
// eslint-disable-next-line camelcase
|
||||
public_edit_items,
|
||||
listed = true,
|
||||
setItemFormPopup,
|
||||
itemFormPopup,
|
||||
clusterRef
|
||||
}: LayerProps) => {
|
||||
const filterTags = useFilterTags()
|
||||
|
||||
const filterTags = useFilterTags();
|
||||
const items = useItems()
|
||||
const setItemsApi = useSetItemsApi()
|
||||
const setItemsData = useSetItemsData()
|
||||
const getItemTags = useGetItemTags()
|
||||
const addMarker = useAddMarker()
|
||||
const addPopup = useAddPopup()
|
||||
const leafletRefs = useLeafletRefs()
|
||||
|
||||
const items = useItems();
|
||||
const setItemsApi = useSetItemsApi();
|
||||
const setItemsData = useSetItemsData();
|
||||
const getItemTags = useGetItemTags();
|
||||
const addMarker = useAddMarker();
|
||||
const addPopup = useAddPopup();
|
||||
const leafletRefs = useLeafletRefs();
|
||||
const location = useLocation()
|
||||
|
||||
const location = useLocation();
|
||||
const allTagsLoaded = useAllTagsLoaded()
|
||||
const allItemsLoaded = useAllItemsLoaded()
|
||||
|
||||
const allTagsLoaded = useAllTagsLoaded();
|
||||
const allItemsLoaded = useAllItemsLoaded();
|
||||
const setMarkerClicked = useSetMarkerClicked()
|
||||
const selectPosition = useSelectPosition()
|
||||
|
||||
const setMarkerClicked = useSetMarkerClicked();
|
||||
const selectPosition = useSelectPosition();
|
||||
const tags = useTags()
|
||||
const addTag = useAddTag()
|
||||
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
|
||||
const [tagsReady, setTagsReady] = useState<boolean>(false)
|
||||
|
||||
const tags = useTags();
|
||||
const addTag = useAddTag();
|
||||
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([]);
|
||||
const [tagsReady, setTagsReady] = useState<boolean>(false);
|
||||
const map = useMap()
|
||||
|
||||
const isLayerVisible = useIsLayerVisible()
|
||||
|
||||
const map = useMap();
|
||||
const isGroupTypeVisible = useIsGroupTypeVisible()
|
||||
|
||||
const isLayerVisible = useIsLayerVisible();
|
||||
const visibleGroupTypes = useVisibleGroupType()
|
||||
|
||||
const isGroupTypeVisible = useIsGroupTypeVisible();
|
||||
|
||||
const visibleGroupTypes = useVisibleGroupType();
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, markerDefaultColor2, api, itemType, itemNameField, itemSubnameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, customEditParameter, public_edit_items, listed, setItemFormPopup, itemFormPopup, clusterRef });
|
||||
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 });
|
||||
useEffect(() => {
|
||||
// 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 })
|
||||
// eslint-disable-next-line camelcase
|
||||
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, markerDefaultColor2, api, itemType, itemNameField, itemSubnameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, customEditParameter, public_edit_items, listed, setItemFormPopup, itemFormPopup, clusterRef })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, api])
|
||||
}, [data, api])
|
||||
|
||||
useMapEvents({
|
||||
popupopen: (e) => {
|
||||
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) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (!location.pathname.includes("/item/")) {
|
||||
window.history.pushState({}, "", `/${item.id}` + `${params.toString() !== "" ? `?${params}` : ""}`)
|
||||
}
|
||||
let title = "";
|
||||
if (item.name) title = item.name;
|
||||
else if (item.layer?.itemNameField) title = getValue(item, item.layer.itemNameField);
|
||||
document.title = `${document.title.split("-")[0]} - ${title}`;
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const openPopup = () => {
|
||||
if (window.location.pathname.split("/").length <= 1 || window.location.pathname.split("/")[1] === "") {
|
||||
map.closePopup();
|
||||
}
|
||||
else {
|
||||
if (window.location.pathname.split("/")[1]) {
|
||||
const id = window.location.pathname.split("/")[1]
|
||||
const ref = leafletRefs[id];
|
||||
if (ref?.marker && ref.item.layer?.name === name) {
|
||||
ref.marker && clusterRef.hasLayer(ref.marker) && clusterRef?.zoomToShowLayer(ref.marker, () => {
|
||||
ref.marker.openPopup();
|
||||
});
|
||||
let title = "";
|
||||
if (ref.item.name) title = ref.item.name;
|
||||
else if (ref.item.layer?.itemNameField) title = getValue(ref.item.name, ref.item.layer.itemNameField);
|
||||
document.title = `${document.title.split("-")[0]} - ${title}`;
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute("content", ref.item.name);
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute("content", ref.item.text);
|
||||
}
|
||||
}
|
||||
useMapEvents({
|
||||
popupopen: (e) => {
|
||||
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) {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
if (!location.pathname.includes('/item/')) {
|
||||
window.history.pushState({}, '', `/${item.id}` + `${params.toString() !== '' ? `?${params}` : ''}`)
|
||||
}
|
||||
let title = ''
|
||||
if (item.name) title = item.name
|
||||
else if (item.layer?.itemNameField) title = getValue(item, item.layer.itemNameField)
|
||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
openPopup();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [leafletRefs, location])
|
||||
|
||||
useEffect(() => {
|
||||
if (tagsReady) {
|
||||
const processedTags = {};
|
||||
newTagsToAdd.map(newtag => {
|
||||
if (!processedTags[newtag.name]) {
|
||||
processedTags[newtag.name] = true;
|
||||
addTag(newtag);
|
||||
}
|
||||
})
|
||||
const openPopup = () => {
|
||||
if (window.location.pathname.split('/').length <= 1 || window.location.pathname.split('/')[1] === '') {
|
||||
map.closePopup()
|
||||
} else {
|
||||
if (window.location.pathname.split('/')[1]) {
|
||||
const id = window.location.pathname.split('/')[1]
|
||||
const ref = leafletRefs[id]
|
||||
if (ref?.marker && ref.item.layer?.name === name) {
|
||||
ref.marker && clusterRef.hasLayer(ref.marker) && clusterRef?.zoomToShowLayer(ref.marker, () => {
|
||||
ref.marker.openPopup()
|
||||
})
|
||||
let title = ''
|
||||
if (ref.item.name) title = ref.item.name
|
||||
else if (ref.item.layer?.itemNameField) title = getValue(ref.item.name, ref.item.layer.itemNameField)
|
||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute('content', ref.item.name)
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute('content', ref.item.text)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tagsReady])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
useEffect(() => {
|
||||
openPopup()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [leafletRefs, location])
|
||||
|
||||
useEffect(() => {
|
||||
if (tagsReady) {
|
||||
const processedTags = {}
|
||||
newTagsToAdd.map(newtag => {
|
||||
if (!processedTags[newtag.name]) {
|
||||
processedTags[newtag.name] = true
|
||||
addTag(newtag)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tagsReady])
|
||||
|
||||
return (
|
||||
<>
|
||||
{items &&
|
||||
items.
|
||||
filter(item => item.layer?.name === name)?.
|
||||
filter(item =>
|
||||
filterTags.length == 0 ? item : filterTags.some(tag => getItemTags(item).some(filterTag => filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())))?.
|
||||
filter(item => item.layer && isLayerVisible(item.layer)).
|
||||
filter(item => item.group_type && isGroupTypeVisible(item.group_type) || visibleGroupTypes.length == 0).
|
||||
map((item: Item) => {
|
||||
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
||||
items
|
||||
.filter(item => item.layer?.name === name)
|
||||
?.filter(item =>
|
||||
filterTags.length === 0 ? item : filterTags.some(tag => getItemTags(item).some(filterTag => filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())))
|
||||
?.filter(item => item.layer && isLayerVisible(item.layer))
|
||||
.filter(item => (item.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 (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField);
|
||||
else item[itemTextField] = "";
|
||||
if (item?.tags) {
|
||||
item[itemTextField] = item[itemTextField] + '\n\n'
|
||||
item.tags.map(tag => {
|
||||
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) { return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `) }
|
||||
return item[itemTextField]
|
||||
})
|
||||
}
|
||||
|
||||
if (item?.tags) {
|
||||
item[itemTextField] = item[itemTextField] + '\n\n';
|
||||
item.tags.map(tag => {
|
||||
if (!item[itemTextField].includes(`#${encodeTag(tag)}`))
|
||||
return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `)
|
||||
return item[itemTextField]
|
||||
if (allTagsLoaded && allItemsLoaded) {
|
||||
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())) {
|
||||
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
|
||||
|
||||
if (allTagsLoaded && allItemsLoaded) {
|
||||
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())) {
|
||||
const newTag = { id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() };
|
||||
setNewTagsToAdd(current => [...current, newTag]);
|
||||
}
|
||||
});
|
||||
!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 (
|
||||
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);
|
||||
if (!(item.id in leafletRefs && leafletRefs[item.id].marker === r)) { r && addMarker(item, r) }
|
||||
}}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
selectPosition && setMarkerClicked(item)
|
||||
},
|
||||
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}
|
||||
(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);
|
||||
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { r && addPopup(item, r as Popup) }
|
||||
}}
|
||||
item={item}
|
||||
setItemFormPopup={setItemFormPopup} />
|
||||
@ -237,25 +228,23 @@ export const Layer = ({
|
||||
}
|
||||
<Tooltip offset={[0, -38]} direction='top'>{item.name ? item.name : getValue(item, itemNameField)}</Tooltip>
|
||||
</Marker>
|
||||
);
|
||||
}
|
||||
else return null;
|
||||
})
|
||||
)
|
||||
} else return null
|
||||
})
|
||||
}
|
||||
{//{children}}
|
||||
{// {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 && 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} />
|
||||
</>)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react';
|
||||
import { ItemsApi, Permission } from '../../types';
|
||||
import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './hooks/usePermissions'
|
||||
import { useAuth } from '../Auth';
|
||||
import { useEffect } from 'react'
|
||||
import { ItemsApi, Permission } from '../../types'
|
||||
import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './hooks/usePermissions'
|
||||
import { useAuth } from '../Auth'
|
||||
|
||||
export function Permissions({data, api, adminRole} : {data?: Permission[], api?: ItemsApi<Permission>, adminRole?: string}) {
|
||||
const setPermissionData = useSetPermissionData();
|
||||
const setPermissionApi = useSetPermissionApi();
|
||||
const setAdminRole = useSetAdminRole();
|
||||
const {user} = useAuth();
|
||||
export function Permissions ({ data, api, adminRole } : {data?: Permission[], api?: ItemsApi<Permission>, adminRole?: string}) {
|
||||
const setPermissionData = useSetPermissionData()
|
||||
const setPermissionApi = useSetPermissionApi()
|
||||
const setAdminRole = useSetAdminRole()
|
||||
const { user } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
adminRole && setAdminRole(adminRole);
|
||||
data && setPermissionData(data);
|
||||
api && setPermissionApi(api);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [api, data, adminRole, user])
|
||||
useEffect(() => {
|
||||
adminRole && setAdminRole(adminRole)
|
||||
data && setPermissionData(data)
|
||||
api && setPermissionApi(api)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [api, data, adminRole, user])
|
||||
|
||||
return (
|
||||
<></>
|
||||
|
||||
@ -1,26 +1,24 @@
|
||||
import * as React from 'react'
|
||||
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>> }) {
|
||||
const layers = useLayers()
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
|
||||
export default function AddButton({ triggerAction }: { triggerAction: React.Dispatch<React.SetStateAction<any>> }) {
|
||||
const canAddItems = () => {
|
||||
let canAdd = false
|
||||
layers.map(layer => {
|
||||
if (layer.api?.createItem && hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) && layer.listed) canAdd = true
|
||||
return null
|
||||
})
|
||||
return canAdd
|
||||
}
|
||||
|
||||
const layers = useLayers();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
|
||||
const canAddItems = () => {
|
||||
let canAdd = false;
|
||||
layers.map(layer => {
|
||||
if (layer.api?.createItem && hasUserPermission(layer.api.collectionName!, "create", undefined, layer) && layer.listed) canAdd = true;
|
||||
})
|
||||
return canAdd;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
<>{
|
||||
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" >
|
||||
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" >
|
||||
<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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
@ -28,26 +26,27 @@ export default function AddButton({ triggerAction }: { triggerAction: React.Disp
|
||||
</label>
|
||||
<ul tabIndex={0} className="tw-dropdown-content tw-pr-1 tw-list-none">
|
||||
{layers.map((layer) => (
|
||||
layer.api?.createItem && hasUserPermission(layer.api.collectionName!, "create", undefined, layer) && layer.listed &&(
|
||||
layer.api?.createItem && hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) && layer.listed && (
|
||||
<li key={layer.name} >
|
||||
<a>
|
||||
<div className="tw-tooltip tw-tooltip-left" data-tip={layer.menuText}>
|
||||
<button tabIndex={0}
|
||||
className="tw-z-500 tw-border-0 tw-pl-2 tw-p-0 tw-mb-3 tw-w-10 tw-h-10 tw-cursor-pointer tw-rounded-full tw-mouse tw-drop-shadow-md tw-transition tw-ease-in tw-duration-200 focus:tw-outline-none"
|
||||
style={{ backgroundColor: layer.menuColor || "#777"}}
|
||||
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> : ""
|
||||
</div>
|
||||
: ''
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import * as L from 'leaflet'
|
||||
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>()
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (controlContainerRef.current !== null) {
|
||||
L.DomEvent.disableClickPropagation(controlContainerRef.current)
|
||||
@ -17,7 +12,7 @@ export const Control = ({ position, children, zIndex, absolute }: { position: "t
|
||||
}, [controlContainerRef])
|
||||
|
||||
return (
|
||||
<div ref={controlContainerRef} style={{zIndex: zIndex}} className={`${absolute && 'tw-absolute'} tw-z-[999] tw-flex-col ${position === 'topLeft' && "tw-top-4 tw-left-4"} ${position === 'bottomLeft' && "tw-bottom-4 tw-left-4"} ${position === 'topRight' && "tw-bottom-4 tw-right-4"} ${position === 'bottomRight' && "tw-bottom-4 tw-right-4"}`}>
|
||||
<div ref={controlContainerRef} style={{ zIndex }} className={`${absolute && 'tw-absolute'} tw-z-[999] tw-flex-col ${position === 'topLeft' && 'tw-top-4 tw-left-4'} ${position === 'bottomLeft' && 'tw-bottom-4 tw-left-4'} ${position === 'topRight' && 'tw-bottom-4 tw-right-4'} ${position === 'bottomRight' && 'tw-bottom-4 tw-right-4'}`}>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -1,34 +1,31 @@
|
||||
import * as React from 'react'
|
||||
import { useAddVisibleGroupType, useIsGroupTypeVisible, useToggleVisibleGroupType, useVisibleGroupType } from '../../hooks/useFilter';
|
||||
import { useEffect } from 'react';
|
||||
import { useAddVisibleGroupType, useIsGroupTypeVisible, useToggleVisibleGroupType, useVisibleGroupType } from '../../hooks/useFilter'
|
||||
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(() => {
|
||||
groupTypes.map(layer =>
|
||||
addVisibleGroupType(layer.value)
|
||||
)
|
||||
useEffect(() => {
|
||||
groupTypes.map(layer =>
|
||||
addVisibleGroupType(layer.value)
|
||||
)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [])
|
||||
|
||||
const isGroupTypeVisible = useIsGroupTypeVisible()
|
||||
const toggleVisibleGroupType = useToggleVisibleGroupType()
|
||||
const addVisibleGroupType = useAddVisibleGroupType()
|
||||
const visibleGroupTypes = useVisibleGroupType()
|
||||
|
||||
const isGroupTypeVisible = useIsGroupTypeVisible();
|
||||
const toggleVisibleGroupType = useToggleVisibleGroupType();
|
||||
const addVisibleGroupType = useAddVisibleGroupType();
|
||||
const visibleGroupTypes = useVisibleGroupType();
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit">
|
||||
{
|
||||
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">
|
||||
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">
|
||||
<label className="tw-btn tw-btn-sm tw-rounded-2xl tw-btn-circle tw-btn-ghost hover:tw-bg-transparent tw-absolute tw-right-0 tw-top-0 tw-text-gray-600" onClick={() => {
|
||||
setOpen(false)
|
||||
setOpen(false)
|
||||
}}>
|
||||
<p className='tw-text-center '>✕</p></label>
|
||||
<ul className='tw-flex-row'>
|
||||
@ -39,11 +36,10 @@ export function FilterControl() {
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
<div className="tw-indicator">
|
||||
: <div className="tw-indicator">
|
||||
{visibleGroupTypes.length < groupTypes.length && <span className="tw-indicator-item tw-badge tw-badge-success tw-h-4 tw-p-2 tw-translate-x-1/3 -tw-translate-y-1/3 tw-border-0"></span>}
|
||||
<div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => {
|
||||
setOpen(true)
|
||||
setOpen(true)
|
||||
}}>
|
||||
<svg 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" />
|
||||
@ -54,5 +50,5 @@ export function FilterControl() {
|
||||
}
|
||||
|
||||
</div >
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,29 +1,27 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useAuth } from "../../../Auth";
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useAuth } from '../../../Auth'
|
||||
|
||||
export const GratitudeControl = () => {
|
||||
const navigate = useNavigate();
|
||||
const {isAuthenticated} = useAuth();
|
||||
|
||||
if(isAuthenticated) return (
|
||||
const navigate = useNavigate()
|
||||
const { isAuthenticated } = useAuth()
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit">
|
||||
{
|
||||
|
||||
|
||||
<div className="tw-card-body hover:tw-bg-slate-300 tw-card tw-p-2 tw-h-10 tw-w-10 tw-transition-all tw-duration-300 hover:tw-cursor-pointer" onClick={() => {
|
||||
navigate("/select-user")
|
||||
navigate('/select-user')
|
||||
}}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2.5} stroke="currentColor" className="size-6">
|
||||
<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 (<></>)
|
||||
}
|
||||
|
||||
@ -1,49 +1,44 @@
|
||||
import * as React from 'react'
|
||||
import { useLayers } from '../../hooks/useLayers';
|
||||
import { useIsLayerVisible, useToggleVisibleLayer } from '../../hooks/useFilter';
|
||||
import { useLayers } from '../../hooks/useLayers'
|
||||
import { useIsLayerVisible, useToggleVisibleLayer } from '../../hooks/useFilter'
|
||||
|
||||
export function LayerControl() {
|
||||
export function LayerControl () {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const layers = useLayers()
|
||||
|
||||
const layers = useLayers();
|
||||
const isLayerVisible = useIsLayerVisible()
|
||||
const toggleVisibleLayer = useToggleVisibleLayer()
|
||||
|
||||
|
||||
|
||||
const isLayerVisible = useIsLayerVisible();
|
||||
const toggleVisibleLayer = useToggleVisibleLayer();
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-mt-2 tw-w-fit">
|
||||
{
|
||||
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">
|
||||
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">
|
||||
<label className="tw-btn tw-btn-sm tw-rounded-2xl tw-btn-circle tw-btn-ghost hover:tw-bg-transparent tw-absolute tw-right-0 tw-top-0 tw-text-gray-600" onClick={() => {
|
||||
setOpen(false)
|
||||
setOpen(false)
|
||||
}}>
|
||||
<p className='tw-text-center '>✕</p></label>
|
||||
<ul className='tw-flex-row'>
|
||||
{
|
||||
layers.map(layer =>
|
||||
(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>)
|
||||
(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>
|
||||
</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)
|
||||
}}>
|
||||
: <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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -7,55 +7,48 @@ import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
// Converts leaflet.locatecontrol to a React Component
|
||||
export const LocateControl = () => {
|
||||
const map = useMap()
|
||||
|
||||
const map = useMap();
|
||||
// prevent react18 from calling useEffect twice
|
||||
const init = useRef(false)
|
||||
|
||||
// prevent react18 from calling useEffect twice
|
||||
const init = useRef(false)
|
||||
const [lc, setLc] = useState<any>(null)
|
||||
const [active, setActive] = useState<boolean>(false)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
|
||||
|
||||
const [lc, setLc] = useState<any>(null);
|
||||
const [active, setActive] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
//@ts-ignore
|
||||
setLc(L.control.locate().addTo(map));
|
||||
init.current = true;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
// @ts-ignore
|
||||
setLc(L.control.locate().addTo(map))
|
||||
init.current = true
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [])
|
||||
|
||||
useMapEvents({
|
||||
locationfound: () => {
|
||||
setLoading(false);
|
||||
setActive(true);
|
||||
},
|
||||
})
|
||||
useMapEvents({
|
||||
locationfound: () => {
|
||||
setLoading(false)
|
||||
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-body tw-card tw-p-2 tw-h-10 tw-w-10 " onClick={() => {
|
||||
if (active) {
|
||||
lc.stop();
|
||||
setActive(false);
|
||||
}
|
||||
else {
|
||||
lc.start();
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
}}>{loading ? <span className="tw-loading tw-loading-spinner tw-loading-md tw-mt-1"></span> :
|
||||
<svg fill={`${active? "#fc8702" : "currentColor"}`} viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" className='tw-mt-1 tw-p-[1px]'>
|
||||
if (active) {
|
||||
lc.stop()
|
||||
setActive(false)
|
||||
} else {
|
||||
lc.start()
|
||||
setLoading(true)
|
||||
}
|
||||
}}>{loading
|
||||
? <span className="tw-loading tw-loading-spinner tw-loading-md tw-mt-1"></span>
|
||||
: <svg fill={`${active ? '#fc8702' : 'currentColor'}`} viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" className='tw-mt-1 tw-p-[1px]'>
|
||||
<path d="M30 14.75h-2.824c-0.608-5.219-4.707-9.318-9.874-9.921l-0.053-0.005v-2.824c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.824c-5.219 0.608-9.318 4.707-9.921 9.874l-0.005 0.053h-2.824c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.824c0.608 5.219 4.707 9.318 9.874 9.921l0.053 0.005v2.824c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.824c5.219-0.608 9.318-4.707 9.921-9.874l0.005-0.053h2.824c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM17.25 24.624v-2.624c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.624c-3.821-0.57-6.803-3.553-7.368-7.326l-0.006-0.048h2.624c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0h-2.624c0.57-3.821 3.553-6.804 7.326-7.368l0.048-0.006v2.624c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.624c3.821 0.57 6.803 3.553 7.368 7.326l0.006 0.048h-2.624c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.624c-0.571 3.821-3.553 6.803-7.326 7.368l-0.048 0.006z"></path>
|
||||
</svg>}
|
||||
|
||||
|
||||
</div>
|
||||
</div></>)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
import * as React from 'react'
|
||||
import { useQuestsOpen, useSetQuestOpen } from '../../../Gaming/hooks/useQuests';
|
||||
import { useQuestsOpen, useSetQuestOpen } from '../../../Gaming/hooks/useQuests'
|
||||
|
||||
export function QuestControl () {
|
||||
const questsOpen = useQuestsOpen()
|
||||
const setQuestsOpen = useSetQuestOpen()
|
||||
|
||||
export function QuestControl() {
|
||||
|
||||
const questsOpen = useQuestsOpen();
|
||||
const setQuestsOpen = useSetQuestOpen();
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
{questsOpen ? "" :
|
||||
<div className="tw-card tw-bg-base-100 tw-shadow-xl tw-my-2 tw-w-10" onClick={e => e.stopPropagation()}>
|
||||
|
||||
{questsOpen
|
||||
? ''
|
||||
: <div className="tw-card tw-bg-base-100 tw-shadow-xl tw-my-2 tw-w-10" onClick={e => e.stopPropagation()}>
|
||||
|
||||
<div className="tw-card-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>
|
||||
@ -19,5 +18,5 @@ export function QuestControl() {
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,97 +1,89 @@
|
||||
import * as React from 'react'
|
||||
import { useAddFilterTag } from '../../hooks/useFilter'
|
||||
import useWindowDimensions from '../../hooks/useWindowDimension';
|
||||
import axios from 'axios';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useMap, useMapEvents } from 'react-leaflet';
|
||||
import { LatLng, LatLngBounds } from 'leaflet';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { useTags } from '../../hooks/useTags';
|
||||
import { useItems } from '../../hooks/useItems';
|
||||
import { useLeafletRefs } from '../../hooks/useLeafletRefs';
|
||||
import { getValue } from '../../../../Utils/GetValue';
|
||||
import { LocateControl } from './LocateControl';
|
||||
import * as L from 'leaflet';
|
||||
import MarkerIconFactory from '../../../../Utils/MarkerIconFactory';
|
||||
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Item } from '../../../../types';
|
||||
import { SidebarControl } from './SidebarControl';
|
||||
|
||||
|
||||
import useWindowDimensions from '../../hooks/useWindowDimension'
|
||||
import axios from 'axios'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useMap, useMapEvents } from 'react-leaflet'
|
||||
import { LatLng, LatLngBounds } from 'leaflet'
|
||||
import { useDebounce } from '../../hooks/useDebounce'
|
||||
import { useTags } from '../../hooks/useTags'
|
||||
import { useItems } from '../../hooks/useItems'
|
||||
import { useLeafletRefs } from '../../hooks/useLeafletRefs'
|
||||
import { getValue } from '../../../../Utils/GetValue'
|
||||
import { LocateControl } from './LocateControl'
|
||||
import * as L from 'leaflet'
|
||||
import MarkerIconFactory from '../../../../Utils/MarkerIconFactory'
|
||||
import { decodeTag } from '../../../../Utils/FormatTags'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Item } from '../../../../types'
|
||||
import { SidebarControl } from './SidebarControl'
|
||||
|
||||
export const SearchControl = () => {
|
||||
const windowDimensions = useWindowDimensions()
|
||||
const [popupOpen, setPopupOpen] = useState(false)
|
||||
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const [popupOpen, setPopupOpen] = useState(false);
|
||||
const [value, setValue] = useState('')
|
||||
const [geoResults, setGeoResults] = useState<Array<any>>([])
|
||||
const [tagsResults, setTagsResults] = useState<Array<any>>([])
|
||||
const [itemsResults, setItemsResults] = useState<Array<Item>>([])
|
||||
const [hideSuggestions, setHideSuggestions] = useState(true)
|
||||
|
||||
const [value, setValue] = useState('');
|
||||
const [geoResults, setGeoResults] = useState<Array<any>>([]);
|
||||
const [tagsResults, setTagsResults] = useState<Array<any>>([]);
|
||||
const [itemsResults, setItemsResults] = useState<Array<Item>>([]);
|
||||
const [hideSuggestions, setHideSuggestions] = useState(true);
|
||||
const map = useMap()
|
||||
const tags = useTags()
|
||||
const items = useItems()
|
||||
const leafletRefs = useLeafletRefs()
|
||||
const addFilterTag = useAddFilterTag()
|
||||
|
||||
const map = useMap();
|
||||
const tags = useTags();
|
||||
const items = useItems();
|
||||
const leafletRefs = useLeafletRefs();
|
||||
const addFilterTag = useAddFilterTag();
|
||||
|
||||
|
||||
useMapEvents({
|
||||
popupopen: () => {
|
||||
setPopupOpen(true);
|
||||
},
|
||||
popupclose: () => {
|
||||
setPopupOpen(false);
|
||||
}
|
||||
})
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useDebounce(() => {
|
||||
const searchGeo = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
`https://photon.komoot.io/api/?q=${value}&limit=5`
|
||||
);
|
||||
setGeoResults(data.features);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
searchGeo();
|
||||
setItemsResults(items.filter(item => {
|
||||
if (item.layer?.itemNameField) item.name = getValue(item, item.layer.itemNameField)
|
||||
if (item.layer?.itemTextField) item.text = getValue(item, item.layer.itemTextField)
|
||||
return value.length > 2 && ((item.layer?.listed && item.name?.toLowerCase().includes(value.toLowerCase()) || item.text?.toLowerCase().includes(value.toLowerCase())))
|
||||
}))
|
||||
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 () => {
|
||||
setTimeout(() => {
|
||||
setHideSuggestions(true);
|
||||
}, 200);
|
||||
useMapEvents({
|
||||
popupopen: () => {
|
||||
setPopupOpen(true)
|
||||
},
|
||||
popupclose: () => {
|
||||
setPopupOpen(false)
|
||||
}
|
||||
})
|
||||
|
||||
const searchInput = useRef<HTMLInputElement>(null);
|
||||
const [embedded, setEmbedded] = useState<boolean>(true)
|
||||
const navigate = useNavigate()
|
||||
|
||||
useDebounce(() => {
|
||||
const searchGeo = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
`https://photon.komoot.io/api/?q=${value}&limit=5`
|
||||
)
|
||||
setGeoResults(data.features)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
searchGeo()
|
||||
setItemsResults(items.filter(item => {
|
||||
if (item.layer?.itemNameField) item.name = getValue(item, item.layer.itemNameField)
|
||||
if (item.layer?.itemTextField) item.text = getValue(item, item.layer.itemTextField)
|
||||
return value.length > 2 && (((item.layer?.listed && item.name?.toLowerCase().includes(value.toLowerCase())) || item.text?.toLowerCase().includes(value.toLowerCase())))
|
||||
}))
|
||||
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 () => {
|
||||
setTimeout(() => {
|
||||
setHideSuggestions(true)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const embedded = params.get("embedded");
|
||||
embedded != "true" && setEmbedded(false)
|
||||
}, [location]);
|
||||
const searchInput = useRef<HTMLInputElement>(null)
|
||||
const [embedded, setEmbedded] = useState<boolean>(true)
|
||||
|
||||
const location = useLocation()
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search)
|
||||
const embedded = params.get('embedded')
|
||||
embedded !== 'true' && setEmbedded(false)
|
||||
}, [location])
|
||||
|
||||
return (<>
|
||||
return (<>
|
||||
{!(windowDimensions.height < 500 && popupOpen && hideSuggestions) &&
|
||||
<div className='tw-w-[calc(100vw-2rem)] tw-max-w-[22rem] '>
|
||||
<div className='tw-flex tw-flex-row'>
|
||||
@ -101,21 +93,22 @@ export const SearchControl = () => {
|
||||
ref={searchInput}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onFocus={() => {
|
||||
setHideSuggestions(false);
|
||||
if (windowDimensions.width < 500) map.closePopup();
|
||||
setHideSuggestions(false)
|
||||
if (windowDimensions.width < 500) map.closePopup()
|
||||
}}
|
||||
onBlur={() => hide()} />
|
||||
{value.length > 0 && <button className="tw-btn tw-btn-sm tw-btn-circle tw-absolute tw-right-2 tw-top-2" onClick={() => setValue("")}>✕</button>}
|
||||
{value.length > 0 && <button className="tw-btn tw-btn-sm tw-btn-circle tw-absolute tw-right-2 tw-top-2" onClick={() => setValue('')}>✕</button>}
|
||||
</div>
|
||||
<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'>
|
||||
{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)
|
||||
addFilterTag(tag)
|
||||
}}>
|
||||
<b>#{decodeTag(tag.name)}</b>
|
||||
</div>
|
||||
@ -126,14 +119,12 @@ export const SearchControl = () => {
|
||||
{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))
|
||||
}
|
||||
|
||||
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" />
|
||||
@ -147,11 +138,11 @@ export const SearchControl = () => {
|
||||
{Array.from(geoResults).length > 0 && (itemsResults.length > 0 || tagsResults.length > 0) && <hr className='tw-opacity-50'></hr>}
|
||||
{Array.from(geoResults).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();
|
||||
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" />
|
||||
@ -159,15 +150,15 @@ export const SearchControl = () => {
|
||||
|
||||
<div>
|
||||
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties.name ? geo?.properties.name : value}</div>
|
||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties?.city && `${capitalizeFirstLetter(geo?.properties?.city)}, `} {geo?.properties?.osm_value && geo?.properties?.osm_value !== "yes" && geo?.properties?.osm_value !== "primary" && geo?.properties?.osm_value !== "path" && geo?.properties?.osm_value !== "secondary" && geo?.properties?.osm_value !== "residential" && geo?.properties?.osm_value !== "unclassified" && `${capitalizeFirstLetter(geo?.properties?.osm_value)}, `} {geo.properties.state && `${geo.properties.state}, `} {geo.properties.country && geo.properties.country}</div>
|
||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{geo?.properties?.city && `${capitalizeFirstLetter(geo?.properties?.city)}, `} {geo?.properties?.osm_value && geo?.properties?.osm_value !== 'yes' && geo?.properties?.osm_value !== 'primary' && geo?.properties?.osm_value !== 'path' && geo?.properties?.osm_value !== 'secondary' && geo?.properties?.osm_value !== 'residential' && geo?.properties?.osm_value !== 'unclassified' && `${capitalizeFirstLetter(geo?.properties?.osm_value)}, `} {geo.properties.state && `${geo.properties.state}, `} {geo.properties.country && geo.properties.country}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
))}
|
||||
{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 })
|
||||
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" />
|
||||
@ -175,7 +166,7 @@ export const SearchControl = () => {
|
||||
|
||||
<div>
|
||||
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{value}</div>
|
||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{"Coordiante"}</div>
|
||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>{'Coordiante'}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -184,26 +175,26 @@ export const SearchControl = () => {
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function isGeoCoordinate(input) {
|
||||
const geokoordinatenRegex = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/;
|
||||
return geokoordinatenRegex.test(input);
|
||||
function isGeoCoordinate (input) {
|
||||
const geokoordinatenRegex = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/
|
||||
return geokoordinatenRegex.test(input)
|
||||
}
|
||||
|
||||
function extractCoordinates(input): number[] | null {
|
||||
const result = input.split(",")
|
||||
if (result) {
|
||||
const latitude = parseFloat(result[0]);
|
||||
const longitude = parseFloat(result[1]);
|
||||
if (!isNaN(latitude) && !isNaN(longitude)) {
|
||||
return [latitude, longitude];
|
||||
}
|
||||
function extractCoordinates (input): number[] | null {
|
||||
const result = input.split(',')
|
||||
if (result) {
|
||||
const latitude = parseFloat(result[0])
|
||||
const longitude = parseFloat(result[1])
|
||||
if (!isNaN(latitude) && !isNaN(longitude)) {
|
||||
return [latitude, longitude]
|
||||
}
|
||||
return null; // Invalid input or error
|
||||
}
|
||||
return null // Invalid input or error
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
function capitalizeFirstLetter (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
}
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
|
||||
|
||||
// Converts leaflet.locatecontrol to a React Component
|
||||
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-body tw-card tw-p-0">
|
||||
@ -19,7 +14,4 @@ export const SidebarControl = () => {
|
||||
|
||||
</div>
|
||||
</div></>)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import * as React from 'react'
|
||||
import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter';
|
||||
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||
import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter'
|
||||
import { decodeTag } from '../../../../Utils/FormatTags'
|
||||
|
||||
export const TagsControl = () => {
|
||||
|
||||
const filterTags = useFilterTags();
|
||||
const removeFilterTag = useRemoveFilterTag();
|
||||
const filterTags = useFilterTags()
|
||||
const removeFilterTag = useRemoveFilterTag()
|
||||
|
||||
return (
|
||||
<div className='tw-flex tw-flex-wrap tw-mt-4 tw-w-[calc(100vw-2rem)] tw-max-w-xs'>
|
||||
@ -20,5 +19,3 @@ export const TagsControl = () => {
|
||||
}
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -21,130 +21,124 @@ export interface ItemFormPopupProps {
|
||||
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
|
||||
const [popupTitle, setPopupTitle] = useState<string>('')
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [popupTitle, setPopupTitle] = useState<string>("");
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const map = useMap()
|
||||
|
||||
const map = useMap();
|
||||
const addItem = useAddItem()
|
||||
const updateItem = useUpdateItem()
|
||||
const items = useItems()
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const removeItem = useRemoveItem()
|
||||
|
||||
const addItem = useAddItem();
|
||||
const updateItem = useUpdateItem();
|
||||
const items = useItems();
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const removeItem = useRemoveItem();
|
||||
const tags = useTags()
|
||||
const addTag = useAddTag()
|
||||
|
||||
const resetFilterTags = useResetFilterTags()
|
||||
|
||||
const tags = useTags();
|
||||
const addTag = useAddTag();
|
||||
const { user } = useAuth()
|
||||
|
||||
const resetFilterTags = useResetFilterTags();
|
||||
const handleSubmit = async (evt: any) => {
|
||||
const formItem: Item = {} as Item
|
||||
Array.from(evt.target).forEach((input: HTMLInputElement) => {
|
||||
if (input.name) {
|
||||
formItem[input.name] = input.value
|
||||
}
|
||||
})
|
||||
formItem.position = new Geometry(props.position.lng, props.position.lat)
|
||||
evt.preventDefault()
|
||||
setSpinner(true)
|
||||
|
||||
const { user } = useAuth();
|
||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
if (props.item) {
|
||||
let success = false
|
||||
try {
|
||||
await props.layer.api?.updateItem!({ ...formItem, id: props.item.id })
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
console.log(props.item)
|
||||
|
||||
const handleSubmit = async (evt: any) => {
|
||||
const formItem: Item = {} as Item;
|
||||
Array.from(evt.target).forEach((input: HTMLInputElement) => {
|
||||
if (input.name) {
|
||||
formItem[input.name] = input.value;
|
||||
}
|
||||
});
|
||||
formItem['position'] = new Geometry(props.position.lng, props.position.lat);
|
||||
evt.preventDefault();
|
||||
setSpinner(true);
|
||||
updateItem({ ...props.item, ...formItem })
|
||||
toast.success('Item updated')
|
||||
}
|
||||
setSpinner(false)
|
||||
map.closePopup()
|
||||
} else {
|
||||
const item = items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name === props.layer.itemType.name)
|
||||
|
||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag=> {
|
||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
addTag({id: crypto.randomUUID(), name: tag.slice(1), color: randomColor()})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
if(props.item) {
|
||||
let success = false;
|
||||
try {
|
||||
await props.layer.api?.updateItem!({...formItem, id: props.item.id});
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if(success) {
|
||||
console.log(props.item);
|
||||
|
||||
updateItem({...props.item, ...formItem});
|
||||
toast.success("Item updated");
|
||||
}
|
||||
setSpinner(false);
|
||||
map.closePopup();
|
||||
}
|
||||
else {
|
||||
const item = items.find(i => i.user_created?.id === user?.id && i.layer?.itemType.name === props.layer.itemType.name);
|
||||
|
||||
const uuid = crypto.randomUUID();
|
||||
let success = false;
|
||||
try {
|
||||
props.layer.onlyOnePerOwner && item && await props.layer.api?.updateItem!({...formItem, id: item?.id });
|
||||
(!props.layer.onlyOnePerOwner || !item) && await props.layer.api?.createItem!({...formItem, id: uuid, name: formItem.name ? formItem.name : user?.first_name });
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if(success) {
|
||||
props.layer.onlyOnePerOwner && item && updateItem({...item, ...formItem});
|
||||
(!props.layer.onlyOnePerOwner || !item) && addItem({...formItem, name: formItem.name ? formItem.name : user?.first_name , user_created: user, type: props.layer.itemType, id: uuid, layer: props.layer, public_edit: !user ? true : false});
|
||||
toast.success("New item created");
|
||||
resetFilterTags();
|
||||
}
|
||||
setSpinner(false);
|
||||
map.closePopup();
|
||||
}
|
||||
props.setItemFormPopup!(null);
|
||||
const uuid = crypto.randomUUID()
|
||||
let success = false
|
||||
try {
|
||||
props.layer.onlyOnePerOwner && item && await props.layer.api?.updateItem!({ ...formItem, id: item?.id });
|
||||
(!props.layer.onlyOnePerOwner || !item) && await props.layer.api?.createItem!({ ...formItem, id: uuid, name: formItem.name ? formItem.name : user?.first_name })
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
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 })
|
||||
toast.success('New item created')
|
||||
resetFilterTags()
|
||||
}
|
||||
setSpinner(false)
|
||||
map.closePopup()
|
||||
}
|
||||
props.setItemFormPopup!(null)
|
||||
}
|
||||
|
||||
|
||||
const resetPopup = () => {
|
||||
if (formRef.current) {
|
||||
formRef.current.reset();
|
||||
}
|
||||
const resetPopup = () => {
|
||||
if (formRef.current) {
|
||||
formRef.current.reset()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
resetPopup();
|
||||
}, [props.position])
|
||||
useEffect(() => {
|
||||
resetPopup()
|
||||
}, [props.position])
|
||||
|
||||
return (
|
||||
return (
|
||||
<LeafletPopup minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}
|
||||
eventHandlers={{
|
||||
remove: () => {
|
||||
setTimeout(function () {
|
||||
resetPopup()
|
||||
}, 100);
|
||||
}
|
||||
remove: () => {
|
||||
setTimeout(function () {
|
||||
resetPopup()
|
||||
}, 100)
|
||||
}
|
||||
}}
|
||||
position={props.position}>
|
||||
<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.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: setPopupTitle }) : ""
|
||||
)
|
||||
? 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' />
|
||||
: <>
|
||||
<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' />
|
||||
</>
|
||||
}
|
||||
|
||||
@ -153,8 +147,5 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
</div>
|
||||
</form>
|
||||
</LeafletPopup>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import * as React from "react"
|
||||
import { Item, ItemsApi } from "../../../../types";
|
||||
import { useHasUserPermission } from "../../hooks/usePermissions";
|
||||
import { getValue } from "../../../../Utils/GetValue";
|
||||
import * as React from 'react'
|
||||
import { Item, ItemsApi } from '../../../../types'
|
||||
import { useHasUserPermission } from '../../hooks/usePermissions'
|
||||
import { getValue } from '../../../../Utils/GetValue'
|
||||
import { useAssetApi } from '../../../AppShell/hooks/useAssets'
|
||||
import DialogModal from "../../../Templates/DialogModal";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DialogModal from '../../../Templates/DialogModal'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export function HeaderView({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemSubnameField, itemAvatarField, loading, hideMenu = false, big = false, truncateSubname = true, hideSubname = false, showAddress = false }: {
|
||||
export function HeaderView ({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemSubnameField, itemAvatarField, loading, hideMenu = false, big = false, truncateSubname = true, hideSubname = false, showAddress = false }: {
|
||||
item: Item,
|
||||
api?: ItemsApi<any>,
|
||||
editCallback?: any,
|
||||
@ -22,63 +22,58 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
|
||||
truncateSubname?: boolean,
|
||||
showAddress?: boolean
|
||||
}) {
|
||||
const [modalOpen, setModalOpen] = React.useState<boolean>(false)
|
||||
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const navigate = useNavigate()
|
||||
const assetsApi = useAssetApi()
|
||||
|
||||
const [modalOpen, setModalOpen] = React.useState<boolean>(false);
|
||||
const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}` : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && assetsApi.url + getValue(item, item.layer?.itemAvatarField) + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
|
||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField)
|
||||
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField)
|
||||
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const navigate = useNavigate();
|
||||
const assetsApi = useAssetApi();
|
||||
|
||||
const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) + `${big ? "?width=160&heigth=160" : "?width=80&heigth=80"}` : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && assetsApi.url + getValue(item, item.layer?.itemAvatarField) + `${big ? "?width=160&heigth=160" : "?width=80&heigth=80"}`;
|
||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField);
|
||||
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField);
|
||||
|
||||
const [address, /* setAdress*/] = React.useState<string>("");
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const [address] = React.useState<string>('')
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
|
||||
setModalOpen(true);
|
||||
event.stopPropagation();
|
||||
setModalOpen(true)
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
{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
|
||||
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}
|
||||
alt={item.name + " logo"}
|
||||
alt={item.name + ' logo'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<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={`${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`}>
|
||||
{title}
|
||||
</div>
|
||||
{showAddress && address && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && "tw-truncate"}`}>
|
||||
{showAddress && address && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
|
||||
{address}
|
||||
</div>}
|
||||
{subtitle && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && "tw-truncate"}`}>
|
||||
{subtitle && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && 'tw-truncate'}`}>
|
||||
{subtitle}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className={`${big ? "tw-mt-5" : "tw-mt-1"}`}>
|
||||
{(api?.deleteItem || item.layer?.api?.updateItem)
|
||||
&& (hasUserPermission(api?.collectionName!, "delete", item) || hasUserPermission(api?.collectionName!, "update", item))
|
||||
&& !hideMenu &&
|
||||
<div onClick={(e) => e.stopPropagation()} className={`${big ? 'tw-mt-5' : 'tw-mt-1'}`}>
|
||||
{(api?.deleteItem || item.layer?.api?.updateItem) &&
|
||||
(hasUserPermission(api?.collectionName!, 'delete', item) || hasUserPermission(api?.collectionName!, 'update', item)) &&
|
||||
!hideMenu &&
|
||||
<div className="tw-dropdown tw-dropdown-bottom">
|
||||
<label tabIndex={0} className="tw-bg-base-100 tw-btn tw-m-1 tw-leading-3 tw-border-none tw-min-h-0 tw-h-6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
@ -86,25 +81,25 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
|
||||
</svg>
|
||||
</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">
|
||||
{((api?.updateItem && hasUserPermission(api.collectionName!, "update", item))) && editCallback && <li>
|
||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={(e) => item.layer?.customEditLink ? navigate(`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && "?"+params}` : ""} `) : editCallback(e)}>
|
||||
{((api?.updateItem && hasUserPermission(api.collectionName!, 'update', item))) && editCallback && <li>
|
||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={(e) => item.layer?.customEditLink ? navigate(`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && '?' + params}` : ''} `) : editCallback(e)}>
|
||||
<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>
|
||||
{((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>
|
||||
{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">
|
||||
{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>
|
||||
@ -119,7 +114,7 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
|
||||
<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 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>
|
||||
|
||||
@ -1,19 +1,14 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getValue } from '../../../../Utils/GetValue'
|
||||
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}) => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const getItemTags = useGetItemTags()
|
||||
|
||||
export const PopupButton = ({url, parameterField, text, colorField, item} : {url: string, parameterField?: string, text: string, colorField?: string, item? : Item}) => {
|
||||
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>
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const getItemTags = useGetItemTags();
|
||||
|
||||
|
||||
|
||||
|
||||
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>
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,8 +6,7 @@ export const PopupCheckboxInput = ({ dataField, label, item }:
|
||||
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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,16 +6,16 @@ type StartEndInputProps = {
|
||||
item?:Item,
|
||||
showLabels?: boolean
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
updateStartValue?: (value: string ) => void;
|
||||
updateStartValue?: (value: string) => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
updateEndValue?: (value: string ) => void;
|
||||
updateEndValue?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const PopupStartEndInput = ({item, showLabels = true, updateStartValue, updateEndValue}:StartEndInputProps) => {
|
||||
return (
|
||||
export const PopupStartEndInput = ({ item, showLabels = true, updateStartValue, updateEndValue }:StartEndInputProps) => {
|
||||
return (
|
||||
<div className='tw-grid tw-grid-cols-2 tw-gap-2 tw-mb-5'>
|
||||
<TextInput type='date' placeholder='start' dataField='start' inputStyle='tw-text-sm tw-px-2' labelTitle={showLabels ? "start" :""} defaultValue={item && item.start? item.start.substring(0, 10) : ""} autocomplete='one-time-code' updateFormValue={updateStartValue}></TextInput>
|
||||
<TextInput type='date' placeholder='end' dataField='end' inputStyle='tw-text-sm tw-px-2' labelTitle={showLabels ? "end" :""} defaultValue={item && item.end ? item.end.substring(0, 10) : ""} autocomplete='one-time-code' updateFormValue={updateEndValue}></TextInput>
|
||||
<TextInput type='date' placeholder='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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -9,8 +9,7 @@ export const PopupTextAreaInput = ({ dataField, placeholder, style, item }:
|
||||
style?: string,
|
||||
item?: Item
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<TextAreaInput defaultValue={item?.text ? item.text : ""} dataField={dataField} placeholder={placeholder} inputStyle={style}></TextAreaInput>
|
||||
)
|
||||
return (
|
||||
<TextAreaInput defaultValue={item?.text ? item.text : ''} dataField={dataField} placeholder={placeholder} inputStyle={style}></TextAreaInput>
|
||||
)
|
||||
}
|
||||
|
||||
@ -9,8 +9,7 @@ export const PopupTextInput = ({ dataField, placeholder, style, item }:
|
||||
style?: string,
|
||||
item?: Item
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<TextInput defaultValue={item?.name ? item.name : ""} dataField={dataField} placeholder={placeholder} inputStyle={style} type='text' containerStyle={'tw-mt-4 tw-mb-4'}></TextInput>
|
||||
)
|
||||
return (
|
||||
<TextInput defaultValue={item?.name ? item.name : ''} dataField={dataField} placeholder={placeholder} inputStyle={style} type='text' containerStyle={'tw-mt-4 tw-mb-4'}></TextInput>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import * as React from 'react'
|
||||
import { Item } from '../../../../types'
|
||||
|
||||
export const StartEndView = ({item} : {item?:Item}) => {
|
||||
export const StartEndView = ({ item } : {item?:Item}) => {
|
||||
return (
|
||||
<div className="tw-flex tw-flex-row tw-mb-4 tw-mt-1">
|
||||
<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.start? item.start.substring(0, 10) : ""}>{item && item.start? new Date(item.start).toLocaleDateString(): ""}</time>
|
||||
<time className='tw-align-middle' dateTime={item && item.start ? item.start.substring(0, 10) : ''}>{item && item.start ? new Date(item.start).toLocaleDateString() : ''}</time>
|
||||
</div>
|
||||
<div className="tw-basis-1/5 tw-place-content-center">
|
||||
<span>-</span>
|
||||
@ -17,9 +17,8 @@ export const StartEndView = ({item} : {item?:Item}) => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-4 tw-w-4 tw-mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<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>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,95 +1,95 @@
|
||||
import { Item } from '../../../../types';
|
||||
import { useTags } from '../../hooks/useTags';
|
||||
import { useAddFilterTag } from '../../hooks/useFilter';
|
||||
import { hashTagRegex } from '../../../../Utils/HashTagRegex';
|
||||
import { fixUrls, mailRegex } from '../../../../Utils/ReplaceURLs';
|
||||
import Markdown from 'react-markdown';
|
||||
import { getValue } from '../../../../Utils/GetValue';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||
import { memo } from 'react';
|
||||
import { Item } from '../../../../types'
|
||||
import { useTags } from '../../hooks/useTags'
|
||||
import { useAddFilterTag } from '../../hooks/useFilter'
|
||||
import { hashTagRegex } from '../../../../Utils/HashTagRegex'
|
||||
import { fixUrls, mailRegex } from '../../../../Utils/ReplaceURLs'
|
||||
import Markdown from 'react-markdown'
|
||||
import { getValue } from '../../../../Utils/GetValue'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
import { decodeTag } from '../../../../Utils/FormatTags'
|
||||
import { memo } from 'react'
|
||||
|
||||
export const TextView = ({ item, truncate = false, itemTextField, rawText }: { item?: Item, truncate?: boolean, itemTextField?: string, rawText?: string }) => {
|
||||
const tags = useTags();
|
||||
const addFilterTag = useAddFilterTag();
|
||||
const tags = useTags()
|
||||
const addFilterTag = useAddFilterTag()
|
||||
|
||||
let text = "";
|
||||
let replacedText = "";
|
||||
let text = ''
|
||||
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)
|
||||
|
||||
|
||||
item && text ? replacedText = fixUrls(text) : "";
|
||||
if (item && text) replacedText = fixUrls(text)
|
||||
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
replacedText ? replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s\)]+(?!\))/g, (url) => {
|
||||
let shortUrl = url;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
if (url.match('^https:\/\/')) {
|
||||
shortUrl = url.split('https://')[1];
|
||||
}
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
if (url.match('^http:\/\/')) {
|
||||
shortUrl = url.split('http://')[1];
|
||||
}
|
||||
return `[${shortUrl}](${url})`;
|
||||
}) : "";
|
||||
if (replacedText) {
|
||||
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
|
||||
let shortUrl = url
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
if (url.match('^https:\/\/')) {
|
||||
shortUrl = url.split('https://')[1]
|
||||
}
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
if (url.match('^http:\/\/')) {
|
||||
shortUrl = url.split('http://')[1]
|
||||
}
|
||||
return `[${shortUrl}](${url})`
|
||||
})
|
||||
}
|
||||
|
||||
replacedText ? replacedText = replacedText.replace(mailRegex, (url) => {
|
||||
return `[${url}](mailto:${url})`;
|
||||
}) : "";
|
||||
if (replacedText) {
|
||||
replacedText = replacedText.replace(mailRegex, (url) => {
|
||||
return `[${url}](mailto:${url})`
|
||||
})
|
||||
}
|
||||
|
||||
replacedText ? replacedText = replacedText.replace(hashTagRegex, (match) => {
|
||||
return `[${match}](${match})`;
|
||||
}) : "";
|
||||
if (replacedText) {
|
||||
replacedText = replacedText.replace(hashTagRegex, (match) => {
|
||||
return `[${match}](${match})`
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH1 = ({ children }) => (
|
||||
<h1 className="tw-text-xl tw-font-bold">{children}</h1>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH2 = ({ children }) => (
|
||||
<h2 className="tw-text-lg tw-font-bold">{children}</h2>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH3 = ({ children }) => (
|
||||
<h3 className="tw-text-base tw-font-bold">{children}</h3>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH4 = ({ children }) => (
|
||||
<h4 className="tw-text-base tw-font-bold">{children}</h4>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH5 = ({ children }) => (
|
||||
<h5 className="tw-text-sm tw-font-bold">{children}</h5>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH6 = ({ children }) => (
|
||||
<h6 className="tw-text-sm tw-font-bold">{children}</h6>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomParagraph = ({ children }) => (
|
||||
<p className="!tw-my-2">{children}</p>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomUnorderdList = ({ children }) => (
|
||||
<ul className="tw-list-disc tw-list-inside">{children}</ul>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomOrderdList = ({ children }) => (
|
||||
<ol className="tw-list-decimal tw-list-inside">{children}</ol>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomHorizontalRow = ({ children }) => (
|
||||
<hr className="tw-border-current">{children}</hr>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomImage = ({ alt, src, title }) => (
|
||||
<img
|
||||
@ -98,14 +98,14 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
||||
alt={alt}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomExternalLink = ({ href, children }) => (
|
||||
<a className='tw-font-bold tw-underline'
|
||||
href={href}
|
||||
target='_blank' rel="noreferrer"
|
||||
> {children}</a>
|
||||
);
|
||||
)
|
||||
/* eslint-disable react/prop-types */
|
||||
const CustomHashTagLink = ({ children, tag, item }) => {
|
||||
return (
|
||||
@ -113,11 +113,11 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
||||
style={{ color: tag ? tag.color : '#faa', fontWeight: 'bold', cursor: 'pointer' }}
|
||||
key={tag ? tag.name + item!.id : item.id}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
addFilterTag(tag!);
|
||||
e.stopPropagation()
|
||||
addFilterTag(tag!)
|
||||
}}>{decodeTag(children)}</a>
|
||||
)
|
||||
};
|
||||
}
|
||||
/* eslint-enable react/prop-types */
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
@ -128,40 +128,39 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
||||
allow="fullscreen; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
));
|
||||
))
|
||||
|
||||
return (
|
||||
<Markdown className={`tw-text-map tw-leading-map tw-text-sm`} remarkPlugins={[remarkBreaks]} components={{
|
||||
<Markdown className={'tw-text-map tw-leading-map tw-text-sm'} remarkPlugins={[remarkBreaks]} components={{
|
||||
p: CustomParagraph,
|
||||
a: ({ href, children }) => {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v=');
|
||||
const isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v=')
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const isRumbleVideo = href?.startsWith('https://rumble.com/embed/');
|
||||
|
||||
const isRumbleVideo = href?.startsWith('https://rumble.com/embed/')
|
||||
|
||||
if (isYouTubeVideo) {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const videoId = href?.split('v=')[1].split('&')[0];
|
||||
const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`;
|
||||
const videoId = href?.split('v=')[1].split('&')[0]
|
||||
const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`
|
||||
|
||||
return (
|
||||
<MemoizedVideoEmbed url={youtubeEmbedUrl}></MemoizedVideoEmbed>
|
||||
);
|
||||
)
|
||||
}
|
||||
if (isRumbleVideo) {
|
||||
return (
|
||||
<MemoizedVideoEmbed url={href!}></MemoizedVideoEmbed>
|
||||
);
|
||||
)
|
||||
}
|
||||
// eslint-disable-next-line react/prop-types
|
||||
if (href?.startsWith("#")) {
|
||||
const tag = tags.find(t => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase());
|
||||
return <CustomHashTagLink tag={tag} item={item}>{children}</CustomHashTagLink>;
|
||||
if (href?.startsWith('#')) {
|
||||
const tag = tags.find(t => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase())
|
||||
return <CustomHashTagLink tag={tag} item={item}>{children}</CustomHashTagLink>
|
||||
} else {
|
||||
return (
|
||||
<CustomExternalLink href={href}>{children}</CustomExternalLink>
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
ul: CustomUnorderdList,
|
||||
@ -173,14 +172,14 @@ export const TextView = ({ item, truncate = false, itemTextField, rawText }: { i
|
||||
h3: CustomH3,
|
||||
h4: CustomH4,
|
||||
h5: CustomH5,
|
||||
h6: CustomH6,
|
||||
h6: CustomH6
|
||||
}}>
|
||||
{replacedText}
|
||||
</Markdown>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function removeMarkdownKeepLinksAndParagraphs(text) {
|
||||
function removeMarkdownKeepLinksAndParagraphs (text) {
|
||||
// Remove Markdown syntax using regular expressions but keep links and paragraphs
|
||||
return text
|
||||
.replace(/!\[.*?\]\(.*?\)/g, '') // Remove images
|
||||
@ -189,29 +188,29 @@ function removeMarkdownKeepLinksAndParagraphs(text) {
|
||||
.replace(/(#+)\s+(.*)/g, '$2') // Remove headers
|
||||
.replace(/>\s+(.*)/g, '$1') // Remove blockquotes
|
||||
.replace(/^\s*\n/gm, '\n') // Preserve empty lines
|
||||
.replace(/(\r\n|\n|\r)/gm, '\n'); // Preserve line breaks
|
||||
.replace(/(\r\n|\n|\r)/gm, '\n') // Preserve line breaks
|
||||
}
|
||||
|
||||
function truncateText(text, limit) {
|
||||
function truncateText (text, limit) {
|
||||
if (text.length <= limit) {
|
||||
return text;
|
||||
return text
|
||||
}
|
||||
|
||||
let truncated = "";
|
||||
let length = 0;
|
||||
let truncated = ''
|
||||
let length = 0
|
||||
|
||||
// Split the text by paragraphs
|
||||
const paragraphs = text.split('\n');
|
||||
const paragraphs = text.split('\n')
|
||||
|
||||
for (const paragraph of paragraphs) {
|
||||
if (length + paragraph.length > limit) {
|
||||
truncated += paragraph.slice(0, limit - length) + '...';
|
||||
break;
|
||||
truncated += paragraph.slice(0, limit - length) + '...'
|
||||
break
|
||||
} else {
|
||||
truncated += paragraph + '\n';
|
||||
length += paragraph.length;
|
||||
truncated += paragraph + '\n'
|
||||
length += paragraph.length
|
||||
}
|
||||
}
|
||||
|
||||
return truncated.trim();
|
||||
return truncated.trim()
|
||||
}
|
||||
|
||||
@ -12,92 +12,82 @@ import { useRemoveItem, useUpdateItem } from '../hooks/useItems'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useSetSelectPosition } from '../hooks/useSelectPosition'
|
||||
|
||||
|
||||
export interface ItemViewPopupProps {
|
||||
item: Item,
|
||||
children?: React.ReactNode;
|
||||
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: any) => {
|
||||
const map = useMap();
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const removeItem = useRemoveItem();
|
||||
const updadateItem = useUpdateItem();
|
||||
const navigate = useNavigate();
|
||||
const setSelectPosition = useSetSelectPosition();
|
||||
const map = useMap()
|
||||
const [loading, setLoading] = React.useState<boolean>(false)
|
||||
const removeItem = useRemoveItem()
|
||||
const updadateItem = useUpdateItem()
|
||||
const navigate = useNavigate()
|
||||
const setSelectPosition = useSetSelectPosition()
|
||||
|
||||
|
||||
const [infoExpanded, setInfoExpanded] = useState<boolean>(false);
|
||||
const [infoExpanded, setInfoExpanded] = useState<boolean>(false)
|
||||
|
||||
const handleEdit = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
map.closePopup();
|
||||
event.stopPropagation()
|
||||
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 })
|
||||
}
|
||||
|
||||
const handleDelete = async (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
setLoading(true);
|
||||
let success = false;
|
||||
event.stopPropagation()
|
||||
setLoading(true)
|
||||
let success = false
|
||||
try {
|
||||
!props.item.layer?.onlyOnePerOwner && await props.item.layer?.api?.deleteItem!(props.item.id);
|
||||
props.item.layer?.onlyOnePerOwner && await props.item.layer.api?.updateItem!({id: props.item.id, position: null})
|
||||
success = true;
|
||||
!props.item.layer?.onlyOnePerOwner && await props.item.layer?.api?.deleteItem!(props.item.id)
|
||||
props.item.layer?.onlyOnePerOwner && await props.item.layer.api?.updateItem!({ id: props.item.id, position: null })
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
!props.item.layer?.onlyOnePerOwner && removeItem(props.item);
|
||||
props.item.layer?.onlyOnePerOwner && updadateItem({...props.item, position: undefined});
|
||||
toast.success("Item deleted");
|
||||
!props.item.layer?.onlyOnePerOwner && removeItem(props.item)
|
||||
props.item.layer?.onlyOnePerOwner && updadateItem({ ...props.item, position: undefined })
|
||||
toast.success('Item deleted')
|
||||
}
|
||||
setLoading(false);
|
||||
map.closePopup();
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
window.history.pushState({}, "", "/" + `${params ? `?${params}` : ""}`);
|
||||
navigate("/");
|
||||
setLoading(false)
|
||||
map.closePopup()
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
window.history.pushState({}, '', '/' + `${params ? `?${params}` : ''}`)
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<LeafletPopup ref={ref} maxHeight={377} minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}>
|
||||
<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'>
|
||||
{props.children ?
|
||||
{props.children
|
||||
|
||||
React.Children.toArray(props.children).map((child) =>
|
||||
React.isValidElement<{ item: Item, test: string }>(child) ?
|
||||
React.cloneElement(child, { item: props.item }) : ""
|
||||
? React.Children.toArray(props.children).map((child) =>
|
||||
React.isValidElement<{ item: Item, test: string }>(child)
|
||||
? React.cloneElement(child, { item: props.item })
|
||||
: ''
|
||||
)
|
||||
|
||||
:
|
||||
|
||||
<TextView item={props.item} />
|
||||
: <TextView item={props.item} />
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 tw-mt-1'>
|
||||
|
||||
|
||||
{
|
||||
infoExpanded ?
|
||||
<p className={`tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500`} >{`${props.item.date_updated && props.item.date_updated != props.item.date_created ? "updated" : "posted" } ${props.item && props.item.user_created && props.item.user_created.first_name ? `by ${props.item.user_created.first_name}` : ""} ${props.item.date_updated ? timeAgo(props.item.date_updated) : timeAgo(props.item.date_created!)}`}</p>
|
||||
:
|
||||
<p className="!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500" onClick={() => setInfoExpanded(true)}>ⓘ</p>
|
||||
infoExpanded
|
||||
? <p className={'tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'} >{`${props.item.date_updated && props.item.date_updated !== props.item.date_created ? 'updated' : 'posted'} ${props.item && props.item.user_created && props.item.user_created.first_name ? `by ${props.item.user_created.first_name}` : ''} ${props.item.date_updated ? timeAgo(props.item.date_updated) : timeAgo(props.item.date_created!)}`}</p>
|
||||
: <p className="!tw-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>
|
||||
{ //** <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>
|
||||
</LeafletPopup>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
|
||||
export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItemPosition }) => {
|
||||
return (
|
||||
export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItemPosition }) => {
|
||||
return (
|
||||
<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={() => {
|
||||
setSelectNewItemPosition(null)
|
||||
setSelectNewItemPosition(null)
|
||||
}}>
|
||||
<p className='tw-text-center '>✕</p></label>
|
||||
<div className="tw-alert tw-bg-base-100 tw-text-base-content">
|
||||
@ -12,5 +11,5 @@ export const SelectPosition = ({ setSelectNewItemPosition }: { setSelectNewItem
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,43 +1,42 @@
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react';
|
||||
import { ItemsApi, Tag } from '../../types';
|
||||
import { useEffect } from 'react'
|
||||
import { ItemsApi, Tag } from '../../types'
|
||||
import { useSetTagData, useSetTagApi, useTags } from './hooks/useTags'
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAddFilterTag, useFilterTags, useResetFilterTags } from './hooks/useFilter';
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useAddFilterTag, useFilterTags, useResetFilterTags } from './hooks/useFilter'
|
||||
|
||||
export function Tags({data, api} : {data?: Tag[], api?: ItemsApi<Tag>}) {
|
||||
const setTagData = useSetTagData();
|
||||
const setTagApi = useSetTagApi();
|
||||
export function Tags ({ data, api } : {data?: Tag[], api?: ItemsApi<Tag>}) {
|
||||
const setTagData = useSetTagData()
|
||||
const setTagApi = useSetTagApi()
|
||||
|
||||
useEffect(() => {
|
||||
data && setTagData(data);
|
||||
api && setTagApi(api);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [api, data])
|
||||
useEffect(() => {
|
||||
data && setTagData(data)
|
||||
api && setTagApi(api)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [api, data])
|
||||
|
||||
const location = useLocation()
|
||||
const addFilterTag = useAddFilterTag()
|
||||
const resetFilterTags = useResetFilterTags()
|
||||
const tags = useTags()
|
||||
const filterTags = useFilterTags()
|
||||
|
||||
const location = useLocation();
|
||||
const addFilterTag = useAddFilterTag();
|
||||
const resetFilterTags = useResetFilterTags();
|
||||
const tags = useTags();
|
||||
const filterTags = useFilterTags()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const urlTags = params.get("tags")
|
||||
const decodedTags = urlTags ? decodeURIComponent(urlTags) : "";
|
||||
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())))
|
||||
{resetFilterTags()
|
||||
decodedTagsArray?.map(urlTag => {
|
||||
const tag = tags.find(t => t.name.toLocaleLowerCase() === urlTag.toLocaleLowerCase())
|
||||
tag && addFilterTag(tag)
|
||||
});}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location, tags]);
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search)
|
||||
const urlTags = params.get('tags')
|
||||
const decodedTags = urlTags ? decodeURIComponent(urlTags) : ''
|
||||
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()))) {
|
||||
resetFilterTags()
|
||||
decodedTagsArray?.map(urlTag => {
|
||||
const tag = tags.find(t => t.name.toLocaleLowerCase() === urlTag.toLocaleLowerCase())
|
||||
tag && addFilterTag(tag)
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location, tags])
|
||||
|
||||
return (
|
||||
<></>
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { UtopiaMapProps } from "../../types";
|
||||
import { ContextWrapper } from "../AppShell/ContextWrapper";
|
||||
import { UtopiaMapInner } from "./UtopiaMapInner";
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { UtopiaMapProps } from '../../types'
|
||||
import { ContextWrapper } from '../AppShell/ContextWrapper'
|
||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
|
||||
function UtopiaMap(props: UtopiaMapProps) {
|
||||
return (
|
||||
function UtopiaMap (props: UtopiaMapProps) {
|
||||
return (
|
||||
<ContextWrapper>
|
||||
<UtopiaMapInner {...props} />
|
||||
</ContextWrapper>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export { UtopiaMap };
|
||||
export { UtopiaMap }
|
||||
|
||||
@ -1,107 +1,104 @@
|
||||
import { TileLayer, MapContainer, useMapEvents, GeoJSON } from "react-leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import * as React from "react";
|
||||
import { UtopiaMapProps } from "../../types";
|
||||
import "./UtopiaMap.css";
|
||||
import { LatLng } from "leaflet";
|
||||
import MarkerClusterGroup from 'react-leaflet-cluster';
|
||||
import AddButton from "./Subcomponents/AddButton";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup";
|
||||
import { SearchControl } from "./Subcomponents/Controls/SearchControl";
|
||||
import { Control } from "./Subcomponents/Controls/Control";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { TagsControl } from "./Subcomponents/Controls/TagsControl";
|
||||
import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from "./hooks/useSelectPosition";
|
||||
import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef";
|
||||
import { Feature, Geometry as GeoJSONGeometry } from 'geojson';
|
||||
import { FilterControl } from "./Subcomponents/Controls/FilterControl";
|
||||
import { LayerControl } from "./Subcomponents/Controls/LayerControl";
|
||||
import { useLayers } from "./hooks/useLayers";
|
||||
import { useAddVisibleLayer } from "./hooks/useFilter";
|
||||
import { GratitudeControl } from "./Subcomponents/Controls/GratitudeControl";
|
||||
import { SelectPosition } from "./Subcomponents/SelectPosition";
|
||||
import { TileLayer, MapContainer, useMapEvents, GeoJSON } from 'react-leaflet'
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import * as React from 'react'
|
||||
import { UtopiaMapProps } from '../../types'
|
||||
import './UtopiaMap.css'
|
||||
import { LatLng } from 'leaflet'
|
||||
import MarkerClusterGroup from 'react-leaflet-cluster'
|
||||
import AddButton from './Subcomponents/AddButton'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ItemFormPopupProps } from './Subcomponents/ItemFormPopup'
|
||||
import { SearchControl } from './Subcomponents/Controls/SearchControl'
|
||||
import { Control } from './Subcomponents/Controls/Control'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
||||
import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from './hooks/useSelectPosition'
|
||||
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
|
||||
import { Feature, Geometry as GeoJSONGeometry } from 'geojson'
|
||||
import { FilterControl } from './Subcomponents/Controls/FilterControl'
|
||||
import { LayerControl } from './Subcomponents/Controls/LayerControl'
|
||||
import { useLayers } from './hooks/useLayers'
|
||||
import { useAddVisibleLayer } from './hooks/useFilter'
|
||||
import { GratitudeControl } from './Subcomponents/Controls/GratitudeControl'
|
||||
import { SelectPosition } from './Subcomponents/SelectPosition'
|
||||
import { toast } from 'react-toastify'
|
||||
import { TextView } from "./Subcomponents/ItemPopupComponents/TextView";
|
||||
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
||||
|
||||
const mapDivRef = React.createRef()
|
||||
|
||||
const mapDivRef = React.createRef();
|
||||
|
||||
export function UtopiaMapInner({
|
||||
height = "500px",
|
||||
width = "100%",
|
||||
center = [50.6, 9.5],
|
||||
zoom = 10,
|
||||
children,
|
||||
geo,
|
||||
showFilterControl = false,
|
||||
showGratitudeControl = false,
|
||||
showLayerControl = true,
|
||||
infoText
|
||||
export function UtopiaMapInner ({
|
||||
height = '500px',
|
||||
width = '100%',
|
||||
center = [50.6, 9.5],
|
||||
zoom = 10,
|
||||
children,
|
||||
geo,
|
||||
showFilterControl = false,
|
||||
showGratitudeControl = false,
|
||||
showLayerControl = true,
|
||||
infoText
|
||||
}: UtopiaMapProps) {
|
||||
// Hooks that rely on contexts, called after ContextWrapper is provided
|
||||
const selectNewItemPosition = useSelectPosition()
|
||||
const setSelectNewItemPosition = useSetSelectPosition()
|
||||
const setClusterRef = useSetClusterRef()
|
||||
const clusterRef = useClusterRef()
|
||||
const setMapClicked = useSetMapClicked()
|
||||
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null)
|
||||
|
||||
// Hooks that rely on contexts, called after ContextWrapper is provided
|
||||
const selectNewItemPosition = useSelectPosition();
|
||||
const setSelectNewItemPosition = useSetSelectPosition();
|
||||
const setClusterRef = useSetClusterRef();
|
||||
const clusterRef = useClusterRef();
|
||||
const setMapClicked = useSetMapClicked();
|
||||
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null);
|
||||
const layers = useLayers()
|
||||
const addVisibleLayer = useAddVisibleLayer()
|
||||
|
||||
const layers = useLayers();
|
||||
const addVisibleLayer = useAddVisibleLayer();
|
||||
|
||||
useEffect(() => {
|
||||
layers.forEach(layer => addVisibleLayer(layer));
|
||||
useEffect(() => {
|
||||
layers.forEach(layer => addVisibleLayer(layer))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [layers]);
|
||||
}, [layers])
|
||||
|
||||
const init = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
infoText && setTimeout(() => {
|
||||
toast(<TextView rawText={infoText}/>, { autoClose: false});
|
||||
}, 4000);
|
||||
init.current=true;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
|
||||
function MapEventListener() {
|
||||
useMapEvents({
|
||||
click: (e) => {
|
||||
resetMetaTags();
|
||||
console.log(e.latlng.lat + ',' + e.latlng.lng);
|
||||
if (selectNewItemPosition) {
|
||||
setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup });
|
||||
}
|
||||
},
|
||||
moveend: () => { }
|
||||
});
|
||||
return null;
|
||||
const init = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
infoText && setTimeout(() => {
|
||||
toast(<TextView rawText={infoText}/>, { autoClose: false })
|
||||
}, 4000)
|
||||
init.current = true
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const resetMetaTags = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (!window.location.pathname.includes("/item/")) {
|
||||
window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`);
|
||||
function MapEventListener () {
|
||||
useMapEvents({
|
||||
click: (e) => {
|
||||
resetMetaTags()
|
||||
console.log(e.latlng.lat + ',' + e.latlng.lng)
|
||||
if (selectNewItemPosition) {
|
||||
setMapClicked({ position: e.latlng, setItemFormPopup })
|
||||
}
|
||||
document.title = document.title.split("-")[0];
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title);
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`);
|
||||
};
|
||||
},
|
||||
moveend: () => { }
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const onEachFeature = (feature: Feature<GeoJSONGeometry, any>, layer: L.Layer) => {
|
||||
if (feature.properties) {
|
||||
layer.bindPopup(feature.properties.name);
|
||||
}
|
||||
};
|
||||
const resetMetaTags = () => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
if (!window.location.pathname.includes('/item/')) {
|
||||
window.history.pushState({}, '', '/' + `${params.toString() !== '' ? `?${params}` : ''}`)
|
||||
}
|
||||
document.title = document.title.split('-')[0]
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute('content', document.title)
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute('content', `${document.querySelector('meta[name="description"]')?.getAttribute('content')}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`tw-h-full ${selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined}`}>
|
||||
<MapContainer ref={mapDivRef} style={{ height: height, width: width }} center={new LatLng(center[0], center[1])} zoom={zoom} zoomControl={false} maxZoom={19}>
|
||||
// eslint-disable-next-line no-undef
|
||||
const onEachFeature = (feature: Feature<GeoJSONGeometry, any>, layer: L.Layer) => {
|
||||
if (feature.properties) {
|
||||
layer.bindPopup(feature.properties.name)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}>
|
||||
<MapContainer ref={mapDivRef} style={{ height, width }} center={new LatLng(center[0], center[1])} zoom={zoom} zoomControl={false} maxZoom={19}>
|
||||
<Outlet />
|
||||
<Control position="topLeft" zIndex="1000" absolute>
|
||||
<SearchControl />
|
||||
@ -120,9 +117,9 @@ export function UtopiaMapInner({
|
||||
<MarkerClusterGroup ref={(r) => setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
|
||||
{
|
||||
React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child)
|
||||
? React.cloneElement(child, { setItemFormPopup: setItemFormPopup, itemFormPopup: itemFormPopup, clusterRef: clusterRef })
|
||||
: child
|
||||
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child)
|
||||
? React.cloneElement(child, { setItemFormPopup, itemFormPopup, clusterRef })
|
||||
: child
|
||||
)
|
||||
}
|
||||
</MarkerClusterGroup>
|
||||
@ -131,12 +128,12 @@ export function UtopiaMapInner({
|
||||
data={geo}
|
||||
onEachFeature={onEachFeature}
|
||||
eventHandlers={{
|
||||
click: (e) => {
|
||||
if (selectNewItemPosition) {
|
||||
e.layer!.closePopup();
|
||||
setMapClicked({ position: e.latlng, setItemFormPopup: setItemFormPopup });
|
||||
}
|
||||
},
|
||||
click: (e) => {
|
||||
if (selectNewItemPosition) {
|
||||
e.layer!.closePopup()
|
||||
setMapClicked({ position: e.latlng, setItemFormPopup })
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -145,5 +142,5 @@ export function UtopiaMapInner({
|
||||
<AddButton triggerAction={setSelectNewItemPosition} />
|
||||
{selectNewItemPosition != null && <SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,41 +1,36 @@
|
||||
import * as React from 'react'
|
||||
import { createContext, useContext, useState } from "react";
|
||||
import { createContext, useContext, useState } from 'react'
|
||||
|
||||
type UseClusterRefManagerResult = ReturnType<typeof useClusterRefManager>;
|
||||
|
||||
const ClusterRefContext = createContext<UseClusterRefManagerResult>({
|
||||
clusterRef: {} as React.MutableRefObject<undefined>,
|
||||
setClusterRef: () => { },
|
||||
});
|
||||
clusterRef: {} as React.MutableRefObject<undefined>,
|
||||
setClusterRef: () => { }
|
||||
})
|
||||
|
||||
function useClusterRefManager(): {
|
||||
function useClusterRefManager (): {
|
||||
clusterRef: any
|
||||
setClusterRef: React.Dispatch<React.SetStateAction<React.MutableRefObject<undefined>>>;
|
||||
} {
|
||||
const [clusterRef, setClusterRef] = useState<React.MutableRefObject<undefined>>({} as React.MutableRefObject<undefined>);
|
||||
|
||||
return { clusterRef, setClusterRef };
|
||||
} {
|
||||
const [clusterRef, setClusterRef] = useState<React.MutableRefObject<undefined>>({} as React.MutableRefObject<undefined>)
|
||||
|
||||
return { clusterRef, setClusterRef }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const ClusterRefProvider: React.FunctionComponent<{
|
||||
children?: React.ReactNode
|
||||
}> = ({ children }) => (
|
||||
<ClusterRefContext.Provider value={useClusterRefManager()}>
|
||||
{children}
|
||||
</ClusterRefContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useClusterRef = (): any=> {
|
||||
const { clusterRef } = useContext(ClusterRefContext);
|
||||
return clusterRef;
|
||||
};
|
||||
|
||||
export const useSetClusterRef = (): UseClusterRefManagerResult["setClusterRef"] => {
|
||||
const { setClusterRef } = useContext(ClusterRefContext);
|
||||
return setClusterRef;
|
||||
export const useClusterRef = (): any => {
|
||||
const { clusterRef } = useContext(ClusterRefContext)
|
||||
return clusterRef
|
||||
}
|
||||
|
||||
export const useSetClusterRef = (): UseClusterRefManagerResult['setClusterRef'] => {
|
||||
const { setClusterRef } = useContext(ClusterRefContext)
|
||||
return setClusterRef
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTimeout } from './useTimeout';
|
||||
import { useEffect } from 'react'
|
||||
import { useTimeout } from './useTimeout'
|
||||
|
||||
export const useDebounce = (callback, delay, deps) => {
|
||||
const { reset, clear } = useTimeout(callback, delay);
|
||||
const { reset, clear } = useTimeout(callback, delay)
|
||||
|
||||
useEffect(reset, [...deps, reset]);
|
||||
useEffect(reset, [...deps, reset])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(clear, []);
|
||||
}
|
||||
useEffect(clear, [])
|
||||
}
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { useCallback, useReducer, createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
import { LayerProps, Tag } from "../../../types";
|
||||
import { useLayers } from "./useLayers";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useWindowDimensions from "./useWindowDimension";
|
||||
import { useCallback, useReducer, createContext, useContext } from 'react'
|
||||
import * as React from 'react'
|
||||
import { LayerProps, Tag } from '../../../types'
|
||||
import { useLayers } from './useLayers'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import useWindowDimensions from './useWindowDimension'
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD_TAG"; tag: Tag }
|
||||
| { type: "REMOVE_TAG"; name: string }
|
||||
| { type: "RESET_TAGS" }
|
||||
| { type: "TOGGLE_LAYER"; layer: LayerProps }
|
||||
| { type: "ADD_LAYER"; layer: LayerProps }
|
||||
| { type: "RESET_LAYERS" }
|
||||
| { type: "TOGGLE_GROUP_TYPE"; groupType: string }
|
||||
| { type: "ADD_GROUP_TYPE"; groupType: string }
|
||||
| { type: "RESET_GROUP_TYPE" }
|
||||
| { type: 'ADD_TAG'; tag: Tag }
|
||||
| { type: 'REMOVE_TAG'; name: string }
|
||||
| { type: 'RESET_TAGS' }
|
||||
| { type: 'TOGGLE_LAYER'; layer: LayerProps }
|
||||
| { type: 'ADD_LAYER'; layer: LayerProps }
|
||||
| { type: 'RESET_LAYERS' }
|
||||
| { type: 'TOGGLE_GROUP_TYPE'; groupType: string }
|
||||
| { type: 'ADD_GROUP_TYPE'; groupType: string }
|
||||
| { type: 'RESET_GROUP_TYPE' }
|
||||
;
|
||||
|
||||
type UseFilterManagerResult = ReturnType<typeof useFilterManager>;
|
||||
|
||||
const FilterContext = createContext<UseFilterManagerResult>({
|
||||
filterTags: [],
|
||||
searchPhrase: "",
|
||||
searchPhrase: '',
|
||||
visibleLayers: [],
|
||||
visibleGroupTypes: [],
|
||||
addFilterTag: () => { },
|
||||
@ -37,9 +37,9 @@ const FilterContext = createContext<UseFilterManagerResult>({
|
||||
addVisibleGroupType: () => { },
|
||||
toggleVisibleGroupType: () => { },
|
||||
isGroupTypeVisible: () => true
|
||||
});
|
||||
})
|
||||
|
||||
function useFilterManager(initialTags: Tag[]): {
|
||||
function useFilterManager (initialTags: Tag[]): {
|
||||
filterTags: Tag[];
|
||||
searchPhrase: string;
|
||||
visibleLayers: LayerProps[];
|
||||
@ -67,182 +67,174 @@ function useFilterManager(initialTags: Tag[]): {
|
||||
} {
|
||||
const [filterTags, dispatchTags] = useReducer((state: Tag[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD_TAG":
|
||||
case 'ADD_TAG':
|
||||
const exist = state.find((tag) =>
|
||||
tag.id === action.tag.id ? true : false
|
||||
);
|
||||
if (!exist) return [
|
||||
...state,
|
||||
action.tag,
|
||||
];
|
||||
else return state;
|
||||
case "REMOVE_TAG":
|
||||
return state.filter(({ name }) => name !== action.name);
|
||||
case "RESET_TAGS":
|
||||
return initialTags;
|
||||
tag.id === action.tag.id
|
||||
)
|
||||
if (!exist) {
|
||||
return [
|
||||
...state,
|
||||
action.tag
|
||||
]
|
||||
} else return state
|
||||
case 'REMOVE_TAG':
|
||||
return state.filter(({ name }) => name !== action.name)
|
||||
case 'RESET_TAGS':
|
||||
return initialTags
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialTags);
|
||||
}, initialTags)
|
||||
|
||||
const initialLayers = useLayers()
|
||||
const navigate = useNavigate()
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const windowDimensions = useWindowDimensions()
|
||||
|
||||
const [visibleLayers, dispatchLayers] = useReducer((state: LayerProps[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD_LAYER":
|
||||
case 'ADD_LAYER':
|
||||
const exist1 = state.find((layer) =>
|
||||
layer.name === action.layer.name ? true : false
|
||||
);
|
||||
if (!exist1) return [
|
||||
...state,
|
||||
action.layer,
|
||||
];
|
||||
else return state;
|
||||
case "TOGGLE_LAYER":
|
||||
layer.name === action.layer.name
|
||||
)
|
||||
if (!exist1) {
|
||||
return [
|
||||
...state,
|
||||
action.layer
|
||||
]
|
||||
} else return state
|
||||
case 'TOGGLE_LAYER':
|
||||
const exist2 = state.some((layer) =>
|
||||
layer.name === action.layer.name);
|
||||
if(exist2) return state.filter(({name}) => name != action.layer.name);
|
||||
else return [... state, action.layer];
|
||||
case "RESET_LAYERS":
|
||||
return initialLayers;
|
||||
layer.name === action.layer.name)
|
||||
if (exist2) return state.filter(({ name }) => name !== action.layer.name)
|
||||
else return [...state, action.layer]
|
||||
case 'RESET_LAYERS':
|
||||
return initialLayers
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialLayers);
|
||||
}, initialLayers)
|
||||
|
||||
const [visibleGroupTypes, dispatchGroupTypes] = useReducer((state: string[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD_GROUP_TYPE":
|
||||
case 'ADD_GROUP_TYPE':
|
||||
const exist1 = state.find((groupType) =>
|
||||
groupType === action.groupType ? true : false
|
||||
);
|
||||
if (!exist1) return [
|
||||
...state,
|
||||
action.groupType,
|
||||
];
|
||||
else return state;
|
||||
case "TOGGLE_GROUP_TYPE":
|
||||
groupType === action.groupType
|
||||
)
|
||||
if (!exist1) {
|
||||
return [
|
||||
...state,
|
||||
action.groupType
|
||||
]
|
||||
} else return state
|
||||
case 'TOGGLE_GROUP_TYPE':
|
||||
const exist2 = state.some((groupType) =>
|
||||
groupType === action.groupType);
|
||||
if(exist2) return state.filter((groupType) => groupType != action.groupType);
|
||||
else return [... state, action.groupType];
|
||||
case "RESET_GROUP_TYPE":
|
||||
return [];
|
||||
groupType === action.groupType)
|
||||
if (exist2) return state.filter((groupType) => groupType !== action.groupType)
|
||||
else return [...state, action.groupType]
|
||||
case 'RESET_GROUP_TYPE':
|
||||
return []
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
},[]);
|
||||
}, [])
|
||||
|
||||
const [searchPhrase, searchPhraseSet] = React.useState<string>("");
|
||||
const [searchPhrase, searchPhraseSet] = React.useState<string>('')
|
||||
|
||||
const addFilterTag = useCallback((tag: Tag) => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const urlTags = params.get("tags")
|
||||
const decodedTags = urlTags ? decodeURIComponent(urlTags) : "";
|
||||
|
||||
if(!decodedTags?.includes(tag.name))
|
||||
params.set("tags", `${urlTags ? urlTags : ""}${urlTags? ';' : ''}${tag.name}`)
|
||||
if(windowDimensions.width < 786 && location.pathname.split("/").length > 2) navigate("/" + `${params ? `?${params}` : ""}`);
|
||||
else navigate(location.pathname + `${params ? `?${params}` : ""}`);
|
||||
|
||||
const params = new URLSearchParams(location.search)
|
||||
const urlTags = params.get('tags')
|
||||
const decodedTags = urlTags ? decodeURIComponent(urlTags) : ''
|
||||
|
||||
|
||||
if (!decodedTags?.includes(tag.name)) { params.set('tags', `${urlTags || ''}${urlTags ? ';' : ''}${tag.name}`) }
|
||||
if (windowDimensions.width < 786 && location.pathname.split('/').length > 2) navigate('/' + `${params ? `?${params}` : ''}`)
|
||||
else navigate(location.pathname + `${params ? `?${params}` : ''}`)
|
||||
|
||||
dispatchTags({
|
||||
type: "ADD_TAG",
|
||||
tag,
|
||||
});
|
||||
type: 'ADD_TAG',
|
||||
tag
|
||||
})
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const removeFilterTag = useCallback((name: string) => {
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const urlTags = params.get("tags");
|
||||
let newUrlTags = "";
|
||||
const tags = urlTags?.split(";");
|
||||
if(tags?.length==0 && urlTags?.length && urlTags?.length > 0) tags[0]=urlTags;
|
||||
tags?.map(urlTag => {
|
||||
if(!(urlTag.toLocaleLowerCase() === name.toLocaleLowerCase()))
|
||||
newUrlTags = newUrlTags + `${newUrlTags===""? urlTag : `;${urlTag}`}`
|
||||
});
|
||||
if(newUrlTags !== "") {
|
||||
params.set("tags", `${newUrlTags}`)
|
||||
navigate(location.pathname + `${params ? `?${params}` : ""}`);
|
||||
}
|
||||
else {
|
||||
params.delete("tags");
|
||||
navigate(location.pathname + `${params ? `?${params}` : ""}`);
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const urlTags = params.get('tags')
|
||||
let newUrlTags = ''
|
||||
const tags = urlTags?.split(';')
|
||||
if (tags?.length === 0 && urlTags?.length && urlTags?.length > 0) tags[0] = urlTags
|
||||
tags?.map(urlTag => {
|
||||
if (!(urlTag.toLocaleLowerCase() === name.toLocaleLowerCase())) { newUrlTags = newUrlTags + `${newUrlTags === '' ? urlTag : `;${urlTag}`}` }
|
||||
return null
|
||||
})
|
||||
if (newUrlTags !== '') {
|
||||
params.set('tags', `${newUrlTags}`)
|
||||
navigate(location.pathname + `${params ? `?${params}` : ''}`)
|
||||
} else {
|
||||
params.delete('tags')
|
||||
navigate(location.pathname + `${params ? `?${params}` : ''}`)
|
||||
}
|
||||
|
||||
dispatchTags({
|
||||
type: "REMOVE_TAG",
|
||||
name,
|
||||
});
|
||||
type: 'REMOVE_TAG',
|
||||
name
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const resetFilterTags = useCallback(() => {
|
||||
dispatchTags({
|
||||
type: "RESET_TAGS",
|
||||
});
|
||||
}, []);
|
||||
type: 'RESET_TAGS'
|
||||
})
|
||||
}, [])
|
||||
|
||||
const addVisibleLayer = (layer: LayerProps) => {
|
||||
dispatchLayers({
|
||||
type: "ADD_LAYER",
|
||||
layer,
|
||||
});
|
||||
|
||||
};
|
||||
type: 'ADD_LAYER',
|
||||
layer
|
||||
})
|
||||
}
|
||||
|
||||
const toggleVisibleLayer = (layer: LayerProps) => {
|
||||
dispatchLayers({
|
||||
type: "TOGGLE_LAYER",
|
||||
layer,
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
type: 'TOGGLE_LAYER',
|
||||
layer
|
||||
})
|
||||
}
|
||||
|
||||
const resetVisibleLayers = useCallback(() => {
|
||||
dispatchLayers({
|
||||
type: "RESET_LAYERS",
|
||||
});
|
||||
}, []);
|
||||
type: 'RESET_LAYERS'
|
||||
})
|
||||
}, [])
|
||||
|
||||
const isLayerVisible = useCallback((layer: LayerProps) => {
|
||||
return visibleLayers.some(l => l.name === layer.name)
|
||||
}, [visibleLayers]);
|
||||
}, [visibleLayers])
|
||||
|
||||
const addVisibleGroupType = (groupType: string) => {
|
||||
dispatchGroupTypes({
|
||||
type: "ADD_GROUP_TYPE",
|
||||
groupType,
|
||||
});
|
||||
|
||||
};
|
||||
type: 'ADD_GROUP_TYPE',
|
||||
groupType
|
||||
})
|
||||
}
|
||||
|
||||
const toggleVisibleGroupType = (groupType: string) => {
|
||||
dispatchGroupTypes({
|
||||
type: "TOGGLE_GROUP_TYPE",
|
||||
groupType,
|
||||
});
|
||||
|
||||
};
|
||||
type: 'TOGGLE_GROUP_TYPE',
|
||||
groupType
|
||||
})
|
||||
}
|
||||
|
||||
const isGroupTypeVisible = useCallback((groupType: string) => {
|
||||
return visibleGroupTypes.some(gt => gt === groupType)
|
||||
}, [visibleGroupTypes]);
|
||||
}, [visibleGroupTypes])
|
||||
|
||||
const setSearchPhrase = useCallback((phrase: string) => {
|
||||
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<{
|
||||
@ -251,82 +243,79 @@ export const FilterProvider: React.FunctionComponent<{
|
||||
<FilterContext.Provider value={useFilterManager(initialTags)}>
|
||||
{children}
|
||||
</FilterContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useFilterTags = (): Tag[] => {
|
||||
const { filterTags } = useContext(FilterContext);
|
||||
return filterTags;
|
||||
};
|
||||
const { filterTags } = useContext(FilterContext)
|
||||
return filterTags
|
||||
}
|
||||
|
||||
export const useAddFilterTag = (): UseFilterManagerResult["addFilterTag"] => {
|
||||
const { addFilterTag } = useContext(FilterContext);
|
||||
return addFilterTag;
|
||||
};
|
||||
export const useAddFilterTag = (): UseFilterManagerResult['addFilterTag'] => {
|
||||
const { addFilterTag } = useContext(FilterContext)
|
||||
return addFilterTag
|
||||
}
|
||||
|
||||
export const useRemoveFilterTag = (): UseFilterManagerResult["removeFilterTag"] => {
|
||||
const { removeFilterTag } = useContext(FilterContext);
|
||||
return removeFilterTag;
|
||||
};
|
||||
export const useRemoveFilterTag = (): UseFilterManagerResult['removeFilterTag'] => {
|
||||
const { removeFilterTag } = useContext(FilterContext)
|
||||
return removeFilterTag
|
||||
}
|
||||
|
||||
export const useResetFilterTags = (): UseFilterManagerResult["resetFilterTags"] => {
|
||||
const { resetFilterTags } = useContext(FilterContext);
|
||||
return resetFilterTags;
|
||||
};
|
||||
export const useResetFilterTags = (): UseFilterManagerResult['resetFilterTags'] => {
|
||||
const { resetFilterTags } = useContext(FilterContext)
|
||||
return resetFilterTags
|
||||
}
|
||||
|
||||
export const useSearchPhrase = (): UseFilterManagerResult["searchPhrase"] => {
|
||||
const { searchPhrase } = useContext(FilterContext);
|
||||
return searchPhrase;
|
||||
};
|
||||
export const useSearchPhrase = (): UseFilterManagerResult['searchPhrase'] => {
|
||||
const { searchPhrase } = useContext(FilterContext)
|
||||
return searchPhrase
|
||||
}
|
||||
|
||||
export const useSetSearchPhrase = (): UseFilterManagerResult["setSearchPhrase"] => {
|
||||
const { setSearchPhrase } = useContext(FilterContext);
|
||||
return setSearchPhrase;
|
||||
};
|
||||
export const useSetSearchPhrase = (): UseFilterManagerResult['setSearchPhrase'] => {
|
||||
const { setSearchPhrase } = useContext(FilterContext)
|
||||
return setSearchPhrase
|
||||
}
|
||||
|
||||
export const useVisibleLayer = (): UseFilterManagerResult["visibleLayers"] => {
|
||||
const { visibleLayers } = useContext(FilterContext);
|
||||
return visibleLayers;
|
||||
};
|
||||
export const useVisibleLayer = (): UseFilterManagerResult['visibleLayers'] => {
|
||||
const { visibleLayers } = useContext(FilterContext)
|
||||
return visibleLayers
|
||||
}
|
||||
|
||||
export const useAddVisibleLayer = (): UseFilterManagerResult["addVisibleLayer"] => {
|
||||
const { addVisibleLayer } = useContext(FilterContext);
|
||||
return addVisibleLayer;
|
||||
};
|
||||
export const useAddVisibleLayer = (): UseFilterManagerResult['addVisibleLayer'] => {
|
||||
const { addVisibleLayer } = useContext(FilterContext)
|
||||
return addVisibleLayer
|
||||
}
|
||||
|
||||
export const useToggleVisibleLayer = (): UseFilterManagerResult['toggleVisibleLayer'] => {
|
||||
const { toggleVisibleLayer } = useContext(FilterContext)
|
||||
return toggleVisibleLayer
|
||||
}
|
||||
|
||||
export const useToggleVisibleLayer = (): UseFilterManagerResult["toggleVisibleLayer"] => {
|
||||
const { toggleVisibleLayer } = useContext(FilterContext);
|
||||
return toggleVisibleLayer;
|
||||
};
|
||||
export const useResetVisibleLayers = (): UseFilterManagerResult['resetVisibleLayers'] => {
|
||||
const { resetVisibleLayers } = useContext(FilterContext)
|
||||
return resetVisibleLayers
|
||||
}
|
||||
|
||||
export const useResetVisibleLayers = (): UseFilterManagerResult["resetVisibleLayers"] => {
|
||||
const { resetVisibleLayers } = useContext(FilterContext);
|
||||
return resetVisibleLayers;
|
||||
};
|
||||
export const useIsLayerVisible = (): UseFilterManagerResult['isLayerVisible'] => {
|
||||
const { isLayerVisible } = useContext(FilterContext)
|
||||
return isLayerVisible
|
||||
}
|
||||
|
||||
export const useIsLayerVisible = (): UseFilterManagerResult["isLayerVisible"] => {
|
||||
const { isLayerVisible } = useContext(FilterContext);
|
||||
return isLayerVisible;
|
||||
};
|
||||
export const useAddVisibleGroupType = (): UseFilterManagerResult['addVisibleGroupType'] => {
|
||||
const { addVisibleGroupType } = useContext(FilterContext)
|
||||
return addVisibleGroupType
|
||||
}
|
||||
|
||||
export const useAddVisibleGroupType = (): UseFilterManagerResult["addVisibleGroupType"] => {
|
||||
const { addVisibleGroupType } = useContext(FilterContext);
|
||||
return addVisibleGroupType;
|
||||
};
|
||||
export const useToggleVisibleGroupType = (): UseFilterManagerResult['toggleVisibleGroupType'] => {
|
||||
const { toggleVisibleGroupType } = useContext(FilterContext)
|
||||
return toggleVisibleGroupType
|
||||
}
|
||||
|
||||
|
||||
export const useToggleVisibleGroupType = (): UseFilterManagerResult["toggleVisibleGroupType"] => {
|
||||
const { toggleVisibleGroupType } = useContext(FilterContext);
|
||||
return toggleVisibleGroupType;
|
||||
};
|
||||
|
||||
|
||||
export const useIsGroupTypeVisible = (): UseFilterManagerResult["isGroupTypeVisible"] => {
|
||||
const { isGroupTypeVisible } = useContext(FilterContext);
|
||||
export const useIsGroupTypeVisible = (): UseFilterManagerResult['isGroupTypeVisible'] => {
|
||||
const { isGroupTypeVisible } = useContext(FilterContext)
|
||||
return isGroupTypeVisible
|
||||
};
|
||||
}
|
||||
|
||||
export const useVisibleGroupType = (): UseFilterManagerResult["visibleGroupTypes"] => {
|
||||
const { visibleGroupTypes } = useContext(FilterContext);
|
||||
return visibleGroupTypes;
|
||||
};
|
||||
export const useVisibleGroupType = (): UseFilterManagerResult['visibleGroupTypes'] => {
|
||||
const { visibleGroupTypes } = useContext(FilterContext)
|
||||
return visibleGroupTypes
|
||||
}
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from "react";
|
||||
import * as React from "react";
|
||||
import { Item, LayerProps } from "../../../types";
|
||||
import { toast } from "react-toastify";
|
||||
import { useAddLayer } from "./useLayers";
|
||||
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
import { Item, LayerProps } from '../../../types'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useAddLayer } from './useLayers'
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD"; item: Item }
|
||||
| { type: "UPDATE"; item: Item }
|
||||
| { type: "REMOVE"; item: Item }
|
||||
| { type: "RESET"; layer: LayerProps }
|
||||
| { type: 'ADD'; item: Item }
|
||||
| { type: 'UPDATE'; item: Item }
|
||||
| { type: 'REMOVE'; item: Item }
|
||||
| { type: 'RESET'; layer: LayerProps }
|
||||
;
|
||||
|
||||
|
||||
type UseItemManagerResult = ReturnType<typeof useItemsManager>;
|
||||
|
||||
const ItemContext = createContext<UseItemManagerResult>({
|
||||
@ -24,9 +22,9 @@ const ItemContext = createContext<UseItemManagerResult>({
|
||||
setItemsApi: () => { },
|
||||
setItemsData: () => { },
|
||||
allItemsLoaded: false
|
||||
});
|
||||
})
|
||||
|
||||
function useItemsManager(initialItems: Item[]): {
|
||||
function useItemsManager (initialItems: Item[]): {
|
||||
items: Item[];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
addItem: (item: Item) => void;
|
||||
@ -43,106 +41,102 @@ function useItemsManager(initialItems: Item[]): {
|
||||
allItemsLoaded: boolean;
|
||||
|
||||
} {
|
||||
const addLayer = useAddLayer()
|
||||
|
||||
const addLayer = useAddLayer();
|
||||
|
||||
const [allItemsLoaded, setallItemsLoaded] = useState<boolean>(false);
|
||||
|
||||
|
||||
const [allItemsLoaded, setallItemsLoaded] = useState<boolean>(false)
|
||||
|
||||
const [items, dispatch] = useReducer((state: Item[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD":
|
||||
case 'ADD':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const exist = state.find((item) =>
|
||||
item.id === action.item.id ? true : false
|
||||
);
|
||||
if (!exist) return [
|
||||
...state,
|
||||
action.item,
|
||||
];
|
||||
else return state;
|
||||
case "UPDATE":
|
||||
item.id === action.item.id
|
||||
)
|
||||
if (!exist) {
|
||||
return [
|
||||
...state,
|
||||
action.item
|
||||
]
|
||||
} else return state
|
||||
case 'UPDATE':
|
||||
return state.map((item) => {
|
||||
if (item.id === action.item.id) {
|
||||
return action.item
|
||||
}
|
||||
return item
|
||||
});
|
||||
case "REMOVE":
|
||||
return state.filter(item => item !== action.item);
|
||||
case "RESET":
|
||||
return state.filter(item => item.layer?.name !== action.layer.name);
|
||||
})
|
||||
case 'REMOVE':
|
||||
return state.filter(item => item !== action.item)
|
||||
case 'RESET':
|
||||
return state.filter(item => item.layer?.name !== action.layer.name)
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialItems);
|
||||
|
||||
}, initialItems)
|
||||
|
||||
const setItemsApi = useCallback(async (layer: LayerProps) => {
|
||||
addLayer(layer);
|
||||
addLayer(layer)
|
||||
const result = await toast.promise(
|
||||
layer.api!.getItems(),
|
||||
{
|
||||
pending: `loading ${layer.name} ...`,
|
||||
success: `${layer.name} loaded`,
|
||||
error: {
|
||||
render( {data} ) {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
if (result) {
|
||||
result.map(item => {
|
||||
dispatch({ type: "ADD", item: { ...item, layer: layer } });
|
||||
)
|
||||
if (result) {
|
||||
result.map(item => {
|
||||
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||
return null
|
||||
})
|
||||
setallItemsLoaded(true);
|
||||
setallItemsLoaded(true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const setItemsData = useCallback((layer: LayerProps) => {
|
||||
addLayer(layer);
|
||||
addLayer(layer)
|
||||
layer.data?.map(item => {
|
||||
dispatch({ type: "ADD", item: { ...item, layer: layer } });
|
||||
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||
return null
|
||||
})
|
||||
setallItemsLoaded(true);
|
||||
setallItemsLoaded(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
|
||||
const addItem = useCallback(async (item: Item) => {
|
||||
const addItem = useCallback(async (item: Item) => {
|
||||
dispatch({
|
||||
type: "ADD",
|
||||
item,
|
||||
});
|
||||
}, []);
|
||||
type: 'ADD',
|
||||
item
|
||||
})
|
||||
}, [])
|
||||
|
||||
const updateItem = useCallback(async (item: Item) => {
|
||||
dispatch({
|
||||
type: "UPDATE",
|
||||
item,
|
||||
});
|
||||
}, []);
|
||||
type: 'UPDATE',
|
||||
item
|
||||
})
|
||||
}, [])
|
||||
|
||||
const removeItem = useCallback((item: Item) => {
|
||||
dispatch({
|
||||
type: "REMOVE",
|
||||
item,
|
||||
});
|
||||
}, []);
|
||||
type: 'REMOVE',
|
||||
item
|
||||
})
|
||||
}, [])
|
||||
|
||||
const resetItems = useCallback((layer: LayerProps) => {
|
||||
dispatch({
|
||||
type: "RESET",
|
||||
type: 'RESET',
|
||||
layer
|
||||
});
|
||||
}, []);
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
return { items, updateItem, addItem, removeItem, resetItems, setItemsApi, setItemsData, allItemsLoaded };
|
||||
return { items, updateItem, addItem, removeItem, resetItems, setItemsApi, setItemsData, allItemsLoaded }
|
||||
}
|
||||
|
||||
export const ItemsProvider: React.FunctionComponent<{
|
||||
@ -151,44 +145,44 @@ export const ItemsProvider: React.FunctionComponent<{
|
||||
<ItemContext.Provider value={useItemsManager(initialItems)}>
|
||||
{children}
|
||||
</ItemContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useItems = (): Item[] => {
|
||||
const { items } = useContext(ItemContext);
|
||||
return items;
|
||||
};
|
||||
const { items } = useContext(ItemContext)
|
||||
return items
|
||||
}
|
||||
|
||||
export const useAddItem = (): UseItemManagerResult["addItem"] => {
|
||||
const { addItem } = useContext(ItemContext);
|
||||
return addItem;
|
||||
};
|
||||
export const useAddItem = (): UseItemManagerResult['addItem'] => {
|
||||
const { addItem } = useContext(ItemContext)
|
||||
return addItem
|
||||
}
|
||||
|
||||
export const useUpdateItem = (): UseItemManagerResult["updateItem"] => {
|
||||
const { updateItem } = useContext(ItemContext);
|
||||
return updateItem;
|
||||
};
|
||||
export const useUpdateItem = (): UseItemManagerResult['updateItem'] => {
|
||||
const { updateItem } = useContext(ItemContext)
|
||||
return updateItem
|
||||
}
|
||||
|
||||
export const useRemoveItem = (): UseItemManagerResult["removeItem"] => {
|
||||
const { removeItem } = useContext(ItemContext);
|
||||
return removeItem;
|
||||
};
|
||||
export const useRemoveItem = (): UseItemManagerResult['removeItem'] => {
|
||||
const { removeItem } = useContext(ItemContext)
|
||||
return removeItem
|
||||
}
|
||||
|
||||
export const useResetItems = (): UseItemManagerResult["resetItems"] => {
|
||||
const { resetItems } = useContext(ItemContext);
|
||||
return resetItems;
|
||||
};
|
||||
export const useResetItems = (): UseItemManagerResult['resetItems'] => {
|
||||
const { resetItems } = useContext(ItemContext)
|
||||
return resetItems
|
||||
}
|
||||
|
||||
export const useSetItemsApi = (): UseItemManagerResult["setItemsApi"] => {
|
||||
const { setItemsApi } = useContext(ItemContext);
|
||||
return setItemsApi;
|
||||
};
|
||||
export const useSetItemsApi = (): UseItemManagerResult['setItemsApi'] => {
|
||||
const { setItemsApi } = useContext(ItemContext)
|
||||
return setItemsApi
|
||||
}
|
||||
|
||||
export const useSetItemsData = (): UseItemManagerResult["setItemsData"] => {
|
||||
const { setItemsData } = useContext(ItemContext);
|
||||
return setItemsData;
|
||||
};
|
||||
export const useSetItemsData = (): UseItemManagerResult['setItemsData'] => {
|
||||
const { setItemsData } = useContext(ItemContext)
|
||||
return setItemsData
|
||||
}
|
||||
|
||||
export const useAllItemsLoaded = (): UseItemManagerResult["allItemsLoaded"] => {
|
||||
const { allItemsLoaded } = useContext(ItemContext);
|
||||
return allItemsLoaded;
|
||||
}
|
||||
export const useAllItemsLoaded = (): UseItemManagerResult['allItemsLoaded'] => {
|
||||
const { allItemsLoaded } = useContext(ItemContext)
|
||||
return allItemsLoaded
|
||||
}
|
||||
|
||||
@ -1,47 +1,48 @@
|
||||
import { useCallback, useReducer, createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
import { LayerProps } from "../../../types";
|
||||
import { useCallback, useReducer, createContext, useContext } from 'react'
|
||||
import * as React from 'react'
|
||||
import { LayerProps } from '../../../types'
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD LAYER"; layer: LayerProps }
|
||||
| { type: 'ADD LAYER'; layer: LayerProps }
|
||||
|
||||
type UseItemManagerResult = ReturnType<typeof useLayerManager>;
|
||||
|
||||
const LayerContext = createContext<UseItemManagerResult>({
|
||||
layers: [],
|
||||
addLayer: () => { },
|
||||
});
|
||||
addLayer: () => { }
|
||||
})
|
||||
|
||||
function useLayerManager(initialLayers: LayerProps[]): {
|
||||
function useLayerManager (initialLayers: LayerProps[]): {
|
||||
layers: LayerProps[];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
addLayer: (layer: LayerProps) => void;
|
||||
} {
|
||||
const [layers, dispatch] = useReducer((state: LayerProps[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD LAYER":
|
||||
case 'ADD LAYER':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const exist = state.find((layer) =>
|
||||
layer.name === action.layer.name ? true : false
|
||||
);
|
||||
if (!exist) return [
|
||||
...state,
|
||||
action.layer,
|
||||
];
|
||||
else return state;
|
||||
layer.name === action.layer.name
|
||||
)
|
||||
if (!exist) {
|
||||
return [
|
||||
...state,
|
||||
action.layer
|
||||
]
|
||||
} else return state
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialLayers);
|
||||
}, initialLayers)
|
||||
|
||||
const addLayer = useCallback((layer: LayerProps) => {
|
||||
dispatch({
|
||||
type: "ADD LAYER",
|
||||
type: 'ADD LAYER',
|
||||
layer
|
||||
});
|
||||
}, []);
|
||||
})
|
||||
}, [])
|
||||
|
||||
return { layers, addLayer };
|
||||
return { layers, addLayer }
|
||||
}
|
||||
|
||||
export const LayersProvider: React.FunctionComponent<{
|
||||
@ -50,14 +51,14 @@ export const LayersProvider: React.FunctionComponent<{
|
||||
<LayerContext.Provider value={useLayerManager(initialLayers)}>
|
||||
{children}
|
||||
</LayerContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useLayers = (): LayerProps[] => {
|
||||
const { layers } = useContext(LayerContext);
|
||||
return layers;
|
||||
};
|
||||
const { layers } = useContext(LayerContext)
|
||||
return layers
|
||||
}
|
||||
|
||||
export const useAddLayer = (): UseItemManagerResult["addLayer"] => {
|
||||
const { addLayer } = useContext(LayerContext);
|
||||
return addLayer;
|
||||
};
|
||||
export const useAddLayer = (): UseItemManagerResult['addLayer'] => {
|
||||
const { addLayer } = useContext(LayerContext)
|
||||
return addLayer
|
||||
}
|
||||
|
||||
@ -1,75 +1,68 @@
|
||||
import { useCallback, useReducer, createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
import { Item } from "../../../types";
|
||||
import { Marker, Popup } from "leaflet";
|
||||
import { useCallback, useReducer, createContext, useContext } from 'react'
|
||||
import * as React from 'react'
|
||||
import { Item } from '../../../types'
|
||||
import { Marker, Popup } from 'leaflet'
|
||||
|
||||
type LeafletRef = {
|
||||
type LeafletRef = {
|
||||
item: Item,
|
||||
marker: Marker,
|
||||
popup: Popup
|
||||
}
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD_MARKER"; item: Item, marker: Marker }
|
||||
| { type: "ADD_POPUP"; item: Item, popup: Popup }
|
||||
| { type: 'ADD_MARKER'; item: Item, marker: Marker }
|
||||
| { type: 'ADD_POPUP'; item: Item, popup: Popup }
|
||||
;
|
||||
|
||||
|
||||
type UseLeafletRefsManagerResult = ReturnType<typeof useLeafletRefsManager>;
|
||||
|
||||
const LeafletRefsContext = createContext<UseLeafletRefsManagerResult>({
|
||||
leafletRefs: {},
|
||||
addMarker: () => { },
|
||||
addPopup: () => { },
|
||||
});
|
||||
addPopup: () => { }
|
||||
})
|
||||
|
||||
function useLeafletRefsManager(initialLeafletRefs: {}): {
|
||||
leafletRefs: Record<string,LeafletRef>;
|
||||
function useLeafletRefsManager (initialLeafletRefs: {}): {
|
||||
leafletRefs: Record<string, LeafletRef>;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
addMarker: (item: Item, marker: Marker) => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
addPopup: (item: Item, popup: Popup) => void;
|
||||
} {
|
||||
|
||||
|
||||
|
||||
const [leafletRefs, dispatch] = useReducer((state: Record<string,LeafletRef>, action: ActionType) => {
|
||||
const [leafletRefs, dispatch] = useReducer((state: Record<string, LeafletRef>, action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD_MARKER":
|
||||
case 'ADD_MARKER':
|
||||
return {
|
||||
...state,
|
||||
[action.item.id]: { ...state[action.item.id], marker: action.marker, item: action.item }}
|
||||
case "ADD_POPUP":
|
||||
[action.item.id]: { ...state[action.item.id], marker: action.marker, item: action.item }
|
||||
}
|
||||
case 'ADD_POPUP':
|
||||
return {
|
||||
...state,
|
||||
[action.item.id]: { ...state[action.item.id], popup: action.popup, item: action.item }}
|
||||
[action.item.id]: { ...state[action.item.id], popup: action.popup, item: action.item }
|
||||
}
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialLeafletRefs);
|
||||
|
||||
|
||||
|
||||
}, initialLeafletRefs)
|
||||
|
||||
const addMarker = useCallback((item: Item, marker: Marker) => {
|
||||
dispatch({
|
||||
type: "ADD_MARKER",
|
||||
type: 'ADD_MARKER',
|
||||
item,
|
||||
marker
|
||||
});
|
||||
}, []);
|
||||
})
|
||||
}, [])
|
||||
|
||||
const addPopup = useCallback((item: Item, popup: Popup) => {
|
||||
dispatch({
|
||||
type: "ADD_POPUP",
|
||||
type: 'ADD_POPUP',
|
||||
item,
|
||||
popup
|
||||
});
|
||||
}, []);
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
return { leafletRefs, addMarker, addPopup };
|
||||
return { leafletRefs, addMarker, addPopup }
|
||||
}
|
||||
|
||||
export const LeafletRefsProvider: React.FunctionComponent<{
|
||||
@ -78,20 +71,19 @@ export const LeafletRefsProvider: React.FunctionComponent<{
|
||||
<LeafletRefsContext.Provider value={useLeafletRefsManager(initialLeafletRefs)}>
|
||||
{children}
|
||||
</LeafletRefsContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useLeafletRefs = (): Record<string,LeafletRef> => {
|
||||
const { leafletRefs } = useContext(LeafletRefsContext);
|
||||
return leafletRefs;
|
||||
};
|
||||
export const useLeafletRefs = (): Record<string, LeafletRef> => {
|
||||
const { leafletRefs } = useContext(LeafletRefsContext)
|
||||
return leafletRefs
|
||||
}
|
||||
|
||||
export const useAddMarker = (): UseLeafletRefsManagerResult["addMarker"] => {
|
||||
const { addMarker } = useContext(LeafletRefsContext);
|
||||
return addMarker;
|
||||
};
|
||||
|
||||
export const useAddPopup = (): UseLeafletRefsManagerResult["addPopup"] => {
|
||||
const { addPopup } = useContext(LeafletRefsContext);
|
||||
return addPopup;
|
||||
};
|
||||
export const useAddMarker = (): UseLeafletRefsManagerResult['addMarker'] => {
|
||||
const { addMarker } = useContext(LeafletRefsContext)
|
||||
return addMarker
|
||||
}
|
||||
|
||||
export const useAddPopup = (): UseLeafletRefsManagerResult['addPopup'] => {
|
||||
const { addPopup } = useContext(LeafletRefsContext)
|
||||
return addPopup
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { useCallback, useReducer, createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
import { Item, ItemsApi, LayerProps, Permission, PermissionAction } from "../../../types";
|
||||
import { useAuth } from "../../Auth";
|
||||
import { useCallback, useReducer, createContext, useContext } from 'react'
|
||||
import * as React from 'react'
|
||||
import { Item, ItemsApi, LayerProps, Permission, PermissionAction } from '../../../types'
|
||||
import { useAuth } from '../../Auth'
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD"; permission: Permission }
|
||||
| { type: "REMOVE"; id: string };
|
||||
| { type: 'ADD'; permission: Permission }
|
||||
| { type: 'REMOVE'; id: string };
|
||||
|
||||
type UsePermissionManagerResult = ReturnType<typeof usePermissionsManager>;
|
||||
|
||||
@ -15,9 +15,9 @@ const PermissionContext = createContext<UsePermissionManagerResult>({
|
||||
setPermissionData: () => { },
|
||||
setAdminRole: () => { },
|
||||
hasUserPermission: () => true
|
||||
});
|
||||
})
|
||||
|
||||
function usePermissionsManager(initialPermissions: Permission[]): {
|
||||
function usePermissionsManager (initialPermissions: Permission[]): {
|
||||
permissions: Permission[];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setPermissionApi: (api: ItemsApi<any>) => void;
|
||||
@ -30,104 +30,99 @@ function usePermissionsManager(initialPermissions: Permission[]): {
|
||||
} {
|
||||
const [permissions, dispatch] = useReducer((state: Permission[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD":
|
||||
case 'ADD':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const exist = state.find((permission) =>
|
||||
permission.id === action.permission.id ? true : false
|
||||
);
|
||||
if (!exist) return [
|
||||
...state,
|
||||
action.permission,
|
||||
];
|
||||
else return state;
|
||||
permission.id === action.permission.id
|
||||
)
|
||||
if (!exist) {
|
||||
return [
|
||||
...state,
|
||||
action.permission
|
||||
]
|
||||
} else return state
|
||||
|
||||
case "REMOVE":
|
||||
return state.filter(({ id }) => id !== action.id);
|
||||
case 'REMOVE':
|
||||
return state.filter(({ id }) => id !== action.id)
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialPermissions);
|
||||
}, initialPermissions)
|
||||
|
||||
const [adminRole, setAdminRole] = React.useState<string | null>(null);
|
||||
const { user } = useAuth();
|
||||
const [adminRole, setAdminRole] = React.useState<string | null>(null)
|
||||
const { user } = useAuth()
|
||||
|
||||
|
||||
const setPermissionApi = useCallback(async (api: ItemsApi<Permission>) => {
|
||||
const result = await api.getItems();
|
||||
const setPermissionApi = useCallback(async (api: ItemsApi<Permission>) => {
|
||||
const result = await api.getItems()
|
||||
if (result) {
|
||||
result.map(permission => {
|
||||
dispatch({ type: "ADD", permission })
|
||||
dispatch({ type: 'ADD', permission })
|
||||
return null
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setPermissionData = useCallback((data: Permission[]) => {
|
||||
data.map(permission => {
|
||||
dispatch({ type: "ADD", permission })
|
||||
dispatch({ type: 'ADD', permission })
|
||||
return null
|
||||
})
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const hasUserPermission = useCallback(
|
||||
(
|
||||
collectionName: string,
|
||||
action: PermissionAction,
|
||||
item?: Item,
|
||||
collectionName: string,
|
||||
action: PermissionAction,
|
||||
item?: Item,
|
||||
layer?: LayerProps
|
||||
) => {
|
||||
|
||||
const evaluateCondition = (condition: any) => {
|
||||
if (condition.user_created?._eq === "$CURRENT_USER") {
|
||||
return item?.user_created?.id === user?.id;
|
||||
if (condition.user_created?._eq === '$CURRENT_USER') {
|
||||
return item?.user_created?.id === user?.id
|
||||
}
|
||||
if (condition.public_edit?._eq === true) {
|
||||
return item?.public_edit === true;
|
||||
return item?.public_edit === true
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const evaluatePermissions = (permissionConditions: any) => {
|
||||
if (!permissionConditions || !permissionConditions._and) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
return permissionConditions._and.every((andCondition: any) =>
|
||||
andCondition._or
|
||||
|
||||
return permissionConditions._and.every((andCondition: any) =>
|
||||
andCondition._or
|
||||
? andCondition._or.some((orCondition: any) => evaluateCondition(orCondition))
|
||||
: evaluateCondition(andCondition)
|
||||
);
|
||||
};
|
||||
if (collectionName === "items" && action === "create" && layer?.public_edit_items) return true;
|
||||
)
|
||||
}
|
||||
if (collectionName === 'items' && action === 'create' && layer?.public_edit_items) return true
|
||||
// Bedingung für leere Berechtigungen nur, wenn NICHT item und create
|
||||
if (permissions.length === 0) return true;
|
||||
else if (user && user.role.id === adminRole) return true;
|
||||
if (permissions.length === 0) return true
|
||||
else if (user && user.role.id === adminRole) return true
|
||||
else {
|
||||
return permissions.some(p =>
|
||||
p.action === action &&
|
||||
p.collection === collectionName &&
|
||||
|
||||
(
|
||||
(p.policy?.name === user?.role.name &&
|
||||
(
|
||||
!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) &&
|
||||
(!item || evaluatePermissions(p.permissions))
|
||||
))
|
||||
)
|
||||
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
[permissions, user, adminRole]
|
||||
);
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
return { permissions, setPermissionApi, setPermissionData, setAdminRole, hasUserPermission };
|
||||
return { permissions, setPermissionApi, setPermissionData, setAdminRole, hasUserPermission }
|
||||
}
|
||||
|
||||
export const PermissionsProvider: React.FunctionComponent<{
|
||||
@ -136,30 +131,29 @@ export const PermissionsProvider: React.FunctionComponent<{
|
||||
<PermissionContext.Provider value={usePermissionsManager(initialPermissions)}>
|
||||
{children}
|
||||
</PermissionContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const usePermissions = (): Permission[] => {
|
||||
const { permissions } = useContext(PermissionContext);
|
||||
return permissions;
|
||||
};
|
||||
|
||||
|
||||
export const useSetPermissionApi = (): UsePermissionManagerResult["setPermissionApi"] => {
|
||||
const { setPermissionApi } = useContext(PermissionContext);
|
||||
return setPermissionApi;
|
||||
const { permissions } = useContext(PermissionContext)
|
||||
return permissions
|
||||
}
|
||||
|
||||
export const useSetPermissionData = (): UsePermissionManagerResult["setPermissionData"] => {
|
||||
const { setPermissionData } = useContext(PermissionContext);
|
||||
return setPermissionData;
|
||||
export const useSetPermissionApi = (): UsePermissionManagerResult['setPermissionApi'] => {
|
||||
const { setPermissionApi } = useContext(PermissionContext)
|
||||
return setPermissionApi
|
||||
}
|
||||
|
||||
export const useHasUserPermission = (): UsePermissionManagerResult["hasUserPermission"] => {
|
||||
const { hasUserPermission } = useContext(PermissionContext);
|
||||
return hasUserPermission;
|
||||
export const useSetPermissionData = (): UsePermissionManagerResult['setPermissionData'] => {
|
||||
const { setPermissionData } = useContext(PermissionContext)
|
||||
return setPermissionData
|
||||
}
|
||||
|
||||
export const useSetAdminRole = (): UsePermissionManagerResult["setAdminRole"] => {
|
||||
const { setAdminRole } = useContext(PermissionContext);
|
||||
return setAdminRole;
|
||||
}
|
||||
export const useHasUserPermission = (): UsePermissionManagerResult['hasUserPermission'] => {
|
||||
const { hasUserPermission } = useContext(PermissionContext)
|
||||
return hasUserPermission
|
||||
}
|
||||
|
||||
export const useSetAdminRole = (): UsePermissionManagerResult['setAdminRole'] => {
|
||||
const { setAdminRole } = useContext(PermissionContext)
|
||||
return setAdminRole
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import * as React from 'react'
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import { Geometry, Item, LayerProps } from '../../../types';
|
||||
import { useUpdateItem } from "./useItems";
|
||||
import { toast } from "react-toastify";
|
||||
import { useHasUserPermission } from "./usePermissions";
|
||||
import { LatLng } from "leaflet";
|
||||
import { ItemFormPopupProps } from "../Subcomponents/ItemFormPopup";
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { Geometry, Item, LayerProps } from '../../../types'
|
||||
import { useUpdateItem } from './useItems'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useHasUserPermission } from './usePermissions'
|
||||
import { LatLng } from 'leaflet'
|
||||
import { ItemFormPopupProps } from '../Subcomponents/ItemFormPopup'
|
||||
|
||||
type PolygonClickedProps = {
|
||||
position: LatLng
|
||||
@ -15,111 +15,106 @@ type PolygonClickedProps = {
|
||||
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>;
|
||||
|
||||
const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
|
||||
selectPosition: null,
|
||||
setSelectPosition: () => { },
|
||||
setMarkerClicked: () => { },
|
||||
setMapClicked: () => {},
|
||||
});
|
||||
selectPosition: null,
|
||||
setSelectPosition: () => { },
|
||||
setMarkerClicked: () => { },
|
||||
setMapClicked: () => {}
|
||||
})
|
||||
|
||||
function useSelectPositionManager(): {
|
||||
function useSelectPositionManager (): {
|
||||
selectPosition: Item | LayerProps | null;
|
||||
setSelectPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>;
|
||||
setMarkerClicked: React.Dispatch<React.SetStateAction<Item>>;
|
||||
setMapClicked: React.Dispatch<React.SetStateAction<PolygonClickedProps | undefined>>;
|
||||
} {
|
||||
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null);
|
||||
const [markerClicked, setMarkerClicked] = useState<Item | null>();
|
||||
const [mapClicked, setMapClicked] = useState<PolygonClickedProps>();
|
||||
const updateItem = useUpdateItem();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
} {
|
||||
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null)
|
||||
const [markerClicked, setMarkerClicked] = useState<Item | null>()
|
||||
const [mapClicked, setMapClicked] = useState<PolygonClickedProps>()
|
||||
const updateItem = useUpdateItem()
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (selectPosition && markerClicked && 'text' in selectPosition && markerClicked.id !==selectPosition.id) {
|
||||
itemUpdateParent({ ...selectPosition, parent: markerClicked.id })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [markerClicked])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectPosition != null) {
|
||||
if ('menuIcon' in selectPosition) {
|
||||
mapClicked && mapClicked.setItemFormPopup({ layer: selectPosition as LayerProps, position: mapClicked?.position })
|
||||
setSelectPosition(null)
|
||||
}
|
||||
if ('text' in selectPosition) {
|
||||
const position = mapClicked?.position.lng && new Geometry(mapClicked?.position.lng, mapClicked?.position.lat);
|
||||
position && itemUpdatePosition({ ...selectPosition as Item, position: position })
|
||||
setSelectPosition(null);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (selectPosition && markerClicked && 'text' in selectPosition && markerClicked.id !== selectPosition.id) {
|
||||
itemUpdateParent({ ...selectPosition, parent: markerClicked.id })
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mapClicked])
|
||||
|
||||
|
||||
const itemUpdateParent = async (updatedItem: Item) => {
|
||||
if (markerClicked?.layer?.api?.collectionName && hasUserPermission(markerClicked?.layer?.api?.collectionName, "update", markerClicked)) {
|
||||
let success = false;
|
||||
try {
|
||||
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, parent: updatedItem.parent, position: null })
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
await updateItem({ ...updatedItem, parent: updatedItem.parent, position: undefined })
|
||||
await linkItem(updatedItem.id);
|
||||
toast.success("Item position updated");
|
||||
setSelectPosition(null);
|
||||
setMarkerClicked(null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setSelectPosition(null);
|
||||
toast.error("you don't have permission to add items to " + markerClicked?.name);
|
||||
}
|
||||
}, [markerClicked])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectPosition != null) {
|
||||
if ('menuIcon' in selectPosition) {
|
||||
mapClicked && mapClicked.setItemFormPopup({ layer: selectPosition as LayerProps, position: mapClicked?.position })
|
||||
setSelectPosition(null)
|
||||
}
|
||||
if ('text' in selectPosition) {
|
||||
const position = mapClicked?.position.lng && new Geometry(mapClicked?.position.lng, mapClicked?.position.lat)
|
||||
position && itemUpdatePosition({ ...selectPosition as Item, position })
|
||||
setSelectPosition(null)
|
||||
}
|
||||
}
|
||||
|
||||
const itemUpdatePosition = async (updatedItem: Item) => {
|
||||
let success = false;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mapClicked])
|
||||
|
||||
const itemUpdateParent = async (updatedItem: Item) => {
|
||||
if (markerClicked?.layer?.api?.collectionName && hasUserPermission(markerClicked?.layer?.api?.collectionName, 'update', markerClicked)) {
|
||||
let success = false
|
||||
try {
|
||||
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, parent: updatedItem.parent, position: null })
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
await updateItem({ ...updatedItem, parent: updatedItem.parent, position: undefined })
|
||||
await linkItem(updatedItem.id)
|
||||
toast.success('Item position updated')
|
||||
setSelectPosition(null)
|
||||
setMarkerClicked(null)
|
||||
}
|
||||
} else {
|
||||
setSelectPosition(null)
|
||||
toast.error("you don't have permission to add items to " + markerClicked?.name)
|
||||
}
|
||||
}
|
||||
|
||||
const itemUpdatePosition = async (updatedItem: Item) => {
|
||||
let success = false
|
||||
try {
|
||||
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, position: updatedItem.position })
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
updateItem(updatedItem)
|
||||
toast.success('Item position updated')
|
||||
}
|
||||
}
|
||||
|
||||
const linkItem = async (id: string) => {
|
||||
if (markerClicked) {
|
||||
const newRelations = markerClicked.relations || []
|
||||
|
||||
if (!newRelations.some(r => r.related_items_id === id)) {
|
||||
newRelations?.push({ items_id: markerClicked.id, related_items_id: id })
|
||||
const updatedItem = { id: markerClicked.id, relations: newRelations }
|
||||
|
||||
let success = false
|
||||
try {
|
||||
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, position: updatedItem.position })
|
||||
success = true;
|
||||
await markerClicked?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
updateItem(updatedItem)
|
||||
toast.success("Item position updated");
|
||||
updateItem({ ...markerClicked, relations: newRelations })
|
||||
toast.success('Item linked')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const linkItem = async (id: string) => {
|
||||
if (markerClicked) {
|
||||
const new_relations = markerClicked.relations || [];
|
||||
|
||||
if (!new_relations.some(r => r.related_items_id == id)) {
|
||||
new_relations?.push({ items_id: markerClicked.id, related_items_id: id })
|
||||
const updatedItem = { id: markerClicked.id, relations: new_relations }
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await markerClicked?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
updateItem({ ...markerClicked, relations: new_relations })
|
||||
toast.success("Item linked");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { selectPosition, setSelectPosition, setMarkerClicked, setMapClicked };
|
||||
}
|
||||
return { selectPosition, setSelectPosition, setMarkerClicked, setMapClicked }
|
||||
}
|
||||
|
||||
export const SelectPositionProvider: React.FunctionComponent<{
|
||||
@ -128,24 +123,24 @@ export const SelectPositionProvider: React.FunctionComponent<{
|
||||
<SelectPositionContext.Provider value={useSelectPositionManager()}>
|
||||
{children}
|
||||
</SelectPositionContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useSelectPosition = (): Item | LayerProps | null => {
|
||||
const { selectPosition } = useContext(SelectPositionContext);
|
||||
return selectPosition;
|
||||
};
|
||||
|
||||
export const useSetSelectPosition = (): UseSelectPositionManagerResult["setSelectPosition"] => {
|
||||
const { setSelectPosition } = useContext(SelectPositionContext);
|
||||
return setSelectPosition;
|
||||
const { selectPosition } = useContext(SelectPositionContext)
|
||||
return selectPosition
|
||||
}
|
||||
|
||||
export const useSetMarkerClicked = (): UseSelectPositionManagerResult["setMarkerClicked"] => {
|
||||
const { setMarkerClicked } = useContext(SelectPositionContext);
|
||||
return setMarkerClicked;
|
||||
export const useSetSelectPosition = (): UseSelectPositionManagerResult['setSelectPosition'] => {
|
||||
const { setSelectPosition } = useContext(SelectPositionContext)
|
||||
return setSelectPosition
|
||||
}
|
||||
|
||||
export const useSetMapClicked = (): UseSelectPositionManagerResult["setMapClicked"] => {
|
||||
const { setMapClicked } = useContext(SelectPositionContext);
|
||||
return setMapClicked;
|
||||
}
|
||||
export const useSetMarkerClicked = (): UseSelectPositionManagerResult['setMarkerClicked'] => {
|
||||
const { setMarkerClicked } = useContext(SelectPositionContext)
|
||||
return setMarkerClicked
|
||||
}
|
||||
|
||||
export const useSetMapClicked = (): UseSelectPositionManagerResult['setMapClicked'] => {
|
||||
const { setMapClicked } = useContext(SelectPositionContext)
|
||||
return setMapClicked
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from "react";
|
||||
import * as React from "react";
|
||||
import { Item, ItemsApi, Tag } from "../../../types";
|
||||
import { hashTagRegex } from "../../../Utils/HashTagRegex";
|
||||
import { getValue } from "../../../Utils/GetValue";
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
import { Item, ItemsApi, Tag } from '../../../types'
|
||||
import { hashTagRegex } from '../../../Utils/HashTagRegex'
|
||||
import { getValue } from '../../../Utils/GetValue'
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD"; tag: Tag }
|
||||
| { type: "REMOVE"; id: string };
|
||||
| { type: 'ADD'; tag: Tag }
|
||||
| { type: 'REMOVE'; id: string };
|
||||
|
||||
type UseTagManagerResult = ReturnType<typeof useTagsManager>;
|
||||
|
||||
@ -18,9 +18,9 @@ const TagContext = createContext<UseTagManagerResult>({
|
||||
setTagData: () => { },
|
||||
getItemTags: () => [],
|
||||
allTagsLoaded: false
|
||||
});
|
||||
})
|
||||
|
||||
function useTagsManager(initialTags: Tag[]): {
|
||||
function useTagsManager (initialTags: Tag[]): {
|
||||
tags: Tag[];
|
||||
addTag: (tag: Tag) => void;
|
||||
setTagApi: (api: ItemsApi<Tag>) => void;
|
||||
@ -28,92 +28,90 @@ function useTagsManager(initialTags: Tag[]): {
|
||||
getItemTags: (item: Item) => Tag[];
|
||||
allTagsLoaded: boolean
|
||||
} {
|
||||
|
||||
const [allTagsLoaded, setallTagsLoaded] = useState<boolean>(false);
|
||||
const [tagCount, setTagCount] = useState<number>(0);
|
||||
const [allTagsLoaded, setallTagsLoaded] = useState<boolean>(false)
|
||||
const [tagCount, setTagCount] = useState<number>(0)
|
||||
|
||||
const [tags, dispatch] = useReducer((state: Tag[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
case "ADD":
|
||||
case 'ADD':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const exist = state.find((tag) =>
|
||||
tag.name.toLocaleLowerCase() === action.tag.name.toLocaleLowerCase() ? true : false
|
||||
);
|
||||
tag.name.toLocaleLowerCase() === action.tag.name.toLocaleLowerCase()
|
||||
)
|
||||
if (!exist) {
|
||||
const newState = [
|
||||
...state,
|
||||
{ ...action.tag}
|
||||
];
|
||||
if(tagCount == newState.length) setallTagsLoaded(true);
|
||||
return newState;
|
||||
}
|
||||
else return state;
|
||||
{ ...action.tag }
|
||||
]
|
||||
if (tagCount === newState.length) setallTagsLoaded(true)
|
||||
return newState
|
||||
} else return state
|
||||
default:
|
||||
throw new Error();
|
||||
throw new Error()
|
||||
}
|
||||
}, initialTags);
|
||||
}, initialTags)
|
||||
|
||||
const [api, setApi] = React.useState<ItemsApi<Tag>>({} as ItemsApi<Tag>)
|
||||
|
||||
|
||||
const setTagApi = useCallback(async (api: ItemsApi<Tag>) => {
|
||||
setApi(api);
|
||||
const result = await api.getItems();
|
||||
setTagCount(result.length);
|
||||
if(tagCount == 0) setallTagsLoaded(true);
|
||||
setApi(api)
|
||||
const result = await api.getItems()
|
||||
setTagCount(result.length)
|
||||
if (tagCount === 0) setallTagsLoaded(true)
|
||||
if (result) {
|
||||
result.map(tag => {
|
||||
//tag.name = tag.name.toLocaleLowerCase();
|
||||
dispatch({ type: "ADD", tag });
|
||||
// tag.name = tag.name.toLocaleLowerCase();
|
||||
dispatch({ type: 'ADD', tag })
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const setTagData = useCallback((data: Tag[]) => {
|
||||
data.map(tag => {
|
||||
//tag.name = tag.name.toLocaleLowerCase();
|
||||
dispatch({ type: "ADD", tag })
|
||||
// tag.name = tag.name.toLocaleLowerCase();
|
||||
dispatch({ type: 'ADD', tag })
|
||||
return null
|
||||
})
|
||||
}, []);
|
||||
|
||||
const addTag = (tag: Tag) => {
|
||||
dispatch({
|
||||
type: "ADD",
|
||||
tag,
|
||||
});
|
||||
if (!tags.some((t) => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) {
|
||||
api?.createItem && api.createItem(tag);
|
||||
}
|
||||
|
||||
};
|
||||
}, [])
|
||||
|
||||
const addTag = (tag: Tag) => {
|
||||
dispatch({
|
||||
type: 'ADD',
|
||||
tag
|
||||
})
|
||||
if (!tags.some((t) => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) {
|
||||
api?.createItem && api.createItem(tag)
|
||||
}
|
||||
}
|
||||
|
||||
const getItemTags = useCallback((item: Item) => {
|
||||
const text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined;
|
||||
const itemTagStrings = text?.match(hashTagRegex);
|
||||
const itemTags: Tag[] = [];
|
||||
const text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined
|
||||
const itemTagStrings = text?.match(hashTagRegex)
|
||||
const itemTags: Tag[] = []
|
||||
itemTagStrings?.map(tag => {
|
||||
if (tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
itemTags.push(tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())!)
|
||||
}
|
||||
})
|
||||
return null
|
||||
})
|
||||
item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => {
|
||||
const offer = tags.find(t=> t.id === o.tags_id)
|
||||
const offer = tags.find(t => t.id === o.tags_id)
|
||||
offer && itemTags.push(offer)
|
||||
});
|
||||
return null
|
||||
})
|
||||
item.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
|
||||
const need = tags.find(t=>t.id === n.tags_id);
|
||||
need && itemTags.push(need);
|
||||
});
|
||||
const need = tags.find(t => t.id === n.tags_id)
|
||||
need && itemTags.push(need)
|
||||
return null
|
||||
})
|
||||
|
||||
return itemTags
|
||||
}, [tags]);
|
||||
}, [tags])
|
||||
|
||||
|
||||
return { tags, addTag, setTagApi, setTagData, getItemTags, allTagsLoaded };
|
||||
return { tags, addTag, setTagApi, setTagData, getItemTags, allTagsLoaded }
|
||||
}
|
||||
|
||||
export const TagsProvider: React.FunctionComponent<{
|
||||
@ -122,35 +120,34 @@ export const TagsProvider: React.FunctionComponent<{
|
||||
<TagContext.Provider value={useTagsManager(initialTags)}>
|
||||
{children}
|
||||
</TagContext.Provider>
|
||||
);
|
||||
)
|
||||
|
||||
export const useTags = (): Tag[] => {
|
||||
const { tags } = useContext(TagContext);
|
||||
return tags;
|
||||
};
|
||||
|
||||
export const useAddTag = (): UseTagManagerResult["addTag"] => {
|
||||
const { addTag } = useContext(TagContext);
|
||||
return addTag;
|
||||
};
|
||||
|
||||
export const useSetTagApi = (): UseTagManagerResult["setTagApi"] => {
|
||||
const { setTagApi } = useContext(TagContext);
|
||||
return setTagApi;
|
||||
const { tags } = useContext(TagContext)
|
||||
return tags
|
||||
}
|
||||
|
||||
export const useSetTagData = (): UseTagManagerResult["setTagData"] => {
|
||||
const { setTagData } = useContext(TagContext);
|
||||
return setTagData;
|
||||
export const useAddTag = (): UseTagManagerResult['addTag'] => {
|
||||
const { addTag } = useContext(TagContext)
|
||||
return addTag
|
||||
}
|
||||
|
||||
|
||||
export const useGetItemTags = (): UseTagManagerResult["getItemTags"] => {
|
||||
const { getItemTags } = useContext(TagContext);
|
||||
return getItemTags;
|
||||
export const useSetTagApi = (): UseTagManagerResult['setTagApi'] => {
|
||||
const { setTagApi } = useContext(TagContext)
|
||||
return setTagApi
|
||||
}
|
||||
|
||||
export const useAllTagsLoaded = (): UseTagManagerResult["allTagsLoaded"] => {
|
||||
const { allTagsLoaded } = useContext(TagContext);
|
||||
return allTagsLoaded;
|
||||
}
|
||||
export const useSetTagData = (): UseTagManagerResult['setTagData'] => {
|
||||
const { setTagData } = useContext(TagContext)
|
||||
return setTagData
|
||||
}
|
||||
|
||||
export const useGetItemTags = (): UseTagManagerResult['getItemTags'] => {
|
||||
const { getItemTags } = useContext(TagContext)
|
||||
return getItemTags
|
||||
}
|
||||
|
||||
export const useAllTagsLoaded = (): UseTagManagerResult['allTagsLoaded'] => {
|
||||
const { allTagsLoaded } = useContext(TagContext)
|
||||
return allTagsLoaded
|
||||
}
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
export const useTimeout = (callback, delay) => {
|
||||
const callbackRef = useRef(callback);
|
||||
const timeoutRef = useRef<any>();
|
||||
const callbackRef = useRef(callback)
|
||||
const timeoutRef = useRef<any>()
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
callbackRef.current = callback
|
||||
}, [callback])
|
||||
|
||||
const set = useCallback(() => {
|
||||
timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
|
||||
}, [delay]);
|
||||
timeoutRef.current = setTimeout(() => callbackRef.current(), delay)
|
||||
}, [delay])
|
||||
|
||||
const clear = useCallback(() => {
|
||||
timeoutRef.current && clearTimeout(timeoutRef.current);
|
||||
}, []);
|
||||
timeoutRef.current && clearTimeout(timeoutRef.current)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
set();
|
||||
return clear;
|
||||
}, [delay, set, clear]);
|
||||
set()
|
||||
return clear
|
||||
}, [delay, set, clear])
|
||||
|
||||
const reset = useCallback(() => {
|
||||
clear();
|
||||
set();
|
||||
}, [clear, set]);
|
||||
clear()
|
||||
set()
|
||||
}, [clear, set])
|
||||
|
||||
return { reset, clear };
|
||||
}
|
||||
return { reset, clear }
|
||||
}
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
function getWindowDimensions() {
|
||||
const { innerWidth: width, innerHeight: height } = window;
|
||||
function getWindowDimensions () {
|
||||
const { innerWidth: width, innerHeight: height } = window
|
||||
return {
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function useWindowDimensions() {
|
||||
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
|
||||
export default function useWindowDimensions () {
|
||||
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowDimensions(getWindowDimensions());
|
||||
function handleResize () {
|
||||
setWindowDimensions(getWindowDimensions())
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
|
||||
return windowDimensions;
|
||||
return windowDimensions
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
export { UtopiaMap } from './UtopiaMap';
|
||||
export { Layer } from './Layer';
|
||||
export { Tags } from "./Tags";
|
||||
export { Permissions } from "./Permissions";
|
||||
export {ItemForm} from './ItemForm';
|
||||
export {ItemView} from './ItemView';
|
||||
export {PopupTextAreaInput} from './Subcomponents/ItemPopupComponents/PopupTextAreaInput';
|
||||
export {PopupStartEndInput} from './Subcomponents/ItemPopupComponents/PopupStartEndInput';
|
||||
export {PopupTextInput} from './Subcomponents/ItemPopupComponents/PopupTextInput';
|
||||
export {PopupCheckboxInput} from './Subcomponents/ItemPopupComponents/PopupCheckboxInput'
|
||||
export {TextView} from './Subcomponents/ItemPopupComponents/TextView';
|
||||
export {StartEndView} from './Subcomponents/ItemPopupComponents/StartEndView'
|
||||
export {PopupButton} from './Subcomponents/ItemPopupComponents/PopupButton'
|
||||
export { UtopiaMap } from './UtopiaMap'
|
||||
export { Layer } from './Layer'
|
||||
export { Tags } from './Tags'
|
||||
export { Permissions } from './Permissions'
|
||||
export { ItemForm } from './ItemForm'
|
||||
export { ItemView } from './ItemView'
|
||||
export { PopupTextAreaInput } from './Subcomponents/ItemPopupComponents/PopupTextAreaInput'
|
||||
export { PopupStartEndInput } from './Subcomponents/ItemPopupComponents/PopupStartEndInput'
|
||||
export { PopupTextInput } from './Subcomponents/ItemPopupComponents/PopupTextInput'
|
||||
export { PopupCheckboxInput } from './Subcomponents/ItemPopupComponents/PopupCheckboxInput'
|
||||
export { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
||||
export { StartEndView } from './Subcomponents/ItemPopupComponents/StartEndView'
|
||||
export { PopupButton } from './Subcomponents/ItemPopupComponents/PopupButton'
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useMap } from "react-leaflet"
|
||||
import { useMap } from 'react-leaflet'
|
||||
|
||||
export const setItemLocation = () => {
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, react-hooks/rules-of-hooks
|
||||
const map = useMap();
|
||||
const map = useMap()
|
||||
|
||||
return (
|
||||
<div></div>
|
||||
|
||||
@ -1,158 +1,156 @@
|
||||
/* eslint-disable no-constant-condition */
|
||||
import { useItems, useUpdateItem, useAddItem } from '../Map/hooks/useItems'
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getValue } from '../../Utils/GetValue';
|
||||
import { useAuth } from '../Auth';
|
||||
import { useAddTag, useGetItemTags, useTags } from '../Map/hooks/useTags';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Item, Tag } from '../../types';
|
||||
import { MapOverlayPage } from '../Templates';
|
||||
import { useLayers } from '../Map/hooks/useLayers';
|
||||
import { useHasUserPermission } from '../Map/hooks/usePermissions';
|
||||
import { OnepagerForm } from './Templates/OnepagerForm';
|
||||
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions';
|
||||
import { SimpleForm } from './Templates/SimpleForm';
|
||||
import { TabsForm } from './Templates/TabsForm';
|
||||
import { FormHeader } from './Subcomponents/FormHeader';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getValue } from '../../Utils/GetValue'
|
||||
import { useAuth } from '../Auth'
|
||||
import { useAddTag, useGetItemTags, useTags } from '../Map/hooks/useTags'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Item, Tag } from '../../types'
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
import { useLayers } from '../Map/hooks/useLayers'
|
||||
import { useHasUserPermission } from '../Map/hooks/usePermissions'
|
||||
import { OnepagerForm } from './Templates/OnepagerForm'
|
||||
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
|
||||
import { SimpleForm } from './Templates/SimpleForm'
|
||||
import { TabsForm } from './Templates/TabsForm'
|
||||
import { FormHeader } from './Subcomponents/FormHeader'
|
||||
|
||||
export function ProfileForm({ userType }: { userType: string }) {
|
||||
export function ProfileForm ({ userType }: { userType: string }) {
|
||||
const [state, setState] = useState({
|
||||
color: '',
|
||||
id: '',
|
||||
groupType: 'wuerdekompass',
|
||||
status: 'active',
|
||||
name: '',
|
||||
subname: '',
|
||||
text: '',
|
||||
contact: '',
|
||||
telephone: '',
|
||||
nextAppointment: '',
|
||||
image: '',
|
||||
markerIcon: '',
|
||||
offers: [] as Tag[],
|
||||
needs: [] as Tag[],
|
||||
relations: [] as Item[],
|
||||
start: '',
|
||||
end: ''
|
||||
})
|
||||
|
||||
const [state, setState] = useState({
|
||||
color: "",
|
||||
id: "",
|
||||
groupType: "wuerdekompass",
|
||||
status: "active",
|
||||
name: "",
|
||||
subname: "",
|
||||
text: "",
|
||||
contact: "",
|
||||
telephone: "",
|
||||
nextAppointment: "",
|
||||
image: "",
|
||||
markerIcon: "",
|
||||
offers: [] as Tag[],
|
||||
needs: [] as Tag[],
|
||||
relations: [] as Item[],
|
||||
start: "",
|
||||
end: ""
|
||||
});
|
||||
const [updatePermission, setUpdatePermission] = useState<boolean>(false)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [item, setItem] = useState<Item>({} as Item)
|
||||
const { user } = useAuth()
|
||||
const updateItem = useUpdateItem()
|
||||
const addItem = useAddItem()
|
||||
const layers = useLayers()
|
||||
const location = useLocation()
|
||||
const tags = useTags()
|
||||
const addTag = useAddTag()
|
||||
const navigate = useNavigate()
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const getItemTags = useGetItemTags()
|
||||
const items = useItems()
|
||||
|
||||
const [updatePermission, setUpdatePermission] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [item, setItem] = useState<Item>({} as Item)
|
||||
const { user } = useAuth();
|
||||
const updateItem = useUpdateItem();
|
||||
const addItem = useAddItem();
|
||||
const layers = useLayers();
|
||||
const location = useLocation();
|
||||
const tags = useTags();
|
||||
const addTag = useAddTag();
|
||||
const navigate = useNavigate();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const getItemTags = useGetItemTags();
|
||||
const items = useItems();
|
||||
const [urlParams, setUrlParams] = useState(new URLSearchParams(location.search))
|
||||
|
||||
const [urlParams, setUrlParams] = useState(new URLSearchParams(location.search));
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
item && hasUserPermission("items", "update", item) && setUpdatePermission(true);
|
||||
useEffect(() => {
|
||||
item && hasUserPermission('items', 'update', item) && setUpdatePermission(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item])
|
||||
}, [item])
|
||||
|
||||
useEffect(() => {
|
||||
const itemId = location.pathname.split("/")[2];
|
||||
useEffect(() => {
|
||||
const itemId = location.pathname.split('/')[2]
|
||||
|
||||
const item = items.find(i => i.id === itemId);
|
||||
item && setItem(item);
|
||||
const item = items.find(i => i.id === itemId)
|
||||
item && setItem(item)
|
||||
|
||||
const layer = layers.find(l => l.itemType.name == userType)
|
||||
const layer = layers.find(l => l.itemType.name === userType)
|
||||
|
||||
!item && setItem({ id: crypto.randomUUID(), name: user ? user.first_name : "", text: "", layer: layer, new: true })
|
||||
!item && setItem({ id: crypto.randomUUID(), name: user ? user.first_name : '', text: '', layer, new: true })
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [items])
|
||||
}, [items])
|
||||
|
||||
useEffect(() => {
|
||||
const newColor = item.layer?.itemColorField && getValue(item, item.layer?.itemColorField)
|
||||
? getValue(item, item.layer?.itemColorField)
|
||||
: (getItemTags(item) && getItemTags(item)[0]?.color)
|
||||
? getItemTags(item)[0].color
|
||||
: item?.layer?.markerDefaultColor;
|
||||
useEffect(() => {
|
||||
const newColor = item.layer?.itemColorField && getValue(item, item.layer?.itemColorField)
|
||||
? getValue(item, item.layer?.itemColorField)
|
||||
: (getItemTags(item) && getItemTags(item)[0]?.color)
|
||||
? getItemTags(item)[0].color
|
||||
: item?.layer?.markerDefaultColor
|
||||
|
||||
const offers = (item?.offers ?? []).reduce((acc: Tag[], o) => {
|
||||
const offer = tags.find(t => t.id === o.tags_id);
|
||||
if (offer) acc.push(offer);
|
||||
return acc;
|
||||
}, []);
|
||||
const offers = (item?.offers ?? []).reduce((acc: Tag[], o) => {
|
||||
const offer = tags.find(t => t.id === o.tags_id)
|
||||
if (offer) acc.push(offer)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const needs = (item?.needs ?? []).reduce((acc: Tag[], o) => {
|
||||
const need = tags.find(t => t.id === o.tags_id);
|
||||
if (need) acc.push(need);
|
||||
return acc;
|
||||
}, []);
|
||||
const needs = (item?.needs ?? []).reduce((acc: Tag[], o) => {
|
||||
const need = tags.find(t => t.id === o.tags_id)
|
||||
if (need) acc.push(need)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const relations = (item?.relations ?? []).reduce((acc: Item[], r) => {
|
||||
const relatedItem = items.find(i => i.id === r.related_items_id);
|
||||
if (relatedItem) acc.push(relatedItem);
|
||||
return acc;
|
||||
}, []);
|
||||
const relations = (item?.relations ?? []).reduce((acc: Item[], r) => {
|
||||
const relatedItem = items.find(i => i.id === r.related_items_id)
|
||||
if (relatedItem) acc.push(relatedItem)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
setState({
|
||||
color: newColor,
|
||||
id: item?.id ?? "",
|
||||
groupType: item?.group_type ?? "wuerdekompass",
|
||||
status: item?.status ?? "active",
|
||||
name: item?.name ?? "",
|
||||
subname: item?.subname ?? "",
|
||||
text: item?.text ?? "",
|
||||
contact: item?.contact ?? "",
|
||||
telephone: item?.telephone ?? "",
|
||||
nextAppointment: item?.next_appointment ?? "",
|
||||
image: item?.image ?? "",
|
||||
markerIcon: item?.marker_icon ?? "",
|
||||
offers: offers,
|
||||
needs: needs,
|
||||
relations: relations,
|
||||
start: item?.start ?? "",
|
||||
end: item?.end ?? ""
|
||||
});
|
||||
setState({
|
||||
color: newColor,
|
||||
id: item?.id ?? '',
|
||||
groupType: item?.group_type ?? 'wuerdekompass',
|
||||
status: item?.status ?? 'active',
|
||||
name: item?.name ?? '',
|
||||
subname: item?.subname ?? '',
|
||||
text: item?.text ?? '',
|
||||
contact: item?.contact ?? '',
|
||||
telephone: item?.telephone ?? '',
|
||||
nextAppointment: item?.next_appointment ?? '',
|
||||
image: item?.image ?? '',
|
||||
markerIcon: item?.marker_icon ?? '',
|
||||
offers,
|
||||
needs,
|
||||
relations,
|
||||
start: item?.start ?? '',
|
||||
end: item?.end ?? ''
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, tags, items]);
|
||||
}, [item, tags, items])
|
||||
|
||||
const [template, setTemplate] = useState<string>("")
|
||||
const [template, setTemplate] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
setTemplate(item.layer?.itemType.template || userType);
|
||||
}, [userType, item])
|
||||
useEffect(() => {
|
||||
setTemplate(item.layer?.itemType.template || userType)
|
||||
}, [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'>
|
||||
|
||||
|
||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||
|
||||
<FormHeader item={item} state={state} setState={setState} />
|
||||
|
||||
{template == "onepager" && (
|
||||
{template === 'onepager' && (
|
||||
<OnepagerForm item={item} state={state} setState={setState}></OnepagerForm>
|
||||
)}
|
||||
|
||||
{template == "simple" &&
|
||||
{template === 'simple' &&
|
||||
<SimpleForm state={state} setState={setState}></SimpleForm>
|
||||
}
|
||||
|
||||
{template == "tabs" &&
|
||||
{template === 'tabs' &&
|
||||
<TabsForm loading={loading} item={item} state={state} setState={setState} updatePermission={updatePermission} linkItem={(id) => linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)} setUrlParams={setUrlParams}></TabsForm>
|
||||
}
|
||||
|
||||
<div className="tw-mt-4 tw-mb-4">
|
||||
<button className={loading ? " tw-loading tw-btn tw-float-right" : "tw-btn tw-float-right"} onClick={() => onUpdateItem(state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, urlParams)} style={true ? { backgroundColor: `${item.layer?.itemColorField && getValue(item, item.layer?.itemColorField) ? getValue(item, item.layer?.itemColorField) : (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`, color: "#fff" } : { color: "#fff" }}>Update</button>
|
||||
<button className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'} onClick={() => onUpdateItem(state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, urlParams)} style={true ? { backgroundColor: `${item.layer?.itemColorField && getValue(item, item.layer?.itemColorField) ? getValue(item, item.layer?.itemColorField) : (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`, color: '#fff' } : { color: '#fff' }}>Update</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</MapOverlayPage>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,164 +1,161 @@
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
import { useItems, useRemoveItem, useUpdateItem } from '../Map/hooks/useItems'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Item, ItemsApi, Tag } from '../../types';
|
||||
import { useMap } from 'react-leaflet';
|
||||
import { LatLng } from 'leaflet';
|
||||
import { useHasUserPermission } from '../Map/hooks/usePermissions';
|
||||
import { HeaderView } from '../Map/Subcomponents/ItemPopupComponents/HeaderView';
|
||||
import { useSelectPosition, useSetSelectPosition } from '../Map/hooks/useSelectPosition';
|
||||
import { useClusterRef } from '../Map/hooks/useClusterRef';
|
||||
import { useLeafletRefs } from '../Map/hooks/useLeafletRefs';
|
||||
import { getValue } from '../../Utils/GetValue';
|
||||
import { TabsView } from './Templates/TabsView';
|
||||
import { OnepagerView } from './Templates/OnepagerView';
|
||||
import { SimpleView } from './Templates/SimpleView';
|
||||
import { handleDelete, linkItem, unlinkItem } from './itemFunctions';
|
||||
import { useTags } from '../Map/hooks/useTags';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Item, ItemsApi, Tag } from '../../types'
|
||||
import { useMap } from 'react-leaflet'
|
||||
import { LatLng } from 'leaflet'
|
||||
import { useHasUserPermission } from '../Map/hooks/usePermissions'
|
||||
import { HeaderView } from '../Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
import { useSelectPosition, useSetSelectPosition } from '../Map/hooks/useSelectPosition'
|
||||
import { useClusterRef } from '../Map/hooks/useClusterRef'
|
||||
import { useLeafletRefs } from '../Map/hooks/useLeafletRefs'
|
||||
import { getValue } from '../../Utils/GetValue'
|
||||
import { TabsView } from './Templates/TabsView'
|
||||
import { OnepagerView } from './Templates/OnepagerView'
|
||||
import { SimpleView } from './Templates/SimpleView'
|
||||
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
|
||||
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 [updatePermission, setUpdatePermission] = useState<boolean>(false)
|
||||
const [relations, setRelations] = useState<Array<Item>>([])
|
||||
const [offers, setOffers] = useState<Array<Tag>>([])
|
||||
const [needs, setNeeds] = useState<Array<Tag>>([])
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [template, setTemplate] = useState<string>('')
|
||||
|
||||
const [item, setItem] = useState<Item>()
|
||||
const [updatePermission, setUpdatePermission] = useState<boolean>(false);
|
||||
const [relations, setRelations] = useState<Array<Item>>([]);
|
||||
const [offers, setOffers] = useState<Array<Tag>>([]);
|
||||
const [needs, setNeeds] = useState<Array<Tag>>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [template, setTemplate] = useState<string>("");
|
||||
const location = useLocation()
|
||||
const items = useItems()
|
||||
const updateItem = useUpdateItem()
|
||||
const map = useMap()
|
||||
const selectPosition = useSelectPosition()
|
||||
const removeItem = useRemoveItem()
|
||||
const tags = useTags()
|
||||
const navigate = useNavigate()
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const setSelectPosition = useSetSelectPosition()
|
||||
const clusterRef = useClusterRef()
|
||||
const leafletRefs = useLeafletRefs()
|
||||
|
||||
const location = useLocation();
|
||||
const items = useItems();
|
||||
const updateItem = useUpdateItem();
|
||||
const map = useMap();
|
||||
const selectPosition = useSelectPosition();
|
||||
const removeItem = useRemoveItem();
|
||||
const tags = useTags();
|
||||
const navigate = useNavigate();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const setSelectPosition = useSetSelectPosition();
|
||||
const clusterRef = useClusterRef();
|
||||
const leafletRefs = useLeafletRefs();
|
||||
const [attestations, setAttestations] = useState<Array<any>>([])
|
||||
|
||||
const [attestations, setAttestations] = useState<Array<any>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (attestationApi) {
|
||||
attestationApi.getItems()
|
||||
.then(value => {
|
||||
console.log(value);
|
||||
|
||||
setAttestations(value);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching items:", error);
|
||||
});
|
||||
}
|
||||
}, [attestationApi])
|
||||
|
||||
useEffect(() => {
|
||||
if (attestationApi) {
|
||||
attestationApi.getItems()
|
||||
.then(value => {
|
||||
console.log(value)
|
||||
|
||||
useEffect(() => {
|
||||
const itemId = location.pathname.split("/")[2];
|
||||
const item = items.find(i => i.id === itemId);
|
||||
item && setItem(item);
|
||||
}, [items, location])
|
||||
|
||||
useEffect(() => {
|
||||
setOffers([]);
|
||||
setNeeds([]);
|
||||
setRelations([]);
|
||||
|
||||
item?.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => {
|
||||
const tag = tags.find(t => t.id === o.tags_id);
|
||||
tag && setOffers(current => [...current, tag])
|
||||
setAttestations(value)
|
||||
})
|
||||
item?.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
|
||||
const tag = tags.find(t => t.id === n.tags_id);
|
||||
tag && setNeeds(current => [...current, tag])
|
||||
})
|
||||
item?.relations?.map(r => {
|
||||
const item = items.find(i => i.id == r.related_items_id)
|
||||
item && setRelations(current => [...current, item])
|
||||
.catch(error => {
|
||||
console.error('Error fetching items:', error)
|
||||
})
|
||||
}
|
||||
}, [attestationApi])
|
||||
|
||||
useEffect(() => {
|
||||
const itemId = location.pathname.split('/')[2]
|
||||
const item = items.find(i => i.id === itemId)
|
||||
item && setItem(item)
|
||||
}, [items, location])
|
||||
|
||||
useEffect(() => {
|
||||
setOffers([])
|
||||
setNeeds([])
|
||||
setRelations([])
|
||||
|
||||
item?.layer?.itemOffersField && getValue(item, item.layer.itemOffersField)?.map(o => {
|
||||
const tag = tags.find(t => t.id === o.tags_id)
|
||||
tag && setOffers(current => [...current, tag])
|
||||
return null
|
||||
})
|
||||
item?.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField)?.map(n => {
|
||||
const tag = tags.find(t => t.id === n.tags_id)
|
||||
tag && setNeeds(current => [...current, tag])
|
||||
return null
|
||||
})
|
||||
item?.relations?.map(r => {
|
||||
const item = items.find(i => i.id === r.related_items_id)
|
||||
item && setRelations(current => [...current, item])
|
||||
return null
|
||||
})
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, items])
|
||||
}, [item, items])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const setMap = async (marker, x) => {
|
||||
await map.setView(new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4), undefined);
|
||||
setTimeout(() => {
|
||||
marker.openPopup();
|
||||
}, 500);
|
||||
useEffect(() => {
|
||||
const setMap = async (marker, x) => {
|
||||
await map.setView(new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4), undefined)
|
||||
setTimeout(() => {
|
||||
marker.openPopup()
|
||||
}, 500)
|
||||
}
|
||||
if (item) {
|
||||
if (item.position) {
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item === item)?.[1].marker
|
||||
marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
const bounds = map.getBounds()
|
||||
const x = bounds.getEast() - bounds.getWest()
|
||||
setMap(marker, x)
|
||||
}
|
||||
if (item) {
|
||||
if (item.position) {
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item == item)?.[1].marker;
|
||||
marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
const bounds = map.getBounds();
|
||||
const x = bounds.getEast() - bounds.getWest();
|
||||
setMap(marker, x);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
const parent = getFirstAncestor(item);
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item == parent)?.[1].marker;
|
||||
marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
const bounds = map.getBounds();
|
||||
const x = bounds.getEast() - bounds.getWest();
|
||||
setMap(marker, x);
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const parent = getFirstAncestor(item)
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item === parent)?.[1].marker
|
||||
marker && clusterRef.hasLayer(marker) && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
const bounds = map.getBounds()
|
||||
const x = bounds.getEast() - bounds.getWest()
|
||||
setMap(marker, x)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item])
|
||||
}, [item])
|
||||
|
||||
const getFirstAncestor = (item: Item): Item | undefined => {
|
||||
const parent = items.find(i => i.id === item.parent);
|
||||
if (parent?.parent) {
|
||||
return getFirstAncestor(parent);
|
||||
} else {
|
||||
return parent;
|
||||
}
|
||||
};
|
||||
const getFirstAncestor = (item: Item): Item | undefined => {
|
||||
const parent = items.find(i => i.id === item.parent)
|
||||
if (parent?.parent) {
|
||||
return getFirstAncestor(parent)
|
||||
} else {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
item && hasUserPermission("items", "update", item) && setUpdatePermission(true);
|
||||
useEffect(() => {
|
||||
item && hasUserPermission('items', 'update', item) && setUpdatePermission(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item])
|
||||
}, [item])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
selectPosition && map.closePopup();
|
||||
useEffect(() => {
|
||||
selectPosition && map.closePopup()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectPosition])
|
||||
}, [selectPosition])
|
||||
|
||||
useEffect(() => {
|
||||
setTemplate(item?.layer?.itemType.template || userType);
|
||||
}, [userType, item])
|
||||
useEffect(() => {
|
||||
setTemplate(item?.layer?.itemType.template || userType)
|
||||
}, [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'}`}>
|
||||
<>
|
||||
<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 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}/>
|
||||
}
|
||||
|
||||
{template == "simple" &&
|
||||
{template === 'simple' &&
|
||||
<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)}/>
|
||||
}
|
||||
</>
|
||||
@ -166,5 +163,5 @@ export function ProfileView({ userType, attestationApi }: { userType: string , a
|
||||
</MapOverlayPage >
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { useState } from "react";
|
||||
import { useHasUserPermission } from "../../Map/hooks/usePermissions";
|
||||
import DialogModal from "../../Templates/DialogModal";
|
||||
import { useItems } from "../../Map/hooks/useItems";
|
||||
import { HeaderView } from "../../Map/Subcomponents/ItemPopupComponents/HeaderView";
|
||||
import { Item } from "../../../types";
|
||||
import { TextInput } from "../../Input";
|
||||
import { getValue } from "../../../Utils/GetValue";
|
||||
import { useGetItemTags } from "../../Map/hooks/useTags";
|
||||
import { useState } from 'react'
|
||||
import { useHasUserPermission } from '../../Map/hooks/usePermissions'
|
||||
import DialogModal from '../../Templates/DialogModal'
|
||||
import { useItems } from '../../Map/hooks/useItems'
|
||||
import { HeaderView } from '../../Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
import { Item } from '../../../types'
|
||||
import { TextInput } from '../../Input'
|
||||
import { getValue } from '../../../Utils/GetValue'
|
||||
import { useGetItemTags } from '../../Map/hooks/useTags'
|
||||
|
||||
export function ActionButton({ item, triggerAddButton, triggerItemSelected, existingRelations, itemType, colorField, collection = "items", customStyle }: {
|
||||
export function ActionButton ({ item, triggerAddButton, triggerItemSelected, existingRelations, itemType, colorField, collection = 'items', customStyle }: {
|
||||
triggerAddButton?: any,
|
||||
triggerItemSelected?: any,
|
||||
existingRelations: Item[],
|
||||
@ -18,39 +18,36 @@ export function ActionButton({ item, triggerAddButton, triggerItemSelected, exis
|
||||
customStyle?:string,
|
||||
item: Item
|
||||
}) {
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const getItemTags = useGetItemTags();
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||
const [search, setSearch] = useState<string>('')
|
||||
const getItemTags = useGetItemTags()
|
||||
|
||||
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 (
|
||||
<>{hasUserPermission(collection, "update", item) &&
|
||||
return (
|
||||
<>{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" }}>
|
||||
{triggerItemSelected && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { setModalOpen(true) }} style={{ backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`, color: '#fff' }}>
|
||||
<svg className="tw-h-5 tw-w-5" stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"></path></svg>
|
||||
|
||||
</button>}
|
||||
{triggerAddButton && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow tw-mt-2" onClick={() => { triggerAddButton() }} style={{ backgroundColor: `${colorField && getValue(item,colorField)? getValue(item,colorField) : (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`, color: "#fff" }}>
|
||||
{triggerAddButton && <button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow tw-mt-2" onClick={() => { triggerAddButton() }} style={{ backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`, color: '#fff' }}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="4" stroke="currentColor" className="tw-w-5 tw-h-5">
|
||||
<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>
|
||||
<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());
|
||||
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>)}
|
||||
@ -60,5 +57,5 @@ export function ActionButton({ item, triggerAddButton, triggerItemSelected, exis
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop';
|
||||
import { useAssetApi } from '../../AppShell/hooks/useAssets';
|
||||
import 'react-image-crop/dist/ReactCrop.css';
|
||||
import DialogModal from "../../Templates/DialogModal";
|
||||
import * as React from 'react'
|
||||
import { useState, useCallback, useRef } from 'react'
|
||||
import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop'
|
||||
import { useAssetApi } from '../../AppShell/hooks/useAssets'
|
||||
import 'react-image-crop/dist/ReactCrop.css'
|
||||
import DialogModal from '../../Templates/DialogModal'
|
||||
|
||||
interface AvatarWidgetProps {
|
||||
avatar: string;
|
||||
@ -11,167 +11,166 @@ interface AvatarWidgetProps {
|
||||
}
|
||||
|
||||
export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar }) => {
|
||||
const [crop, setCrop] = useState<Crop>();
|
||||
const [image, setImage] = useState<string>("");
|
||||
const [cropModalOpen, setCropModalOpen] = useState<boolean>(false);
|
||||
const [cropping, setCropping] = useState<boolean>(false);
|
||||
const [crop, setCrop] = useState<Crop>()
|
||||
const [image, setImage] = useState<string>('')
|
||||
const [cropModalOpen, setCropModalOpen] = useState<boolean>(false)
|
||||
const [cropping, setCropping] = useState<boolean>(false)
|
||||
|
||||
const assetsApi = useAssetApi();
|
||||
const assetsApi = useAssetApi()
|
||||
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const imgRef = useRef<HTMLImageElement>(null)
|
||||
|
||||
const onImageChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files && event.target.files[0];
|
||||
if (file) {
|
||||
const validFormats = ["image/jpeg", "image/png"];
|
||||
const maxSizeMB = 10;
|
||||
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
||||
const onImageChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files && event.target.files[0]
|
||||
if (file) {
|
||||
const validFormats = ['image/jpeg', 'image/png']
|
||||
const maxSizeMB = 10
|
||||
const maxSizeBytes = maxSizeMB * 1024 * 1024
|
||||
|
||||
if (!validFormats.includes(file.type)) {
|
||||
alert("Unsupported file format. Please upload a JPEG or PNG image.");
|
||||
return;
|
||||
}
|
||||
if (!validFormats.includes(file.type)) {
|
||||
alert('Unsupported file format. Please upload a JPEG or PNG image.')
|
||||
return
|
||||
}
|
||||
|
||||
if (file.size > maxSizeBytes) {
|
||||
alert(`File size exceeds ${maxSizeMB}MB. Please upload a smaller image.`);
|
||||
return;
|
||||
}
|
||||
if (file.size > maxSizeBytes) {
|
||||
alert(`File size exceeds ${maxSizeMB}MB. Please upload a smaller image.`)
|
||||
return
|
||||
}
|
||||
|
||||
setImage(URL.createObjectURL(file));
|
||||
setCropModalOpen(true);
|
||||
} else {
|
||||
alert("No file selected or an error occurred while selecting the file.");
|
||||
}
|
||||
}, []);
|
||||
setImage(URL.createObjectURL(file))
|
||||
setCropModalOpen(true)
|
||||
} else {
|
||||
alert('No file selected or an error occurred while selecting the file.')
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onImageLoad = useCallback((e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
const { width, height } = e.currentTarget;
|
||||
setCrop(centerAspectCrop(width, height, 1));
|
||||
}, []);
|
||||
const onImageLoad = useCallback((e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
const { width, height } = e.currentTarget
|
||||
setCrop(centerAspectCrop(width, height, 1))
|
||||
}, [])
|
||||
|
||||
const centerAspectCrop = (mediaWidth: number, mediaHeight: number, aspect: number) => {
|
||||
return centerCrop(
|
||||
makeAspectCrop(
|
||||
{
|
||||
unit: 'px',
|
||||
width: mediaWidth / 2,
|
||||
},
|
||||
aspect,
|
||||
mediaWidth,
|
||||
mediaHeight,
|
||||
),
|
||||
mediaWidth,
|
||||
mediaHeight,
|
||||
);
|
||||
};
|
||||
const centerAspectCrop = (mediaWidth: number, mediaHeight: number, aspect: number) => {
|
||||
return centerCrop(
|
||||
makeAspectCrop(
|
||||
{
|
||||
unit: 'px',
|
||||
width: mediaWidth / 2
|
||||
},
|
||||
aspect,
|
||||
mediaWidth,
|
||||
mediaHeight
|
||||
),
|
||||
mediaWidth,
|
||||
mediaHeight
|
||||
)
|
||||
}
|
||||
|
||||
async function resizeImage(image: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<HTMLImageElement> {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
async function resizeImage (image: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<HTMLImageElement> {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
let width = image.width;
|
||||
let height = image.height;
|
||||
let width = image.width
|
||||
let height = image.height
|
||||
|
||||
if (width > maxWidth) {
|
||||
height *= maxWidth / width;
|
||||
width = maxWidth;
|
||||
}
|
||||
if (height > maxHeight) {
|
||||
width *= maxHeight / height;
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
if (ctx) {
|
||||
ctx.drawImage(image, 0, 0, width, height);
|
||||
}
|
||||
|
||||
const resizedImage = new Image();
|
||||
resizedImage.src = canvas.toDataURL();
|
||||
|
||||
await resizedImage.decode();
|
||||
return resizedImage;
|
||||
if (width > maxWidth) {
|
||||
height *= maxWidth / width
|
||||
width = maxWidth
|
||||
}
|
||||
if (height > maxHeight) {
|
||||
width *= maxHeight / height
|
||||
height = maxHeight
|
||||
}
|
||||
|
||||
const renderCrop = useCallback(async () => {
|
||||
const image = imgRef.current;
|
||||
if (crop && image) {
|
||||
const resizedImage = await resizeImage(image, 1024, 1024); // Bildgröße vor dem Zuschneiden reduzieren
|
||||
const scaleX = resizedImage.naturalWidth / resizedImage.width;
|
||||
const scaleY = resizedImage.naturalHeight / resizedImage.height;
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
const canvas = new OffscreenCanvas(
|
||||
crop.width * scaleX,
|
||||
crop.height * scaleY
|
||||
);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
canvas.width = crop.width * pixelRatio * scaleX;
|
||||
canvas.height = crop.height * pixelRatio * scaleY;
|
||||
if (ctx) {
|
||||
ctx.drawImage(image, 0, 0, width, height)
|
||||
}
|
||||
|
||||
if (ctx) {
|
||||
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
ctx.drawImage(
|
||||
resizedImage,
|
||||
crop.x * scaleX,
|
||||
crop.y * scaleY,
|
||||
crop.width * scaleX,
|
||||
crop.height * scaleY,
|
||||
0,
|
||||
0,
|
||||
crop.width * scaleX,
|
||||
crop.height * scaleY
|
||||
);
|
||||
}
|
||||
const resizedImage = new Image()
|
||||
resizedImage.src = canvas.toDataURL()
|
||||
|
||||
const blob = await canvas.convertToBlob();
|
||||
await resizeBlob(blob);
|
||||
setCropping(false);
|
||||
setImage("");
|
||||
}
|
||||
await resizedImage.decode()
|
||||
return resizedImage
|
||||
}
|
||||
|
||||
const renderCrop = useCallback(async () => {
|
||||
const image = imgRef.current
|
||||
if (crop && image) {
|
||||
const resizedImage = await resizeImage(image, 1024, 1024) // Bildgröße vor dem Zuschneiden reduzieren
|
||||
const scaleX = resizedImage.naturalWidth / resizedImage.width
|
||||
const scaleY = resizedImage.naturalHeight / resizedImage.height
|
||||
|
||||
const canvas = new OffscreenCanvas(
|
||||
crop.width * scaleX,
|
||||
crop.height * scaleY
|
||||
)
|
||||
const ctx = canvas.getContext('2d')
|
||||
const pixelRatio = window.devicePixelRatio
|
||||
canvas.width = crop.width * pixelRatio * scaleX
|
||||
canvas.height = crop.height * pixelRatio * scaleY
|
||||
|
||||
if (ctx) {
|
||||
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
|
||||
ctx.drawImage(
|
||||
resizedImage,
|
||||
crop.x * scaleX,
|
||||
crop.y * scaleY,
|
||||
crop.width * scaleX,
|
||||
crop.height * scaleY,
|
||||
0,
|
||||
0,
|
||||
crop.width * scaleX,
|
||||
crop.height * scaleY
|
||||
)
|
||||
}
|
||||
|
||||
const blob = await canvas.convertToBlob()
|
||||
await resizeBlob(blob)
|
||||
setCropping(false)
|
||||
setImage('')
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [crop]);
|
||||
}, [crop])
|
||||
|
||||
const resizeBlob = useCallback(async (blob: Blob) => {
|
||||
const img = new Image();
|
||||
img.src = URL.createObjectURL(blob);
|
||||
await img.decode();
|
||||
const resizeBlob = useCallback(async (blob: Blob) => {
|
||||
const img = new Image()
|
||||
img.src = URL.createObjectURL(blob)
|
||||
await img.decode()
|
||||
|
||||
const canvas = new OffscreenCanvas(400, 400);
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx?.drawImage(img, 0, 0, 400, 400);
|
||||
const canvas = new OffscreenCanvas(400, 400)
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx?.drawImage(img, 0, 0, 400, 400)
|
||||
|
||||
const resizedBlob = await canvas.convertToBlob();
|
||||
const asset = await assetsApi.upload(resizedBlob, "avatar");
|
||||
setAvatar(asset.id);
|
||||
}, [assetsApi, setAvatar]);
|
||||
const resizedBlob = await canvas.convertToBlob()
|
||||
const asset = await assetsApi.upload(resizedBlob, 'avatar')
|
||||
setAvatar(asset.id)
|
||||
}, [assetsApi, setAvatar])
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
{!cropping ?
|
||||
<label className="custom-file-upload">
|
||||
{!cropping
|
||||
? <label className="custom-file-upload">
|
||||
<input type="file" accept="image/*" className="tw-file-input tw-w-full tw-max-w-xs" onChange={onImageChange} />
|
||||
<div className='button tw-btn tw-btn-lg tw-btn-circle tw-animate-none'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="tw-w-6 tw-h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
|
||||
</svg>
|
||||
</div>
|
||||
{avatar ?
|
||||
<div className='tw-h-20 tw-w-20'>
|
||||
{avatar
|
||||
? <div className='tw-h-20 tw-w-20'>
|
||||
<img src={assetsApi.url + avatar} className='tw-h-20 tw-w-20 tw-rounded-full' />
|
||||
</div>
|
||||
:
|
||||
<div className='tw-h-20 tw-w-20'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 150 150" className='tw-w-20 tw-h-20 tw-rounded-full' style={{ backgroundColor: "#eee" }}>
|
||||
: <div className='tw-h-20 tw-w-20'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 150 150" className='tw-w-20 tw-h-20 tw-rounded-full' style={{ backgroundColor: '#eee' }}>
|
||||
<path fill="#ccc" d="M 104.68731,56.689353 C 102.19435,80.640493 93.104981,97.26875 74.372196,97.26875 55.639402,97.26875 46.988823,82.308034 44.057005,57.289941 41.623314,34.938838 55.639402,15.800152 74.372196,15.800152 c 18.732785,0 32.451944,18.493971 30.315114,40.889201 z" />
|
||||
<path fill="#ccc" d="M 92.5675 89.6048 C 90.79484 93.47893 89.39893 102.4504 94.86478 106.9039 C 103.9375 114.2963 106.7064 116.4723 118.3117 118.9462 C 144.0432 124.4314 141.6492 138.1543 146.5244 149.2206 L 4.268444 149.1023 C 8.472223 138.6518 6.505799 124.7812 32.40051 118.387 C 41.80992 116.0635 45.66513 113.8823 53.58659 107.0158 C 58.52744 102.7329 57.52583 93.99267 56.43084 89.26926 C 52.49275 88.83011 94.1739 88.14054 92.5675 89.6048 z" />
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</label>
|
||||
: <div className='tw-w-20 tw-flex tw-items-center tw-justify-center'>
|
||||
: <div className='tw-w-20 tw-flex tw-items-center tw-justify-center'>
|
||||
<span className="tw-loading tw-loading-spinner"></span>
|
||||
</div>
|
||||
}
|
||||
@ -179,19 +178,19 @@ export const AvatarWidget: React.FC<AvatarWidgetProps> = ({ avatar, setAvatar })
|
||||
title=""
|
||||
isOpened={cropModalOpen}
|
||||
onClose={() => {
|
||||
setCropModalOpen(false);
|
||||
setImage("");
|
||||
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();
|
||||
<button className={'tw-btn tw-btn-primary'} onClick={() => {
|
||||
setCropping(true)
|
||||
setCropModalOpen(false)
|
||||
renderCrop()
|
||||
}}>Select</button>
|
||||
</DialogModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,35 +1,34 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import * as React from "react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import "./ColorPicker.css"
|
||||
import useClickOutside from "../hooks/useClickOutside";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import * as React from 'react'
|
||||
import { HexColorPicker } from 'react-colorful'
|
||||
import './ColorPicker.css'
|
||||
import useClickOutside from '../hooks/useClickOutside'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const ColorPicker = ({ color, onChange, className }) => {
|
||||
const popover = useRef<HTMLDivElement>(null);
|
||||
const [isOpen, toggle] = useState(false);
|
||||
const popover = useRef<HTMLDivElement>(null)
|
||||
const [isOpen, toggle] = useState(false)
|
||||
|
||||
const close = useCallback(() => toggle(false), []);
|
||||
useClickOutside(popover, close);
|
||||
const close = useCallback(() => toggle(false), [])
|
||||
useClickOutside(popover, close)
|
||||
|
||||
const colorPickerRef = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Füge dem Color-Picker explizit Event-Listener hinzu
|
||||
const colorPickerElement = colorPickerRef.current;
|
||||
if (colorPickerElement) {
|
||||
const enablePropagation = (event) => {
|
||||
// Verhindere, dass Leaflet die Propagation stoppt
|
||||
event.stopPropagation = () => {};
|
||||
};
|
||||
|
||||
// Event-Listener für den Color-Picker
|
||||
['click', 'dblclick', 'mousedown', 'touchstart'].forEach(eventType => {
|
||||
colorPickerElement.addEventListener(eventType, enablePropagation, true);
|
||||
});
|
||||
}
|
||||
|
||||
}, []);
|
||||
// Füge dem Color-Picker explizit Event-Listener hinzu
|
||||
const colorPickerElement = colorPickerRef.current
|
||||
if (colorPickerElement) {
|
||||
const enablePropagation = (event) => {
|
||||
// Verhindere, dass Leaflet die Propagation stoppt
|
||||
event.stopPropagation = () => {}
|
||||
};
|
||||
|
||||
// Event-Listener für den Color-Picker
|
||||
['click', 'dblclick', 'mousedown', 'touchstart'].forEach(eventType => {
|
||||
colorPickerElement.addEventListener(eventType, enablePropagation, true)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={colorPickerRef} className={`picker ${className}`}>
|
||||
@ -45,6 +44,5 @@ export const ColorPicker = ({ color, onChange, className }) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAssetApi } from "../../AppShell/hooks/useAssets";
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useAssetApi } from '../../AppShell/hooks/useAssets'
|
||||
|
||||
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">
|
||||
<h2 className="tw-text-lg tw-font-semibold">Du hast Fragen?</h2>
|
||||
<div className="tw-mt-4 tw-flex tw-items-center">
|
||||
@ -54,21 +54,21 @@ const ContactInfo = ({ email, telephone, name, avatar, link }: { email: string,
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default ContactInfo;
|
||||
export default ContactInfo
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const ConditionalLink = ({ url, children }) => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
if (url) {
|
||||
return (
|
||||
<Link to={url+"?"+params}>
|
||||
if (url) {
|
||||
return (
|
||||
<Link to={url + '?' + params}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return children;
|
||||
};
|
||||
)
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { TextInput } from "../../Input"
|
||||
import { AvatarWidget } from "./AvatarWidget"
|
||||
import { ColorPicker } from "./ColorPicker"
|
||||
import { TextInput } from '../../Input'
|
||||
import { AvatarWidget } from './AvatarWidget'
|
||||
import { ColorPicker } from './ColorPicker'
|
||||
|
||||
export const FormHeader = ({item, state, setState}) => {
|
||||
export const FormHeader = ({ item, state, setState }) => {
|
||||
return (
|
||||
<div className="tw-flex">
|
||||
<AvatarWidget avatar={state.image} setAvatar={(i) => setState(prevState => ({
|
||||
...prevState,
|
||||
image: i
|
||||
...prevState,
|
||||
image: i
|
||||
}))} />
|
||||
<ColorPicker color={state.color} onChange={(c) => setState(prevState => ({
|
||||
...prevState,
|
||||
color: c
|
||||
}))} className={"-tw-left-6 tw-top-14 -tw-mr-6"} />
|
||||
...prevState,
|
||||
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 => ({
|
||||
...prevState,
|
||||
name: v
|
||||
<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
|
||||
<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>
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { useEffect } from "react";
|
||||
import { getValue } from "../../../Utils/GetValue";
|
||||
import { Item } from "../../../types";
|
||||
import { useAssetApi } from "../../AppShell/hooks/useAssets";
|
||||
import { useEffect } from 'react'
|
||||
import { getValue } from '../../../Utils/GetValue'
|
||||
import { Item } from '../../../types'
|
||||
import { useAssetApi } from '../../AppShell/hooks/useAssets'
|
||||
|
||||
|
||||
|
||||
|
||||
export function LinkedItemsHeaderView({ item, unlinkCallback, itemNameField, itemAvatarField, loading, unlinkPermission, itemSubnameField }: {
|
||||
export function LinkedItemsHeaderView ({ item, unlinkCallback, itemNameField, itemAvatarField, loading, unlinkPermission, itemSubnameField }: {
|
||||
item: Item,
|
||||
unlinkCallback?: any,
|
||||
itemNameField?: string,
|
||||
@ -15,35 +12,30 @@ export function LinkedItemsHeaderView({ item, unlinkCallback, itemNameField, ite
|
||||
loading?: boolean,
|
||||
unlinkPermission: boolean
|
||||
}) {
|
||||
const assetsApi = useAssetApi()
|
||||
|
||||
const assetsApi = useAssetApi();
|
||||
|
||||
|
||||
const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && assetsApi.url + getValue(item, item.layer?.itemAvatarField);
|
||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField);
|
||||
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField);
|
||||
|
||||
|
||||
const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && assetsApi.url + getValue(item, item.layer?.itemAvatarField)
|
||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField)
|
||||
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [item])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
{avatar && (
|
||||
<img
|
||||
className={`tw-w-10 tw-inline tw-rounded-full`}
|
||||
className={'tw-w-10 tw-inline tw-rounded-full'}
|
||||
src={avatar}
|
||||
alt={item.name + " logo"}
|
||||
alt={item.name + ' logo'}
|
||||
/>
|
||||
)}
|
||||
<div className={`${avatar ? "tw-ml-2" : ""} tw-overflow-hidden`}>
|
||||
<div className={`tw-text-xl tw-font-semibold tw-truncate`}>
|
||||
<div className={`${avatar ? 'tw-ml-2' : ''} tw-overflow-hidden`}>
|
||||
<div className={'tw-text-xl tw-font-semibold tw-truncate'}>
|
||||
{title}
|
||||
</div>
|
||||
{subtitle && <div className="tw-text-xs tw-truncate tw-text-gray-500 ">
|
||||
@ -63,9 +55,9 @@ export function LinkedItemsHeaderView({ item, unlinkCallback, itemNameField, ite
|
||||
<ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-z-1000">
|
||||
{true && <li>
|
||||
<a 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>}
|
||||
{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>
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { LayerProps } from "../../../types";
|
||||
import { useHasUserPermission } from "../../Map/hooks/usePermissions";
|
||||
import { LayerProps } from '../../../types'
|
||||
import { useHasUserPermission } from '../../Map/hooks/usePermissions'
|
||||
|
||||
export function PlusButton({ layer, triggerAction, color, collection="items" }: { layer?: LayerProps ,triggerAction: any, color: string, collection?:string }) {
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
return (
|
||||
<>{hasUserPermission(collection, "create", undefined, layer) &&
|
||||
export function PlusButton ({ layer, triggerAction, color, collection = 'items' }: { layer?: LayerProps, triggerAction: any, color: string, collection?:string }) {
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
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" >
|
||||
<button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { triggerAction() }} style={{ backgroundColor: color, color: "#fff"}}>
|
||||
<button tabIndex={0} className="tw-z-500 tw-btn tw-btn-circle tw-shadow" onClick={() => { triggerAction() }} style={{ backgroundColor: color, color: '#fff' }}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor" className="tw-w-5 tw-h-5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import SocialShareBar from './SocialShareBar';
|
||||
|
||||
import SocialShareBar from './SocialShareBar'
|
||||
|
||||
/* const flags = {
|
||||
de: (
|
||||
@ -19,10 +18,10 @@ import SocialShareBar from './SocialShareBar';
|
||||
}; */
|
||||
|
||||
const statusMapping = {
|
||||
'in_planning': 'in Planung',
|
||||
'paused': 'pausiert',
|
||||
'active': 'aktiv'
|
||||
};
|
||||
in_planning: 'in Planung',
|
||||
paused: 'pausiert',
|
||||
active: 'aktiv'
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const SubHeader = ({ type, status, url, title }) => (
|
||||
@ -30,7 +29,7 @@ const SubHeader = ({ type, status, url, title }) => (
|
||||
<div className='tw-float-left tw-mt-2 tw-mb-4 tw-flex tw-items-center'>
|
||||
|
||||
{status && <div className="tw-mt-1.5">
|
||||
<span className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-0.5 tw-px-2 tw-inline-flex tw-items-center tw-mr-2"><span className={`tw-w-2 tw-h-2 ${ status=="in_planning" && "tw-bg-blue-700"} ${ status=="paused" && "tw-bg-orange-400"} ${ status=="active" && "tw-bg-green-500"} tw-rounded-full tw-mr-1.5`}></span>{statusMapping[status]}</span>
|
||||
<span className="tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-0.5 tw-px-2 tw-inline-flex tw-items-center tw-mr-2"><span className={`tw-w-2 tw-h-2 ${status === 'in_planning' && 'tw-bg-blue-700'} ${status === 'paused' && 'tw-bg-orange-400'} ${status === 'active' && 'tw-bg-green-500'} tw-rounded-full tw-mr-1.5`}></span>{statusMapping[status]}</span>
|
||||
</div>}
|
||||
{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>
|
||||
@ -40,6 +39,6 @@ const SubHeader = ({ type, status, url, title }) => (
|
||||
<SocialShareBar url={url} title={title} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default SubHeader;
|
||||
export default SubHeader
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const RelationCard = ({ title, description, imageSrc }) => (
|
||||
<div className={`tw-mb-6 ${imageSrc ? 'md:tw-flex md:tw-space-x-4' : ''}`}>
|
||||
@ -12,6 +11,6 @@ const RelationCard = ({ title, description, imageSrc }) => (
|
||||
<p className="tw-mt-2 tw-text-sm tw-text-gray-600">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default RelationCard;
|
||||
export default RelationCard
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import SocialShareButton from './SocialShareButton';
|
||||
import SocialShareButton from './SocialShareButton'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const SocialShareBar = ({url, title, platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email']}) => {
|
||||
return (
|
||||
const SocialShareBar = ({ url, title, platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email'] }) => {
|
||||
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">
|
||||
{platforms.map((platform) => (
|
||||
<SocialShareButton
|
||||
@ -13,7 +13,7 @@ const SocialShareBar = ({url, title, platforms = ['facebook', 'twitter', 'linked
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SocialShareBar;
|
||||
export default SocialShareBar
|
||||
|
||||
@ -1,81 +1,81 @@
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
const platformConfigs = {
|
||||
facebook: {
|
||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||
icon: (
|
||||
facebook: {
|
||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||
icon: (
|
||||
<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" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#3b5998'
|
||||
},
|
||||
twitter: {
|
||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||
icon: (
|
||||
),
|
||||
bgColor: '#3b5998'
|
||||
},
|
||||
twitter: {
|
||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||
icon: (
|
||||
<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" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#55acee'
|
||||
},
|
||||
linkedin: {
|
||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||
icon: (
|
||||
),
|
||||
bgColor: '#55acee'
|
||||
},
|
||||
linkedin: {
|
||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||
icon: (
|
||||
<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" />
|
||||
<circle cx="4" cy="4" r="2" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#4875b4'
|
||||
},
|
||||
xing: {
|
||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||
icon: (
|
||||
),
|
||||
bgColor: '#4875b4'
|
||||
},
|
||||
xing: {
|
||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||
icon: (
|
||||
<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" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#026466'
|
||||
},
|
||||
email: {
|
||||
shareUrl: 'mailto:?subject={title}&body={url}',
|
||||
icon: (
|
||||
),
|
||||
bgColor: '#026466'
|
||||
},
|
||||
email: {
|
||||
shareUrl: 'mailto:?subject={title}&body={url}',
|
||||
icon: (
|
||||
<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" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#444444'
|
||||
}
|
||||
};
|
||||
),
|
||||
bgColor: '#444444'
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const SocialShareButton = ({ platform, url, title }) => {
|
||||
const config = platformConfigs[platform];
|
||||
const config = platformConfigs[platform]
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
if (!config) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { shareUrl, icon, bgColor } = config;
|
||||
const finalShareUrl = shareUrl
|
||||
.replace('{url}', encodeURIComponent(url))
|
||||
.replace('{title}', encodeURIComponent(title));
|
||||
const { shareUrl, icon, bgColor } = config
|
||||
const finalShareUrl = shareUrl
|
||||
.replace('{url}', encodeURIComponent(url))
|
||||
.replace('{title}', encodeURIComponent(title))
|
||||
|
||||
return (
|
||||
return (
|
||||
<a
|
||||
href={finalShareUrl}
|
||||
target='_blank'
|
||||
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'
|
||||
style={{
|
||||
color: 'white',
|
||||
backgroundColor: bgColor
|
||||
color: 'white',
|
||||
backgroundColor: bgColor
|
||||
}}
|
||||
>
|
||||
{React.cloneElement(icon, { className: 'tw-w-4 tw-h-4 tw-fill-current' })}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SocialShareButton;
|
||||
export default SocialShareButton
|
||||
|
||||
@ -1,99 +1,95 @@
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTags } from '../../Map/hooks/useTags';
|
||||
import { Tag } from '../../../types';
|
||||
import { Autocomplete } from '../../Input/Autocomplete';
|
||||
import { randomColor } from '../../../Utils/RandomColor';
|
||||
import { decodeTag, encodeTag } from '../../../Utils/FormatTags';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTags } from '../../Map/hooks/useTags'
|
||||
import { Tag } from '../../../types'
|
||||
import { Autocomplete } from '../../Input/Autocomplete'
|
||||
import { randomColor } from '../../../Utils/RandomColor'
|
||||
import { decodeTag, encodeTag } from '../../../Utils/FormatTags'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const TagsWidget = ({placeholder, containerStyle, defaultTags, onUpdate}) => {
|
||||
export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate }) => {
|
||||
const [input, setInput] = useState('')
|
||||
const [isKeyReleased, setIsKeyReleased] = useState(false)
|
||||
const tags = useTags()
|
||||
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([])
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const [isKeyReleased, setIsKeyReleased] = useState(false);
|
||||
const tags = useTags();
|
||||
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([]);
|
||||
|
||||
const [focusInput, setFocusInput] = useState<boolean>(false);
|
||||
const [currentTags, setCurrentTags] = useState<Array<Tag>>(defaultTags);
|
||||
const [focusInput, setFocusInput] = useState<boolean>(false)
|
||||
const [currentTags, setCurrentTags] = useState<Array<Tag>>(defaultTags)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentTags(defaultTags)
|
||||
}, [defaultTags])
|
||||
|
||||
|
||||
|
||||
const onChange = (e) => {
|
||||
const { value } = e.target;
|
||||
setInput(value);
|
||||
};
|
||||
const { value } = e.target
|
||||
setInput(value)
|
||||
}
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
const { key } = e;
|
||||
const trimmedInput = input.trim();
|
||||
const { key } = e
|
||||
const trimmedInput = input.trim()
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
if ((key === 'Enter' || key === ',' ) && trimmedInput.length && !defaultTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) {
|
||||
e.preventDefault();
|
||||
if ((key === 'Enter' || key === ',') && trimmedInput.length && !defaultTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) {
|
||||
e.preventDefault()
|
||||
const newTag = tags.find(t => t.name === trimmedInput.toLocaleLowerCase())
|
||||
newTag && onUpdate([...currentTags, newTag]);
|
||||
!newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: encodeTag(trimmedInput), color: randomColor() }]);
|
||||
setInput('');
|
||||
setPushFilteredSuggestions([]);
|
||||
newTag && onUpdate([...currentTags, newTag])
|
||||
!newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: encodeTag(trimmedInput), color: randomColor() }])
|
||||
setInput('')
|
||||
setPushFilteredSuggestions([])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
if (key === "Backspace" && !input.length && defaultTags.length && isKeyReleased) {
|
||||
const defaultTagsCopy = [...defaultTags];
|
||||
const poppedTag = defaultTagsCopy.pop();
|
||||
e.preventDefault();
|
||||
onUpdate(defaultTagsCopy);
|
||||
poppedTag && setInput(poppedTag.name);
|
||||
if (key === 'Backspace' && !input.length && defaultTags.length && isKeyReleased) {
|
||||
const defaultTagsCopy = [...defaultTags]
|
||||
const poppedTag = defaultTagsCopy.pop()
|
||||
e.preventDefault()
|
||||
onUpdate(defaultTagsCopy)
|
||||
poppedTag && setInput(poppedTag.name)
|
||||
}
|
||||
|
||||
setIsKeyReleased(false);
|
||||
};
|
||||
setIsKeyReleased(false)
|
||||
}
|
||||
|
||||
const onKeyUp = () => {
|
||||
setIsKeyReleased(true);
|
||||
setIsKeyReleased(true)
|
||||
}
|
||||
|
||||
const deleteTag = (tag) => {
|
||||
onUpdate(currentTags.filter((t) => t !== tag))
|
||||
}
|
||||
|
||||
|
||||
const onSelected = (tag) => {
|
||||
// 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())
|
||||
newTag && onUpdate([...currentTags, newTag]);
|
||||
!newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }]);
|
||||
setInput('');
|
||||
setPushFilteredSuggestions([]);
|
||||
newTag && onUpdate([...currentTags, newTag])
|
||||
!newTag && onUpdate([...currentTags, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }])
|
||||
setInput('')
|
||||
setPushFilteredSuggestions([])
|
||||
}
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
value: input,
|
||||
placeholder: placeholder,
|
||||
onKeyDown: onKeyDown,
|
||||
onKeyUp: onKeyUp,
|
||||
onChange: onChange,
|
||||
placeholder,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
onChange,
|
||||
className: 'tw-bg-transparent tw-w-fit tw-mt-5 tw-h-fit'
|
||||
}
|
||||
|
||||
/* eslint-disable react/prop-types */
|
||||
return (
|
||||
<div onClick={()=> {
|
||||
setFocusInput(true);
|
||||
setTimeout(()=> {
|
||||
<div onClick={() => {
|
||||
setFocusInput(true)
|
||||
setTimeout(() => {
|
||||
setFocusInput(false)
|
||||
}, 200)
|
||||
}} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}>
|
||||
<div className='tw-flex tw-flex-wrap tw-h-fit'>
|
||||
{defaultTags.map((tag) => (
|
||||
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4' style={{ backgroundColor: tag.color ? tag.color : "#666" }}>
|
||||
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4' style={{ backgroundColor: tag.color ? tag.color : '#666' }}>
|
||||
<div className="tw-card-actions tw-justify-end">
|
||||
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (deleteTag(tag))}>✕</label>
|
||||
</div><b>{decodeTag(tag.name)}</b>
|
||||
@ -105,4 +101,4 @@ export const TagsWidget = ({placeholder, containerStyle, defaultTags, onUpdate})
|
||||
</div>
|
||||
)
|
||||
/* eslint-enable react/prop-types */
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import { useEffect } from "react";
|
||||
import { Item, Tag } from "../../../types"
|
||||
import { TextAreaInput, TextInput } from "../../Input"
|
||||
import ComboBoxInput from "../../Input/ComboBoxInput"
|
||||
import { useEffect } from 'react'
|
||||
import { Item, Tag } from '../../../types'
|
||||
import { TextAreaInput, TextInput } from '../../Input'
|
||||
import ComboBoxInput from '../../Input/ComboBoxInput'
|
||||
|
||||
export const OnepagerForm = ({ item, state, setState }: {
|
||||
state: {
|
||||
@ -25,53 +25,51 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
setState: React.Dispatch<React.SetStateAction<any>>,
|
||||
item: Item
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
switch (state.groupType) {
|
||||
case 'wuerdekompass':
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
color: item?.layer?.menuColor || '#1A5FB4',
|
||||
markerIcon: 'group',
|
||||
image: '59e6a346-d1ee-4767-9e42-fc720fb535c9'
|
||||
}))
|
||||
break
|
||||
case 'themenkompass':
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
color: '#26A269',
|
||||
markerIcon: 'group',
|
||||
image: '59e6a346-d1ee-4767-9e42-fc720fb535c9'
|
||||
}))
|
||||
break
|
||||
case 'liebevoll.jetzt':
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
color: '#E8B620',
|
||||
markerIcon: 'liebevoll.jetzt',
|
||||
image: 'e735b96c-507b-471c-8317-386ece0ca51d'
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
switch (state.groupType) {
|
||||
case "wuerdekompass":
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
color: item?.layer?.menuColor || "#1A5FB4",
|
||||
markerIcon: "group",
|
||||
image: "59e6a346-d1ee-4767-9e42-fc720fb535c9"
|
||||
}));
|
||||
break;
|
||||
case "themenkompass":
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
color: "#26A269",
|
||||
markerIcon: "group",
|
||||
image: "59e6a346-d1ee-4767-9e42-fc720fb535c9"
|
||||
}));
|
||||
break;
|
||||
case "liebevoll.jetzt":
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
color: "#E8B620",
|
||||
markerIcon: "liebevoll.jetzt",
|
||||
image: "e735b96c-507b-471c-8317-386ece0ca51d"
|
||||
}));
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.groupType])
|
||||
}, [state.groupType])
|
||||
|
||||
const typeMapping = [
|
||||
{ value: 'wuerdekompass', label: 'Regional-Gruppe' },
|
||||
{ value: 'themenkompass', label: 'Themen-Gruppe' },
|
||||
{ value: 'liebevoll.jetzt', label: 'liebevoll.jetzt' }
|
||||
]
|
||||
const statusMapping = [
|
||||
{ value: 'active', label: 'aktiv' },
|
||||
{ value: 'in_planning', label: 'in Planung' },
|
||||
{ value: 'paused', label: 'pausiert' }
|
||||
]
|
||||
|
||||
const typeMapping = [
|
||||
{ value: 'wuerdekompass', label: 'Regional-Gruppe' },
|
||||
{ value: 'themenkompass', label: 'Themen-Gruppe' },
|
||||
{ value: 'liebevoll.jetzt', label: 'liebevoll.jetzt' }
|
||||
];
|
||||
const statusMapping = [
|
||||
{ value: 'active', label: 'aktiv' },
|
||||
{ value: 'in_planning', label: 'in Planung' },
|
||||
{ value: 'paused', label: 'pausiert' }
|
||||
];
|
||||
|
||||
return (
|
||||
return (
|
||||
<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>
|
||||
@ -83,8 +81,8 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
options={typeMapping}
|
||||
value={state.groupType}
|
||||
onValueChange={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
groupType: v
|
||||
...prevState,
|
||||
groupType: v
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
@ -97,8 +95,8 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
options={statusMapping}
|
||||
value={state.status}
|
||||
onValueChange={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
status: v
|
||||
...prevState,
|
||||
status: v
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
@ -112,8 +110,8 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
placeholder="Email"
|
||||
defaultValue={state.contact}
|
||||
updateFormValue={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
contact: v
|
||||
...prevState,
|
||||
contact: v
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
@ -126,8 +124,8 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
placeholder="Telefonnummer"
|
||||
defaultValue={state.telephone}
|
||||
updateFormValue={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
telephone: v
|
||||
...prevState,
|
||||
telephone: v
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
@ -140,8 +138,8 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
placeholder="Nächste Termine"
|
||||
defaultValue={state.nextAppointment}
|
||||
updateFormValue={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
nextAppointment: v
|
||||
...prevState,
|
||||
nextAppointment: v
|
||||
}))}
|
||||
inputStyle="tw-h-24"
|
||||
/>
|
||||
@ -153,14 +151,14 @@ export const OnepagerForm = ({ item, state, setState }: {
|
||||
</label>
|
||||
<TextAreaInput
|
||||
placeholder="Beschreibung"
|
||||
defaultValue={state.text || ""}
|
||||
defaultValue={state.text || ''}
|
||||
updateFormValue={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
text: v
|
||||
...prevState,
|
||||
text: v
|
||||
}))}
|
||||
inputStyle="tw-h-48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,30 +1,27 @@
|
||||
import { Item } from "../../../types"
|
||||
import { TextView } from "../../Map"
|
||||
import ContactInfo from "../Subcomponents/ContactInfo"
|
||||
import ProfileSubHeader from "../Subcomponents/ProfileSubHeader"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useItems } from "../../Map/hooks/useItems"
|
||||
|
||||
export const OnepagerView = ({item, userType}:{item: Item, userType: string}) => {
|
||||
|
||||
const [profile_owner, setProfileOwner] = useState<Item>();
|
||||
const items = useItems();
|
||||
|
||||
import { Item } from '../../../types'
|
||||
import { TextView } from '../../Map'
|
||||
import ContactInfo from '../Subcomponents/ContactInfo'
|
||||
import ProfileSubHeader from '../Subcomponents/ProfileSubHeader'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useItems } from '../../Map/hooks/useItems'
|
||||
|
||||
export const OnepagerView = ({ item, userType }:{item: Item, userType: string}) => {
|
||||
const [profileOwner, setProfileOwner] = useState<Item>()
|
||||
const items = useItems()
|
||||
|
||||
useEffect(() => {
|
||||
setProfileOwner(items.find(i => (i.user_created?.id === item.user_created?.id) && i.layer?.itemType.name === userType));
|
||||
setProfileOwner(items.find(i => (i.user_created?.id === item.user_created?.id) && i.layer?.itemType.name === userType))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, items])
|
||||
|
||||
const typeMapping = {
|
||||
'wuerdekompass': 'Regional-Gruppe',
|
||||
'themenkompass': 'Themenkompass-Gruppe',
|
||||
'liebevoll.jetzt': 'liebevoll.jetzt',
|
||||
};
|
||||
wuerdekompass: 'Regional-Gruppe',
|
||||
themenkompass: 'Themenkompass-Gruppe',
|
||||
'liebevoll.jetzt': 'liebevoll.jetzt'
|
||||
}
|
||||
|
||||
const groupType = item.group_type ? item.group_type : 'default';
|
||||
const groupTypeText = typeMapping[groupType];
|
||||
const groupType = item.group_type ? item.group_type : 'default'
|
||||
const groupTypeText = typeMapping[groupType]
|
||||
|
||||
return (
|
||||
<div className='tw-h-full tw-overflow-y-auto fade'>
|
||||
@ -37,7 +34,7 @@ const groupTypeText = typeMapping[groupType];
|
||||
/>
|
||||
</div>
|
||||
{item.user_created.first_name && (
|
||||
<ContactInfo link={`/item/${profile_owner?.id}`} name={profile_owner?.name ? profile_owner.name : item.user_created.first_name} avatar={profile_owner?.image ? profile_owner.image : item.user_created.avatar} email={item.contact} telephone={item.telephone} />
|
||||
<ContactInfo link={`/item/${profileOwner?.id}`} name={profileOwner?.name ? profileOwner.name : item.user_created.first_name} avatar={profileOwner?.image ? profileOwner.image : item.user_created.avatar} email={item.contact} telephone={item.telephone} />
|
||||
)}
|
||||
|
||||
{/* Description Section */}
|
||||
@ -56,19 +53,19 @@ const groupTypeText = typeMapping[groupType];
|
||||
)};
|
||||
|
||||
{/* Relations Section */}
|
||||
{/*{d.relations && (*/}
|
||||
{/* <div className="tw-my-10 tw-px-6">*/}
|
||||
{/* <h2 className="tw-text-lg tw-font-semibold tw-mb-4">Projekte</h2>*/}
|
||||
{/* {d.relations.map((project, index) => (*/}
|
||||
{/* <RelationCard*/}
|
||||
{/* key={index}*/}
|
||||
{/* title={project.title}*/}
|
||||
{/* description={project.description}*/}
|
||||
{/* imageSrc={project.imageSrc}*/}
|
||||
{/* />*/}
|
||||
{/* ))}*/}
|
||||
{/* </div>*/}
|
||||
{/*)}*/}
|
||||
{/* {d.relations && ( */}
|
||||
{/* <div className="tw-my-10 tw-px-6"> */}
|
||||
{/* <h2 className="tw-text-lg tw-font-semibold tw-mb-4">Projekte</h2> */}
|
||||
{/* {d.relations.map((project, index) => ( */}
|
||||
{/* <RelationCard */}
|
||||
{/* key={index} */}
|
||||
{/* title={project.title} */}
|
||||
{/* description={project.description} */}
|
||||
{/* imageSrc={project.imageSrc} */}
|
||||
{/* /> */}
|
||||
{/* ))} */}
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { TextAreaInput } from "../../Input";
|
||||
import { TextAreaInput } from '../../Input'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const SimpleForm = ({ state, setState }) => {
|
||||
@ -6,7 +6,7 @@ export const SimpleForm = ({ state, setState }) => {
|
||||
<TextAreaInput
|
||||
placeholder="About me ..."
|
||||
// eslint-disable-next-line react/prop-types
|
||||
defaultValue={state?.text || ""}
|
||||
defaultValue={state?.text || ''}
|
||||
updateFormValue={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
text: v
|
||||
@ -14,5 +14,5 @@ export const SimpleForm = ({ state, setState }) => {
|
||||
containerStyle='tw-mt-8 tw-h-full'
|
||||
inputStyle='tw-h-full'
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import * as React from 'react'
|
||||
import { TextView } from '../../Map'
|
||||
import { Item } from '../../../types'
|
||||
|
||||
export const SimpleView = ({item}:{item: Item}) => {
|
||||
export const SimpleView = ({ item }:{item: Item}) => {
|
||||
return (
|
||||
<div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'>
|
||||
<TextView item={item} />
|
||||
|
||||
@ -1,73 +1,72 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { TextAreaInput } from "../../Input"
|
||||
import { PopupStartEndInput, TextView } from "../../Map"
|
||||
import { ActionButton } from "../Subcomponents/ActionsButton"
|
||||
import { LinkedItemsHeaderView } from "../Subcomponents/LinkedItemsHeaderView"
|
||||
import { TagsWidget } from "../Subcomponents/TagsWidget"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useUpdateItem } from "../../Map/hooks/useItems"
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { TextAreaInput } from '../../Input'
|
||||
import { PopupStartEndInput, TextView } from '../../Map'
|
||||
import { ActionButton } from '../Subcomponents/ActionsButton'
|
||||
import { LinkedItemsHeaderView } from '../Subcomponents/LinkedItemsHeaderView'
|
||||
import { TagsWidget } from '../Subcomponents/TagsWidget'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useUpdateItem } from '../../Map/hooks/useItems'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const TabsForm = ({ item, state, setState, updatePermission, linkItem, unlinkItem, loading, setUrlParams }) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(1)
|
||||
const navigate = useNavigate()
|
||||
const updateItem = useUpdateItem()
|
||||
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
const navigate = useNavigate();
|
||||
const updateItem = useUpdateItem();
|
||||
const updateActiveTab = useCallback((id: number) => {
|
||||
setActiveTab(id)
|
||||
|
||||
const updateActiveTab = useCallback((id: number) => {
|
||||
setActiveTab(id);
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
params.set("tab", `${id}`);
|
||||
const newUrl = location.pathname + "?" + params.toString();
|
||||
window.history.pushState({}, '', newUrl);
|
||||
setUrlParams(params);
|
||||
params.set('tab', `${id}`)
|
||||
const newUrl = location.pathname + '?' + params.toString()
|
||||
window.history.pushState({}, '', newUrl)
|
||||
setUrlParams(params)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.pathname]);
|
||||
}, [location.pathname])
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const urlTab = params.get("tab");
|
||||
setActiveTab(urlTab ? Number(urlTab) : 1);
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search)
|
||||
const urlTab = params.get('tab')
|
||||
setActiveTab(urlTab ? Number(urlTab) : 1)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.search]);
|
||||
}, [location.search])
|
||||
|
||||
return (
|
||||
return (
|
||||
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-3">
|
||||
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label="Info" checked={activeTab == 1 && true} onChange={() => updateActiveTab(1)} />
|
||||
<input type="radio" name="my_tabs_2" role="tab" className={'tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'} aria-label="Info" checked={activeTab === 1 && true} onChange={() => updateActiveTab(1)} />
|
||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-border-[var(--fallback-bc,oklch(var(--bc)/0.2))] tw-rounded-box tw-h-[calc(100dvh-332px)] tw-min-h-56 tw-border-none">
|
||||
<div className={`tw-flex tw-flex-col tw-h-full ${item.layer.itemType.show_start_end_input && "tw-pt-4"}`}>
|
||||
<div className={`tw-flex tw-flex-col tw-h-full ${item.layer.itemType.show_start_end_input && 'tw-pt-4'}`}>
|
||||
{item.layer.itemType.show_start_end_input &&
|
||||
<PopupStartEndInput
|
||||
item={item}
|
||||
showLabels={false}
|
||||
updateEndValue={(e) => setState(prevState => ({
|
||||
...prevState,
|
||||
end: e
|
||||
...prevState,
|
||||
end: e
|
||||
}))}
|
||||
updateStartValue={(s) => setState(prevState => ({
|
||||
...prevState,
|
||||
start: s
|
||||
...prevState,
|
||||
start: s
|
||||
}))}></PopupStartEndInput>
|
||||
}
|
||||
|
||||
<TextAreaInput placeholder="about ..."
|
||||
defaultValue={item?.text ? item.text : ""}
|
||||
defaultValue={item?.text ? item.text : ''}
|
||||
updateFormValue={(v) => setState(prevState => ({
|
||||
...prevState,
|
||||
text: v
|
||||
...prevState,
|
||||
text: v
|
||||
}))}
|
||||
containerStyle='tw-grow'
|
||||
inputStyle={`tw-h-full ${!item.layer.itemType.show_start_end_input && "tw-border-t-0 tw-rounded-tl-none"}`} />
|
||||
inputStyle={`tw-h-full ${!item.layer.itemType.show_start_end_input && 'tw-border-t-0 tw-rounded-tl-none'}`} />
|
||||
<div>
|
||||
<TextAreaInput
|
||||
placeholder="contact info ..."
|
||||
defaultValue={state.contact || ""}
|
||||
defaultValue={state.contact || ''}
|
||||
updateFormValue={(c) => setState(prevState => ({
|
||||
...prevState,
|
||||
contact: c
|
||||
...prevState,
|
||||
contact: c
|
||||
}))}
|
||||
inputStyle="tw-h-24"
|
||||
containerStyle="tw-pt-4"
|
||||
@ -77,19 +76,19 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
|
||||
</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)} />
|
||||
<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
|
||||
...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
|
||||
...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>
|
||||
@ -98,13 +97,12 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
|
||||
}
|
||||
{item.layer?.itemType.relations &&
|
||||
<>
|
||||
<input type="radio" name="my_tabs_2" role="tab" className="tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label="Relations" checked={activeTab == 7 && true} onChange={() => updateActiveTab(7)} />
|
||||
<input type="radio" name="my_tabs_2" role="tab" className="tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label="Relations" checked={activeTab === 7 && true} onChange={() => updateActiveTab(7)} />
|
||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-332px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mx-4 tw-overflow-x-hidden fade">
|
||||
<div 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'>
|
||||
@ -120,7 +118,6 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,52 +12,50 @@ import { timeAgo } from '../../../Utils/TimeAgo'
|
||||
|
||||
// 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> }) => {
|
||||
const addFilterTag = useAddFilterTag()
|
||||
const [activeTab, setActiveTab] = useState<number>()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const addFilterTag = useAddFilterTag();
|
||||
const [activeTab, setActiveTab] = useState<number>();
|
||||
const navigate = useNavigate();
|
||||
const [addItemPopupType] = useState<string>('')
|
||||
|
||||
const [addItemPopupType, /* setAddItemPopupType */] = useState<string>("");
|
||||
|
||||
const items = useItems();
|
||||
const assetsApi = useAssetApi();
|
||||
const items = useItems()
|
||||
const assetsApi = useAssetApi()
|
||||
const getUserProfile = (id: string) => {
|
||||
return items.find(i => i.user_created.id === id && i.layer?.itemType.name === userType)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
scroll();
|
||||
scroll()
|
||||
}, [addItemPopupType])
|
||||
|
||||
function scroll() {
|
||||
tabRef.current?.scrollIntoView();
|
||||
function scroll () {
|
||||
tabRef.current?.scrollIntoView()
|
||||
}
|
||||
|
||||
const tabRef = useRef<HTMLFormElement>(null);
|
||||
const tabRef = useRef<HTMLFormElement>(null)
|
||||
|
||||
const updateActiveTab = useCallback((id: number) => {
|
||||
setActiveTab(id);
|
||||
setActiveTab(id)
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set("tab", `${id}`);
|
||||
const newUrl = location.pathname + "?" + params.toString();
|
||||
window.history.pushState({}, '', newUrl);
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
params.set('tab', `${id}`)
|
||||
const newUrl = location.pathname + '?' + params.toString()
|
||||
window.history.pushState({}, '', newUrl)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.pathname]);
|
||||
|
||||
}, [location.pathname])
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const urlTab = params.get("tab");
|
||||
setActiveTab(urlTab ? Number(urlTab) : 1);
|
||||
const params = new URLSearchParams(location.search)
|
||||
const urlTab = params.get('tab')
|
||||
setActiveTab(urlTab ? Number(urlTab) : 1)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.search]);
|
||||
}, [location.search])
|
||||
|
||||
return (
|
||||
<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"
|
||||
className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`}
|
||||
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab != 1 ? "📝" : "📝\u00A0Info"}`} checked={activeTab == 1 && true}
|
||||
className={'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
|
||||
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 1 ? '📝' : '📝\u00A0Info'}`} checked={activeTab === 1 && true}
|
||||
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">
|
||||
@ -71,15 +69,15 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
|
||||
{item.layer?.itemType.questlog &&
|
||||
<>
|
||||
<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 != 2 ? "❤️" : "❤️\u00A0Credibility"}`} checked={activeTab == 2 && true}
|
||||
className={'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'}
|
||||
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Credibility'}`} checked={activeTab === 2 && true}
|
||||
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>
|
||||
{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())
|
||||
.map((a, i) => (
|
||||
<tr key={i}>
|
||||
@ -92,7 +90,7 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
|
||||
<div className='tw-mr-2'><i>{a.text}</i></div>
|
||||
</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="tw-avatar">
|
||||
<div className="tw-mask tw-rounded-full h-8 w-8 tw-mr-2">
|
||||
@ -121,13 +119,13 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
|
||||
|
||||
<>
|
||||
|
||||
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 ${!(item.layer?.itemType.icon_as_labels && activeTab != 3) && "tw-min-w-[10.4em]"} [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab != 3 ? "♻️" : "♻️\u00A0Offers & Needs"}`} checked={activeTab == 3 && true} onChange={() => updateActiveTab(3)} />
|
||||
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 ${!(item.layer?.itemType.icon_as_labels && activeTab !== 3) && 'tw-min-w-[10.4em]'} [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 3 ? '♻️' : '♻️\u00A0Offers & Needs'}`} checked={activeTab === 3 && true} onChange={() => updateActiveTab(3)} />
|
||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-overflow-y-auto fade tw-pt-4 tw-pb-1" >
|
||||
<div className='tw-h-full'>
|
||||
<div className='tw-grid tw-grid-cols-1'>
|
||||
{
|
||||
offers.length > 0 ?
|
||||
<div className='tw-col-span-1'>
|
||||
offers.length > 0
|
||||
? <div className='tw-col-span-1'>
|
||||
<h3 className='-tw-mb-2'>Offers</h3>
|
||||
< div className='tw-flex tw-flex-wrap tw-mb-4'>
|
||||
{
|
||||
@ -136,36 +134,36 @@ export const TabsView = ({ attestations, userType, item, offers, needs, relation
|
||||
}} />)
|
||||
}
|
||||
</div>
|
||||
</div> : ""
|
||||
</div>
|
||||
: ''
|
||||
}
|
||||
{
|
||||
needs.length > 0 ?
|
||||
<div className='tw-col-span-1'>
|
||||
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>
|
||||
</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)} />
|
||||
<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'>
|
||||
|
||||
@ -1,77 +1,64 @@
|
||||
import * as React from 'react'
|
||||
import { MapOverlayPage } from '../Templates'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useState } from 'react';
|
||||
import { UserItem } from '../../types';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useAuth } from '../Auth';
|
||||
import { TextInput } from '../Input';
|
||||
import { useState } from 'react'
|
||||
import { UserItem } from '../../types'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useAuth } from '../Auth'
|
||||
import { TextInput } from '../Input'
|
||||
|
||||
export function UserSettings() {
|
||||
const { user, updateUser, loading, /* token */ } = useAuth();
|
||||
export function UserSettings () {
|
||||
const { user, updateUser, loading /* token */ } = useAuth()
|
||||
|
||||
const [id, setId] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [id, setId] = useState<string>('')
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const [password, setPassword] = useState<string>('')
|
||||
|
||||
const [passwordChanged, setPasswordChanged] = useState<boolean>(false)
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [passwordChanged, setPasswordChanged] = useState<boolean>(false);
|
||||
React.useEffect(() => {
|
||||
setId(user?.id ? user.id : '')
|
||||
setEmail(user?.email ? user.email : '')
|
||||
setPassword(user?.password ? user.password : '')
|
||||
}, [user])
|
||||
|
||||
const onUpdateUser = () => {
|
||||
let changedUser = {} as UserItem
|
||||
|
||||
changedUser = { id, email, ...passwordChanged && { password } }
|
||||
|
||||
const navigate = useNavigate();
|
||||
toast.promise(
|
||||
|
||||
React.useEffect(() => {
|
||||
setId(user?.id ? user.id : "");
|
||||
setEmail(user?.email ? user.email : "");
|
||||
setPassword(user?.password ? user.password : "");
|
||||
}, [user])
|
||||
updateUser(changedUser),
|
||||
{
|
||||
pending: 'updating Profile ...',
|
||||
success: 'Profile updated',
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => navigate('/'))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const onUpdateUser = () => {
|
||||
let changedUser = {} as UserItem;
|
||||
|
||||
changedUser = { id: id, email: email, ...passwordChanged && { password: password } };
|
||||
|
||||
|
||||
toast.promise(
|
||||
|
||||
updateUser(changedUser),
|
||||
{
|
||||
pending: 'updating Profile ...',
|
||||
success: 'Profile updated',
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
},
|
||||
})
|
||||
.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'>
|
||||
<div className={`tw-text-xl tw-font-semibold`}>Settings</div>
|
||||
<div className={'tw-text-xl tw-font-semibold'}>Settings</div>
|
||||
<div className="tw-divider tw-mt-2"></div>
|
||||
<div className="tw-grid tw-grid-cols-1 tw-gap-6">
|
||||
<TextInput type='email' placeholder="new E-Mail" defaultValue={user?.email ? user.email : ""} updateFormValue={(v) => setEmail(v)} />
|
||||
<TextInput type='password' placeholder="new Password" defaultValue={user?.password ? user.password : ""} updateFormValue={(v) => {
|
||||
setPassword(v);
|
||||
setPasswordChanged(true);
|
||||
<TextInput type='email' placeholder="new E-Mail" defaultValue={user?.email ? user.email : ''} updateFormValue={(v) => setEmail(v)} />
|
||||
<TextInput type='password' placeholder="new Password" defaultValue={user?.password ? user.password : ''} updateFormValue={(v) => {
|
||||
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 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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,35 +1,35 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect } from 'react'
|
||||
|
||||
// Improved version of https://usehooks.com/useOnClickOutside/
|
||||
const useClickOutside = (ref, handler) => {
|
||||
useEffect(() => {
|
||||
let startedInside = false;
|
||||
let startedWhenMounted = false;
|
||||
let startedInside = false
|
||||
let startedWhenMounted = false
|
||||
|
||||
const listener = (event) => {
|
||||
// Do nothing if `mousedown` or `touchstart` started inside ref element
|
||||
if (startedInside || !startedWhenMounted) return;
|
||||
if (startedInside || !startedWhenMounted) return
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
if (!ref.current || ref.current.contains(event.target)) return;
|
||||
if (!ref.current || ref.current.contains(event.target)) return
|
||||
|
||||
handler(event);
|
||||
};
|
||||
handler(event)
|
||||
}
|
||||
|
||||
const validateEventStart = (event) => {
|
||||
startedWhenMounted = ref.current;
|
||||
startedInside = ref.current && ref.current.contains(event.target);
|
||||
};
|
||||
startedWhenMounted = ref.current
|
||||
startedInside = ref.current && ref.current.contains(event.target)
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", validateEventStart);
|
||||
document.addEventListener("touchstart", validateEventStart);
|
||||
document.addEventListener("click", listener);
|
||||
document.addEventListener('mousedown', validateEventStart)
|
||||
document.addEventListener('touchstart', validateEventStart)
|
||||
document.addEventListener('click', listener)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", validateEventStart);
|
||||
document.removeEventListener("touchstart", validateEventStart);
|
||||
document.removeEventListener("click", listener);
|
||||
};
|
||||
}, [ref, handler]);
|
||||
};
|
||||
document.removeEventListener('mousedown', validateEventStart)
|
||||
document.removeEventListener('touchstart', validateEventStart)
|
||||
document.removeEventListener('click', listener)
|
||||
}
|
||||
}, [ref, handler])
|
||||
}
|
||||
|
||||
export default useClickOutside;
|
||||
export default useClickOutside
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export {UserSettings} from './UserSettings'
|
||||
export {PlusButton} from "./Subcomponents/PlusButton"
|
||||
export {ProfileView} from "./ProfileView"
|
||||
export {ProfileForm} from "./ProfileForm"
|
||||
export { UserSettings } from './UserSettings'
|
||||
export { PlusButton } from './Subcomponents/PlusButton'
|
||||
export { ProfileView } from './ProfileView'
|
||||
export { ProfileForm } from './ProfileForm'
|
||||
|
||||
@ -1,210 +1,211 @@
|
||||
import * as React from 'react'
|
||||
import { Item } from '../../types';
|
||||
import { encodeTag } from '../../Utils/FormatTags';
|
||||
import { hashTagRegex } from '../../Utils/HashTagRegex';
|
||||
import { randomColor } from '../../Utils/RandomColor';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Item } from '../../types'
|
||||
import { encodeTag } from '../../Utils/FormatTags'
|
||||
import { hashTagRegex } from '../../Utils/HashTagRegex'
|
||||
import { randomColor } from '../../Utils/RandomColor'
|
||||
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) => {
|
||||
evt.preventDefault();
|
||||
const formItem: Item = {} as Item;
|
||||
Array.from(evt.target).forEach((input: HTMLInputElement) => {
|
||||
if (input.name) {
|
||||
formItem[input.name] = input.value;
|
||||
}
|
||||
});
|
||||
setLoading(true);
|
||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
||||
}
|
||||
});
|
||||
const uuid = crypto.randomUUID();
|
||||
|
||||
const layer = layers.find(l => l.name.toLocaleLowerCase().replace("s", "") == addItemPopupType.toLocaleLowerCase())
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await layer?.api?.createItem!({ ...formItem, id: uuid, type: type, parent: item.id });
|
||||
await linkItem(uuid);
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
evt.preventDefault()
|
||||
const formItem: Item = {} as Item
|
||||
Array.from(evt.target).forEach((input: HTMLInputElement) => {
|
||||
if (input.name) {
|
||||
formItem[input.name] = input.value
|
||||
}
|
||||
if (success) {
|
||||
addItem({ ...formItem, id: uuid, type: type, layer: layer, user_created: user, parent: item.id });
|
||||
toast.success("New item created");
|
||||
resetFilterTags();
|
||||
})
|
||||
setLoading(true)
|
||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() })
|
||||
}
|
||||
setLoading(false);
|
||||
setAddItemPopupType("");
|
||||
return null
|
||||
})
|
||||
const uuid = crypto.randomUUID()
|
||||
|
||||
const layer = layers.find(l => l.name.toLocaleLowerCase().replace('s', '') === addItemPopupType.toLocaleLowerCase())
|
||||
|
||||
let success = false
|
||||
try {
|
||||
await layer?.api?.createItem!({ ...formItem, id: uuid, type, parent: item.id })
|
||||
await linkItem(uuid)
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
addItem({ ...formItem, id: uuid, type, layer, user_created: user, parent: item.id })
|
||||
toast.success('New item created')
|
||||
resetFilterTags()
|
||||
}
|
||||
setLoading(false)
|
||||
setAddItemPopupType('')
|
||||
}
|
||||
|
||||
export const linkItem = async (id: string, item, updateItem) => {
|
||||
const new_relations = item.relations || [];
|
||||
new_relations?.push({ items_id: item.id, related_items_id: id })
|
||||
const updatedItem = { id: item.id, relations: new_relations }
|
||||
const newRelations = item.relations || []
|
||||
newRelations?.push({ items_id: item.id, related_items_id: id })
|
||||
const updatedItem = { id: item.id, relations: newRelations }
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await item?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
updateItem({ ...item, relations: new_relations })
|
||||
toast.success("Item linked");
|
||||
}
|
||||
let success = false
|
||||
try {
|
||||
await item?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
updateItem({ ...item, relations: newRelations })
|
||||
toast.success('Item linked')
|
||||
}
|
||||
}
|
||||
|
||||
export const unlinkItem = async (id: string, item, updateItem) => {
|
||||
const new_relations = item.relations?.filter(r => r.related_items_id !== id)
|
||||
const updatedItem = { id: item.id, relations: new_relations }
|
||||
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await item?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
updateItem({ ...item, relations: new_relations })
|
||||
toast.success("Item unlinked");
|
||||
}
|
||||
const newRelations = item.relations?.filter(r => r.related_items_id !== id)
|
||||
const updatedItem = { id: item.id, relations: newRelations }
|
||||
|
||||
let success = false
|
||||
try {
|
||||
await item?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
updateItem({ ...item, relations: newRelations })
|
||||
toast.success('Item unlinked')
|
||||
}
|
||||
}
|
||||
|
||||
export const handleDelete = async (event: React.MouseEvent<HTMLElement>, item, setLoading, removeItem, map, navigate) => {
|
||||
event.stopPropagation();
|
||||
setLoading(true);
|
||||
let success = false;
|
||||
try {
|
||||
await item.layer?.api?.deleteItem!(item.id)
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
removeItem(item);
|
||||
toast.success("Item deleted");
|
||||
}
|
||||
setLoading(false);
|
||||
map.closePopup();
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
window.history.pushState({}, "", "/" + `${params ? `?${params}` : ""}`);
|
||||
navigate("/");
|
||||
event.stopPropagation()
|
||||
setLoading(true)
|
||||
let success = false
|
||||
try {
|
||||
await item.layer?.api?.deleteItem!(item.id)
|
||||
success = true
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
removeItem(item)
|
||||
toast.success('Item deleted')
|
||||
}
|
||||
setLoading(false)
|
||||
map.closePopup()
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
window.history.pushState({}, '', '/' + `${params ? `?${params}` : ''}`)
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
|
||||
export const onUpdateItem = async (state, item, tags, addTag, setLoading, navigate, updateItem, addItem, user, params) => {
|
||||
let changedItem = {} as Item;
|
||||
let changedItem = {} as Item
|
||||
|
||||
const offer_updates: Array<any> = [];
|
||||
//check for new offers
|
||||
await state.offers?.map(o => {
|
||||
const existingOffer = item?.offers?.find(t => t.tags_id === o.id)
|
||||
existingOffer && offer_updates.push(existingOffer.id)
|
||||
if (!existingOffer && !tags.some(t => t.id === o.id)) addTag({ ...o, offer_or_need: true })
|
||||
!existingOffer && offer_updates.push({ items_id: item?.id, tags_id: o.id })
|
||||
});
|
||||
const offerUpdates: Array<any> = []
|
||||
// check for new offers
|
||||
await state.offers?.map(o => {
|
||||
const existingOffer = item?.offers?.find(t => t.tags_id === o.id)
|
||||
existingOffer && offerUpdates.push(existingOffer.id)
|
||||
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 })
|
||||
return null
|
||||
})
|
||||
|
||||
const needs_updates: Array<any> = [];
|
||||
const needsUpdates: Array<any> = []
|
||||
|
||||
await state.needs?.map(n => {
|
||||
const existingNeed = item?.needs?.find(t => t.tags_id === n.id)
|
||||
existingNeed && needs_updates.push(existingNeed.id)
|
||||
!existingNeed && needs_updates.push({ items_id: item?.id, tags_id: n.id })
|
||||
!existingNeed && !tags.some(t => t.id === n.id) && addTag({ ...n, offer_or_need: true })
|
||||
});
|
||||
await state.needs?.map(n => {
|
||||
const existingNeed = item?.needs?.find(t => t.tags_id === n.id)
|
||||
existingNeed && needsUpdates.push(existingNeed.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 })
|
||||
return null
|
||||
})
|
||||
|
||||
// update profile item in current state
|
||||
changedItem = {
|
||||
id: state.id,
|
||||
name: state.name,
|
||||
subname: state.subname,
|
||||
text: state.text,
|
||||
...state.color && {color: state.color},
|
||||
position: item.position,
|
||||
...state.groupType && {group_type: state.groupType},
|
||||
...state.status && {status: state.status},
|
||||
contact: state.contact,
|
||||
telephone: state.telephone,
|
||||
...state.end && {end: state.end},
|
||||
...state.start && {start: state.start},
|
||||
...state.markerIcon && { markerIcon: state.markerIcon },
|
||||
next_appointment: state.nextAppointment,
|
||||
...state.image.length > 10 && { image: state.image },
|
||||
...state.offers.length > 0 && { offers: offer_updates },
|
||||
...state.needs.length > 0 && { needs: needs_updates }
|
||||
};
|
||||
// update profile item in current state
|
||||
changedItem = {
|
||||
id: state.id,
|
||||
name: state.name,
|
||||
subname: state.subname,
|
||||
text: state.text,
|
||||
...state.color && { color: state.color },
|
||||
position: item.position,
|
||||
...state.groupType && { group_type: state.groupType },
|
||||
...state.status && { status: state.status },
|
||||
contact: state.contact,
|
||||
telephone: state.telephone,
|
||||
...state.end && { end: state.end },
|
||||
...state.start && { start: state.start },
|
||||
...state.markerIcon && { markerIcon: state.markerIcon },
|
||||
next_appointment: state.nextAppointment,
|
||||
...state.image.length > 10 && { image: state.image },
|
||||
...state.offers.length > 0 && { offers: offerUpdates },
|
||||
...state.needs.length > 0 && { needs: needsUpdates }
|
||||
}
|
||||
|
||||
const offers_state: Array<any> = [];
|
||||
const needs_state: Array<any> = [];
|
||||
const offersState: Array<any> = []
|
||||
const needsState: Array<any> = []
|
||||
|
||||
state.offers.map(o => {
|
||||
offers_state.push({ items_id: item?.id, tags_id: o.id })
|
||||
});
|
||||
state.offers.map(o => {
|
||||
offersState.push({ items_id: item?.id, tags_id: o.id })
|
||||
return null
|
||||
})
|
||||
|
||||
state.needs.map(n => {
|
||||
needs_state.push({ items_id: item?.id, tags_id: n.id })
|
||||
});
|
||||
state.needs.map(n => {
|
||||
needsState.push({ items_id: item?.id, tags_id: n.id })
|
||||
return null
|
||||
})
|
||||
|
||||
changedItem = { ...changedItem, offers: offers_state, needs: needs_state };
|
||||
changedItem = { ...changedItem, offers: offersState, needs: needsState }
|
||||
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
|
||||
await state.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
addTag({ id: crypto.randomUUID(), name: encodeTag(tag.slice(1).toLocaleLowerCase()), color: randomColor() })
|
||||
await state.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
||||
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
|
||||
await sleep(200)
|
||||
|
||||
if (!item.new) {
|
||||
item?.layer?.api?.updateItem && toast.promise(
|
||||
item?.layer?.api?.updateItem(changedItem),
|
||||
{
|
||||
pending: 'updating Item ...',
|
||||
success: 'Item updated',
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//take care that addTag request comes before item request
|
||||
await sleep(200);
|
||||
|
||||
if (!item.new) {
|
||||
item?.layer?.api?.updateItem && toast.promise(
|
||||
item?.layer?.api?.updateItem(changedItem),
|
||||
{
|
||||
pending: 'updating Item ...',
|
||||
success: 'Item updated',
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch(setLoading(false))
|
||||
.then(() => item && updateItem({ ...item, ...changedItem }))
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
navigate(`/item/${item.id}${params && "?" + params}`)
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
item.new = false;
|
||||
item.layer?.api?.createItem && toast.promise(
|
||||
item.layer?.api?.createItem(changedItem),
|
||||
{
|
||||
pending: 'updating Item ...',
|
||||
success: 'Item updated',
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch(setLoading(false))
|
||||
.then(() => item && addItem({ ...item, ...changedItem, layer: item.layer, user_created: user, type: item.layer?.itemType }))
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
navigate(`/${params && "?" + params}`)
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(setLoading(false))
|
||||
.then(() => item && updateItem({ ...item, ...changedItem }))
|
||||
.then(() => {
|
||||
setLoading(false)
|
||||
navigate(`/item/${item.id}${params && '?' + params}`)
|
||||
})
|
||||
} else {
|
||||
item.new = false
|
||||
item.layer?.api?.createItem && toast.promise(
|
||||
item.layer?.api?.createItem(changedItem),
|
||||
{
|
||||
pending: 'updating Item ...',
|
||||
success: 'Item updated',
|
||||
error: {
|
||||
render ({ data }) {
|
||||
return `${data}`
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(setLoading(false))
|
||||
.then(() => item && addItem({ ...item, ...changedItem, layer: item.layer, user_created: user, type: item.layer?.itemType }))
|
||||
.then(() => {
|
||||
setLoading(false)
|
||||
navigate(`/${params && '?' + params}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,90 +2,86 @@ import * as React from 'react'
|
||||
import { MapOverlayPage } from './MapOverlayPage'
|
||||
import { useItems } from '../Map/hooks/useItems'
|
||||
import { useAssetApi } from '../AppShell/hooks/useAssets'
|
||||
import { EmojiPicker } from './EmojiPicker';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Item, ItemsApi } from '../../types';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { EmojiPicker } from './EmojiPicker'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { Item, ItemsApi } from '../../types'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
export const AttestationForm = ({api}:{api?:ItemsApi<any>}) => {
|
||||
export const AttestationForm = ({ api }:{api?:ItemsApi<any>}) => {
|
||||
const items = useItems()
|
||||
const assetsApi = useAssetApi()
|
||||
const [users, setUsers] = useState<Array<Item>>()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const items = useItems();
|
||||
const assetsApi = useAssetApi();
|
||||
const [users, setUsers] = useState<Array<Item>>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const to_user_ids = params.get("to");
|
||||
setUsers(items.filter(i => to_user_ids?.includes(i.id)))
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search)
|
||||
const toUserIds = params.get('to')
|
||||
setUsers(items.filter(i => toUserIds?.includes(i.id)))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [items, location])
|
||||
}, [items, location])
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.style.width = 'auto';
|
||||
inputRef.current.style.width = `${inputRef.current.scrollWidth+20}px`;
|
||||
}
|
||||
}, [inputValue]);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
};
|
||||
|
||||
const sendAttestation = async () => {
|
||||
const to : Array<any> = [];
|
||||
users?.map(u => to.push({ directus_users_id: u.user_created.id }));
|
||||
|
||||
|
||||
api?.createItem && toast.promise(
|
||||
api.createItem({
|
||||
text: inputValue,
|
||||
emoji: selectedEmoji,
|
||||
color: selectedColor,
|
||||
shape: selectedShape,
|
||||
to: to
|
||||
}), {
|
||||
pending: 'creating attestation ...',
|
||||
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")
|
||||
)
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.style.width = 'auto'
|
||||
inputRef.current.style.width = `${inputRef.current.scrollWidth + 20}px`
|
||||
}
|
||||
}, [inputValue])
|
||||
|
||||
const [selectedEmoji, setSelectedEmoji] = useState('select badge');
|
||||
const [selectedShape, setSelectedShape] = useState('circle');
|
||||
const [selectedColor, setSelectedColor] = useState('#fff0d6');
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value)
|
||||
}
|
||||
|
||||
const sendAttestation = async () => {
|
||||
const to : Array<any> = []
|
||||
users?.map(u => to.push({ directus_users_id: u.user_created.id }))
|
||||
|
||||
return (
|
||||
api?.createItem && toast.promise(
|
||||
api.createItem({
|
||||
text: inputValue,
|
||||
emoji: selectedEmoji,
|
||||
color: selectedColor,
|
||||
shape: selectedShape,
|
||||
to
|
||||
}), {
|
||||
pending: 'creating attestation ...',
|
||||
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')
|
||||
)
|
||||
}
|
||||
|
||||
const [selectedEmoji, setSelectedEmoji] = useState('select badge')
|
||||
const [selectedShape, setSelectedShape] = useState('circle')
|
||||
const [selectedColor, setSelectedColor] = useState('#fff0d6')
|
||||
|
||||
return (
|
||||
<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-base tw-text-gray-400'>to</div>
|
||||
<div className='tw-flex tw-flex-row tw-justify-center tw-items-center tw-flex-wrap'>
|
||||
{users && users.map((u, k) => (
|
||||
<div key={k} className="tw-flex tw-items-center tw-space-x-3 tw-mx-2 tw-my-1">
|
||||
{u.image ? <div className="tw-avatar">
|
||||
{u.image
|
||||
? <div className="tw-avatar">
|
||||
<div className="tw-mask tw-mask-circle tw-w-8 tw-h-8">
|
||||
<img src={assetsApi.url + u.image + "?width=40&heigth=40"} alt="Avatar" />
|
||||
<img src={assetsApi.url + u.image + '?width=40&heigth=40'} alt="Avatar" />
|
||||
</div>
|
||||
</div> :
|
||||
<div 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-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'>
|
||||
@ -105,5 +101,5 @@ export const AttestationForm = ({api}:{api?:ItemsApi<any>}) => {
|
||||
</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>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,23 +1,20 @@
|
||||
import { Link } from "react-router-dom"
|
||||
import * as React from "react"
|
||||
import {TitleCard} from "./TitleCard"
|
||||
import { Link } from 'react-router-dom'
|
||||
import * as React from 'react'
|
||||
import { TitleCard } from './TitleCard'
|
||||
|
||||
|
||||
export function CardPage({title, hideTitle, children, parents} : {
|
||||
export function CardPage ({ title, hideTitle, children, parents } : {
|
||||
title: string,
|
||||
hideTitle?: boolean,
|
||||
children?: React.ReactNode,
|
||||
parents?: Array<{name: string, path: string}>
|
||||
}) {
|
||||
|
||||
|
||||
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" >
|
||||
<div className='tw-w-full xl:tw-max-w-6xl '>
|
||||
<div className="tw-text-sm tw-breadcrumbs">
|
||||
<ul>
|
||||
<li><Link to={'/'} >Home</Link></li>
|
||||
{parents?.map((b,i) => <li key={i}><Link to={b.path} >{b.name}</Link></li>)}
|
||||
{parents?.map((b, i) => <li key={i}><Link to={b.path} >{b.name}</Link></li>)}
|
||||
<li>{title}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -1,33 +1,32 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export const CircleLayout = ({ items,radius, fontSize } : {items: any, radius: number, fontSize: any}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
export const CircleLayout = ({ items, radius, fontSize } : {items: any, radius: number, fontSize: any}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
const itemCount = items.length;
|
||||
const container = containerRef.current
|
||||
const itemCount = items.length
|
||||
|
||||
if (container) {
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
const startAngle = (Math.PI) / 2;
|
||||
const angle = startAngle + (i / itemCount) * (2 * Math.PI);
|
||||
const x = radius * Math.cos(angle);
|
||||
const y = radius * Math.sin(angle);
|
||||
const child = container.children[i] as HTMLElement;
|
||||
child.style.transform = `translate(${x}px, ${y}px)`;
|
||||
const startAngle = (Math.PI) / 2
|
||||
const angle = startAngle + (i / itemCount) * (2 * Math.PI)
|
||||
const x = radius * Math.cos(angle)
|
||||
const y = radius * Math.sin(angle)
|
||||
const child = container.children[i] as HTMLElement
|
||||
child.style.transform = `translate(${x}px, ${y}px)`
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [items]);
|
||||
}, [items])
|
||||
|
||||
return (
|
||||
<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) => (
|
||||
<div key={item} className="tw-absolute" style={{fontSize: fontSize}}>
|
||||
<div key={item} className="tw-absolute" style={{ fontSize }}>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
import { useState } from "react";
|
||||
import { timeAgo } from "../../Utils/TimeAgo";
|
||||
import { Item } from "../../types";
|
||||
import { useState } from 'react'
|
||||
import { timeAgo } from '../../Utils/TimeAgo'
|
||||
import { Item } from '../../types'
|
||||
|
||||
export const DateUserInfo = ({ item }: { item: Item }) => {
|
||||
const [infoExpanded, setInfoExpanded] = useState<boolean>(false);
|
||||
return (
|
||||
const [infoExpanded, setInfoExpanded] = useState<boolean>(false)
|
||||
return (
|
||||
<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>
|
||||
:
|
||||
<p className="!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500" onClick={() => setInfoExpanded(true)}>ⓘ</p>
|
||||
infoExpanded
|
||||
? <p className={'tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'} onClick={() => setInfoExpanded(false)} >{`${item.date_updated && item.date_updated !== item.date_created ? 'updated' : 'posted'} ${item && item.user_created && item.user_created.first_name ? `by ${item.user_created.first_name}` : ''} ${item.date_updated ? timeAgo(item.date_updated) : timeAgo(item.date_created!)}`}</p>
|
||||
: <p className="!tw-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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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