mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
refactored tags and offers & needs implemented
This commit is contained in:
parent
c6be6abb84
commit
1583e55e7e
@ -1,5 +1,7 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { decodeTag } from '../../Utils/FormatTags';
|
||||||
|
import { TagView } from '../Templates/TagView';
|
||||||
|
|
||||||
export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFilteredSuggestions, setFocus }: { inputProps: any, suggestions: Array<any>, onSelected: (suggestion) => void, pushFilteredSuggestions?: Array<any>, setFocus?: boolean }) => {
|
export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFilteredSuggestions, setFocus }: { inputProps: any, suggestions: Array<any>, onSelected: (suggestion) => void, pushFilteredSuggestions?: Array<any>, setFocus?: boolean }) => {
|
||||||
|
|
||||||
@ -20,14 +22,6 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
|
|
||||||
const getSuggestionValue = suggestion => suggestion.name;
|
const getSuggestionValue = suggestion => suggestion.name;
|
||||||
|
|
||||||
// Use your imagination to render suggestions.
|
|
||||||
const renderSuggestion = suggestion => (
|
|
||||||
<div key={suggestion.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-h-[2.75em] tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit' style={{ backgroundColor: suggestion.color ? suggestion.color : "#666" }}>
|
|
||||||
<div className="tw-card-actions tw-justify-end">
|
|
||||||
</div><b>#{suggestion.name}</b>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const getSuggestions = value => {
|
const getSuggestions = value => {
|
||||||
const inputValue = value.trim().toLowerCase();
|
const inputValue = value.trim().toLowerCase();
|
||||||
const inputLength = inputValue.length;
|
const inputLength = inputValue.length;
|
||||||
@ -52,8 +46,6 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
console.log(filteredSuggestions);
|
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
heighlightedSuggestion < filteredSuggestions.length-1 && setHeighlightedSuggestion(current => current +1)
|
heighlightedSuggestion < filteredSuggestions.length-1 && setHeighlightedSuggestion(current => current +1)
|
||||||
@ -72,7 +64,6 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
inputProps.onKeyDown(event);
|
inputProps.onKeyDown(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -80,7 +71,7 @@ export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFiltered
|
|||||||
<input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} onKeyDown={handleKeyDown}/>
|
<input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} onKeyDown={handleKeyDown}/>
|
||||||
<ul className='tw-absolute tw-z-[4000]'>
|
<ul className='tw-absolute tw-z-[4000]'>
|
||||||
{filteredSuggestions.map((suggestion, index) => (
|
{filteredSuggestions.map((suggestion, index) => (
|
||||||
<li key={index} onClick={() => handleSuggestionClick(suggestion)}>{renderSuggestion(suggestion)}{index == heighlightedSuggestion && "+"}</li>
|
<li key={index} onClick={() => handleSuggestionClick(suggestion)}><TagView tag={suggestion}></TagView>{index == heighlightedSuggestion && "+"}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -33,7 +33,11 @@ export const Layer = ({
|
|||||||
itemOwnerField,
|
itemOwnerField,
|
||||||
itemLatitudeField = 'position.coordinates.1',
|
itemLatitudeField = 'position.coordinates.1',
|
||||||
itemLongitudeField = 'position.coordinates.0',
|
itemLongitudeField = 'position.coordinates.0',
|
||||||
|
itemTagsField,
|
||||||
|
itemOffersField,
|
||||||
|
itemNeedsField,
|
||||||
onlyOnePerOwner = false,
|
onlyOnePerOwner = false,
|
||||||
|
customEditLink,
|
||||||
setItemFormPopup,
|
setItemFormPopup,
|
||||||
itemFormPopup,
|
itemFormPopup,
|
||||||
clusterRef
|
clusterRef
|
||||||
@ -66,8 +70,8 @@ export const Layer = ({
|
|||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, onlyOnePerOwner, setItemFormPopup, itemFormPopup, clusterRef });
|
data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, setItemFormPopup, itemFormPopup, clusterRef });
|
||||||
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, onlyOnePerOwner, setItemFormPopup, itemFormPopup, clusterRef });
|
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, setItemFormPopup, itemFormPopup, clusterRef });
|
||||||
}, [data, api])
|
}, [data, api])
|
||||||
|
|
||||||
useMapEvents({
|
useMapEvents({
|
||||||
@ -139,9 +143,9 @@ export const Layer = ({
|
|||||||
map((item: Item) => {
|
map((item: Item) => {
|
||||||
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
||||||
|
|
||||||
|
|
||||||
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField);
|
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField);
|
||||||
else item[itemTextField] = "";
|
else item[itemTextField] = "";
|
||||||
|
|
||||||
if (item?.tags) {
|
if (item?.tags) {
|
||||||
item[itemTextField] = item[itemTextField] + '\n\n';
|
item[itemTextField] = item[itemTextField] + '\n\n';
|
||||||
item.tags.map(tag => {
|
item.tags.map(tag => {
|
||||||
@ -154,9 +158,9 @@ export const Layer = ({
|
|||||||
|
|
||||||
|
|
||||||
if (allTagsLoaded && allItemsLoaded) {
|
if (allTagsLoaded && allItemsLoaded) {
|
||||||
item[itemTextField].toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
item[itemTextField].match(hashTagRegex)?.map(tag => {
|
||||||
if ((!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) && !newTagsToAdd.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
if ((!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) && !newTagsToAdd.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
const newTag = { id: crypto.randomUUID(), name: tag.slice(1).toLocaleLowerCase(), color: randomColor() };
|
const newTag = { id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() };
|
||||||
setNewTagsToAdd(current => [...current, newTag]);
|
setNewTagsToAdd(current => [...current, newTag]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter';
|
import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter';
|
||||||
|
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||||
|
|
||||||
export const TagsControl = () => {
|
export const TagsControl = () => {
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ export const TagsControl = () => {
|
|||||||
<div key={tag.id} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mr-2 tw-mb-2' style={{ backgroundColor: tag.color }}>
|
<div key={tag.id} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mr-2 tw-mb-2' style={{ backgroundColor: tag.color }}>
|
||||||
<div className="tw-card-actions tw-justify-end">
|
<div className="tw-card-actions tw-justify-end">
|
||||||
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (removeFilterTag(tag.name!))}>✕</label>
|
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (removeFilterTag(tag.name!))}>✕</label>
|
||||||
</div><b>#{formatTag(tag.name)}</b>
|
</div><b>#{decodeTag(tag.name)}</b>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -21,7 +22,3 @@ export const TagsControl = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function formatTag(string : string) {
|
|
||||||
let formatedTag = string.replace(/_/g, " ");
|
|
||||||
return formatedTag = formatedTag.charAt(0).toUpperCase() + formatedTag.slice(1);
|
|
||||||
}
|
|
||||||
@ -58,7 +58,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
|||||||
|
|
||||||
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag=> {
|
formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag=> {
|
||||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
addTag({id: crypto.randomUUID(), name: tag.slice(1).toLocaleLowerCase(), color: randomColor()})
|
addTag({id: crypto.randomUUID(), name: tag.slice(1), color: randomColor()})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { fixUrls, mailRegex } from '../../../../Utils/ReplaceURLs';
|
|||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown'
|
||||||
import { getValue } from '../../../../Utils/GetValue';
|
import { getValue } from '../../../../Utils/GetValue';
|
||||||
import remarkBreaks from 'remark-breaks'
|
import remarkBreaks from 'remark-breaks'
|
||||||
|
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||||
|
|
||||||
export const TextView = ({ item, truncate = false}: { item?: Item, truncate?: boolean }) => {
|
export const TextView = ({ item, truncate = false}: { item?: Item, truncate?: boolean }) => {
|
||||||
const tags = useTags();
|
const tags = useTags();
|
||||||
@ -95,7 +96,7 @@ export const TextView = ({ item, truncate = false}: { item?: Item, truncate?: bo
|
|||||||
addFilterTag(tag!);
|
addFilterTag(tag!);
|
||||||
// map.fitBounds(items)
|
// map.fitBounds(items)
|
||||||
// map.closePopup();
|
// map.closePopup();
|
||||||
}}>{children}</a>
|
}}>{decodeTag(children)}</a>
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import AddButton from "./Subcomponents/AddButton";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup";
|
import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup";
|
||||||
import { ItemsProvider } from "./hooks/useItems";
|
import { ItemsProvider } from "./hooks/useItems";
|
||||||
import { TagsProvider, useAllTagsLoaded, useTags } from "./hooks/useTags";
|
import { TagsProvider } from "./hooks/useTags";
|
||||||
import { LayersProvider } from "./hooks/useLayers";
|
import { LayersProvider } from "./hooks/useLayers";
|
||||||
import { FilterProvider, useAddFilterTag } from "./hooks/useFilter";
|
import { FilterProvider } from "./hooks/useFilter";
|
||||||
import { SearchControl } from "./Subcomponents/Controls/SearchControl";
|
import { SearchControl } from "./Subcomponents/Controls/SearchControl";
|
||||||
import { PermissionsProvider } from "./hooks/usePermissions";
|
import { PermissionsProvider } from "./hooks/usePermissions";
|
||||||
import { LeafletRefsProvider } from "./hooks/useLeafletRefs";
|
import { LeafletRefsProvider } from "./hooks/useLeafletRefs";
|
||||||
|
|||||||
@ -61,7 +61,7 @@ function useTagsManager(initialTags: Tag[]): {
|
|||||||
if(tagCount == 0) setallTagsLoaded(true);
|
if(tagCount == 0) setallTagsLoaded(true);
|
||||||
if (result) {
|
if (result) {
|
||||||
result.map(tag => {
|
result.map(tag => {
|
||||||
tag.name = tag.name.toLocaleLowerCase();
|
//tag.name = tag.name.toLocaleLowerCase();
|
||||||
dispatch({ type: "ADD", tag });
|
dispatch({ type: "ADD", tag });
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ function useTagsManager(initialTags: Tag[]): {
|
|||||||
|
|
||||||
const setTagData = useCallback((data: Tag[]) => {
|
const setTagData = useCallback((data: Tag[]) => {
|
||||||
data.map(tag => {
|
data.map(tag => {
|
||||||
tag.name = tag.name.toLocaleLowerCase();
|
//tag.name = tag.name.toLocaleLowerCase();
|
||||||
dispatch({ type: "ADD", tag })
|
dispatch({ type: "ADD", tag })
|
||||||
})
|
})
|
||||||
}, []);
|
}, []);
|
||||||
@ -90,11 +90,11 @@ function useTagsManager(initialTags: Tag[]): {
|
|||||||
|
|
||||||
const getItemTags = useCallback((item: Item) => {
|
const getItemTags = useCallback((item: Item) => {
|
||||||
const text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined;
|
const text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : undefined;
|
||||||
const itemTagStrings = text?.toLocaleLowerCase().match(hashTagRegex);
|
const itemTagStrings = text?.match(hashTagRegex);
|
||||||
const itemTags: Tag[] = [];
|
const itemTags: Tag[] = [];
|
||||||
itemTagStrings?.map(tag => {
|
itemTagStrings?.map(tag => {
|
||||||
if (tags.find(t => t.name === tag.slice(1))) {
|
if (tags.find(t => t.name === tag.slice(1))) {
|
||||||
itemTags.push(tags.find(t => t.name === tag.slice(1))!)
|
itemTags.push(tags.find(t => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return itemTags
|
return itemTags
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { useMap } from 'react-leaflet';
|
|||||||
import { LatLng } from 'leaflet';
|
import { LatLng } from 'leaflet';
|
||||||
import { TextView } from '../Map';
|
import { TextView } from '../Map';
|
||||||
import useWindowDimensions from '../Map/hooks/useWindowDimension';
|
import useWindowDimensions from '../Map/hooks/useWindowDimension';
|
||||||
|
import { TagView } from '../Templates/TagView';
|
||||||
|
import { useTags } from '../Map/hooks/useTags';
|
||||||
|
|
||||||
export function OverlayProfile() {
|
export function OverlayProfile() {
|
||||||
|
|
||||||
@ -18,6 +20,11 @@ export function OverlayProfile() {
|
|||||||
const map = useMap();
|
const map = useMap();
|
||||||
const windowDimension = useWindowDimensions();
|
const windowDimension = useWindowDimensions();
|
||||||
|
|
||||||
|
const tags = useTags();
|
||||||
|
|
||||||
|
console.log(item);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const itemId = location.pathname.split("/")[2];
|
const itemId = location.pathname.split("/")[2];
|
||||||
@ -32,18 +39,46 @@ export function OverlayProfile() {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapOverlayPage className='tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-[calc(100dvh-96px)] md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-max-w-xl !tw-left-auto tw-top-0 tw-bottom-0'>
|
<MapOverlayPage className='tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-[calc(100dvh-96px)] md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-max-w-2xl !tw-left-auto tw-top-0 tw-bottom-0'>
|
||||||
{item &&
|
{item &&
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row tw-w-full">
|
<div className="flex flex-row tw-w-full">
|
||||||
<p className="text-4xl">{item.layer?.itemAvatarField && getValue(item, item.layer.itemAvatarField) && <img className='h-20 rounded-full inline' src={`https://api.utopia-lab.org/assets/${getValue(item, item.layer.itemAvatarField)}?width=160&heigth=160`}></img>} {item.layer?.itemNameField && getValue(item, item.layer.itemNameField)}</p>
|
<p className="text-4xl">{item.layer?.itemAvatarField && getValue(item, item.layer.itemAvatarField) && <img className='h-20 rounded-full inline' src={`https://api.utopia-lab.org/assets/${getValue(item, item.layer.itemAvatarField)}?width=160&heigth=160`}></img>} {item.layer?.itemNameField && getValue(item, item.layer.itemNameField)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='tw-overflow-y-auto tw-h-full tw-pt-4 fade'>
|
<div className='tw-overflow-y-auto tw-h-full tw-pt-4 fade tw-pb-4'>
|
||||||
|
{
|
||||||
|
item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField).length > 0 ?
|
||||||
|
<>
|
||||||
|
<h3 className='-tw-mb-2'>Offers</h3>
|
||||||
|
< div className='tw-flex tw-flex-wrap tw-mb-2'>
|
||||||
|
{
|
||||||
|
item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField).map(o => {
|
||||||
|
const tag = tags.find(t => t.id === o.tags_id);
|
||||||
|
return (tag ? <TagView key={tag?.id} tag={tag} /> : "")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</> : ""
|
||||||
|
}
|
||||||
|
{
|
||||||
|
item.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField).length > 0 ?
|
||||||
|
<>
|
||||||
|
<h3 className='-tw-mb-2'>Needs</h3>
|
||||||
|
< div className='tw-flex tw-flex-wrap tw-mb-4'>
|
||||||
|
{
|
||||||
|
item.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField).map(o => {
|
||||||
|
const tag = tags.find(t => t.id === o.tags_id);
|
||||||
|
return (tag ? <TagView key={tag?.id} tag={tag} /> : "")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</> : ""
|
||||||
|
}
|
||||||
<TextView item={item} />
|
<TextView item={item} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</MapOverlayPage>
|
</MapOverlayPage >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,9 +13,10 @@ import { hashTagRegex } from '../../Utils/HashTagRegex';
|
|||||||
import { useAddTag, useTags } from '../Map/hooks/useTags';
|
import { useAddTag, useTags } from '../Map/hooks/useTags';
|
||||||
import { randomColor } from '../../Utils/RandomColor';
|
import { randomColor } from '../../Utils/RandomColor';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { UserItem } from '../../types';
|
import { Tag, UserItem } from '../../types';
|
||||||
import { MapOverlayPage } from '../Templates';
|
import { MapOverlayPage } from '../Templates';
|
||||||
import { TagsWidget } from './TagsWidget';
|
import { TagsWidget } from './TagsWidget';
|
||||||
|
import { decodeTag, encodeTag } from '../../Utils/FormatTags';
|
||||||
|
|
||||||
|
|
||||||
export function OverlayProfileSettings() {
|
export function OverlayProfileSettings() {
|
||||||
@ -27,6 +28,8 @@ export function OverlayProfileSettings() {
|
|||||||
const [text, setText] = useState<string>("");
|
const [text, setText] = useState<string>("");
|
||||||
const [avatar, setAvatar] = useState<string>("");
|
const [avatar, setAvatar] = useState<string>("");
|
||||||
const [color, setColor] = useState<string>("");
|
const [color, setColor] = useState<string>("");
|
||||||
|
const [offers, setOffers] = useState<Array<Tag>>([]);
|
||||||
|
const [needs, setNeeds] = useState<Array<Tag>>([]);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<number>(1);
|
const [activeTab, setActiveTab] = useState<number>(1);
|
||||||
|
|
||||||
@ -48,8 +51,18 @@ export function OverlayProfileSettings() {
|
|||||||
setId(user?.id ? user.id : "");
|
setId(user?.id ? user.id : "");
|
||||||
setName(user?.first_name ? user.first_name : "");
|
setName(user?.first_name ? user.first_name : "");
|
||||||
setText(user?.description ? user.description : "");
|
setText(user?.description ? user.description : "");
|
||||||
setAvatar(user?.avatar ? user?.avatar : ""),
|
setAvatar(user?.avatar ? user?.avatar : "");
|
||||||
setColor(user?.color ? user.color : "#aabbcc")
|
setColor(user?.color ? user.color : "#aabbcc");
|
||||||
|
setOffers([]);
|
||||||
|
setNeeds([]);
|
||||||
|
user?.offers.map(o=> {
|
||||||
|
const offer = tags.find(t => t.id === o.tags_id);
|
||||||
|
offer && setOffers(current => [...current,offer])
|
||||||
|
})
|
||||||
|
user?.needs.map(o=> {
|
||||||
|
const need = tags.find(t => t.id === o.tags_id);
|
||||||
|
need && setNeeds(current => [...current,need])
|
||||||
|
})
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
const imgRef = React.useRef<HTMLImageElement>(null)
|
const imgRef = React.useRef<HTMLImageElement>(null)
|
||||||
@ -145,17 +158,49 @@ export function OverlayProfileSettings() {
|
|||||||
setAvatar(asset.id)
|
setAvatar(asset.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onUpdateUser = async () => {
|
||||||
const onUpdateUser = () => {
|
|
||||||
let changedUser = {} as UserItem;
|
let changedUser = {} as UserItem;
|
||||||
|
|
||||||
changedUser = { id: id, first_name: name, description: text, color: color, ...avatar.length > 10 && { avatar: avatar } };
|
let offer_updates : Array<any> = [];
|
||||||
const item = items.find(i => i.layer?.itemOwnerField && getValue(i, i.layer?.itemOwnerField).id === id);
|
//check for new offers
|
||||||
if (item && item.layer && item.layer.itemOwnerField) item[item.layer.itemOwnerField] = changedUser;
|
offers.map(o => {
|
||||||
|
const existingOffer = user?.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({directus_user_id: user?.id, tags_id: o.id})
|
||||||
|
});
|
||||||
|
|
||||||
|
let needs_updates : Array<any> = [];
|
||||||
|
|
||||||
|
needs.map(n => {
|
||||||
|
const existingNeed = user?.needs.find(t => t.tags_id === n.id)
|
||||||
|
existingNeed && needs_updates.push(existingNeed.id)
|
||||||
|
!existingNeed && needs_updates.push({directus_user_id: user?.id, tags_id: n.id})
|
||||||
|
!existingNeed && !tags.some(t => t.id === n.id) && addTag({...n,offer_or_need: true})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
changedUser = { id: id, first_name: name, description: text, color: color, ...avatar.length > 10 && { avatar: avatar }, ... offers.length > 0 && {offers: offer_updates}, ... needs.length > 0 && {needs: needs_updates} };
|
||||||
|
// update profile item in current state
|
||||||
|
const item = items.find(i => i.layer?.itemOwnerField && getValue(i, i.layer?.itemOwnerField).id === id);
|
||||||
|
|
||||||
|
let offer_state : Array<any> = [];
|
||||||
|
let needs_state : Array<any> = [];
|
||||||
|
|
||||||
|
offers.map(o => {
|
||||||
|
offer_state.push({directus_user_id: user?.id, tags_id: o.id})
|
||||||
|
});
|
||||||
|
|
||||||
|
needs.map(n => {
|
||||||
|
needs_state.push({directus_user_id: user?.id, tags_id: n.id})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (item && item.layer && item.layer.itemOwnerField) item[item.layer.itemOwnerField] = {... changedUser, offers: offer_state, needs: needs_state};
|
||||||
|
// add new hashtags from profile text
|
||||||
text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => {
|
||||||
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||||
addTag({ id: crypto.randomUUID(), name: tag.slice(1).toLocaleLowerCase(), color: randomColor() })
|
addTag({ id: crypto.randomUUID(), name: encodeTag(tag.slice(1).toLocaleLowerCase()), color: randomColor()})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -178,7 +223,7 @@ export function OverlayProfileSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MapOverlayPage backdrop className='tw-mx-4 tw-mt-4 tw-mb-12 tw-overflow-x-hidden tw-max-h-[calc(100dvh-96px)] !tw-h-[calc(100dvh-96px)] tw-w-[calc(100%-32px)] md:tw-w-[calc(50%-32px)] tw-max-w-xl !tw-left-auto tw-top-0 tw-bottom-0'>
|
<MapOverlayPage backdrop className='tw-mx-4 tw-mt-4 tw-mb-12 tw-overflow-x-hidden tw-max-h-[calc(100dvh-96px)] !tw-h-[calc(100dvh-96px)] tw-w-[calc(100%-32px)] md:tw-w-[calc(50%-32px)] tw-max-w-2xl !tw-left-auto tw-top-0 tw-bottom-0'>
|
||||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||||
<div className="tw-flex">
|
<div className="tw-flex">
|
||||||
{!cropping ?
|
{!cropping ?
|
||||||
@ -216,17 +261,17 @@ export function OverlayProfileSettings() {
|
|||||||
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-4">
|
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-4">
|
||||||
<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="Text" checked={activeTab == 1 && true} onChange={() => setActiveTab(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="Text" checked={activeTab == 1 && true} onChange={() => setActiveTab(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">
|
<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">
|
||||||
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0' />
|
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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 == 2 && true} onChange={() => setActiveTab(2)} />
|
<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 == 2 && true} onChange={() => setActiveTab(2)} />
|
||||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
|
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
|
||||||
<div className='tw-h-full'>
|
<div className='tw-h-full'>
|
||||||
<div className='tw-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'>
|
<div className='tw-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'>
|
||||||
<TagsWidget 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'/>
|
<TagsWidget defaultTags={offers} onUpdate={(v)=>setOffers(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>
|
||||||
<div className='tw-w-full tw-h-[calc(50%-0.75em)] '>
|
<div className='tw-w-full tw-h-[calc(50%-0.75em)] '>
|
||||||
<TagsWidget 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'/>
|
<TagsWidget defaultTags={needs} onUpdate={(v)=>setNeeds(v)} placeholder="enter your needs" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import { useTags } from '../Map/hooks/useTags';
|
|||||||
import { Tag } from '../../types';
|
import { Tag } from '../../types';
|
||||||
import { Autocomplete } from '../Input/Autocomplete';
|
import { Autocomplete } from '../Input/Autocomplete';
|
||||||
import { randomColor } from '../../Utils/RandomColor';
|
import { randomColor } from '../../Utils/RandomColor';
|
||||||
|
import { decodeTag, encodeTag } from '../../Utils/FormatTags';
|
||||||
|
|
||||||
export const TagsWidget = ({placeholder, containerStyle}) => {
|
export const TagsWidget = ({placeholder, containerStyle, defaultTags, onUpdate}) => {
|
||||||
|
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [localTags, setLocalTags] = useState<Array<Tag>>([]);
|
|
||||||
const [isKeyReleased, setIsKeyReleased] = useState(false);
|
const [isKeyReleased, setIsKeyReleased] = useState(false);
|
||||||
const tags = useTags();
|
const tags = useTags();
|
||||||
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([]);
|
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([]);
|
||||||
@ -25,20 +25,20 @@ export const TagsWidget = ({placeholder, containerStyle}) => {
|
|||||||
const { key } = e;
|
const { key } = e;
|
||||||
const trimmedInput = input.trim();
|
const trimmedInput = input.trim();
|
||||||
|
|
||||||
if ((key === 'Enter' || key === ',' ) && trimmedInput.length && !localTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) {
|
if ((key === 'Enter' || key === ',' ) && trimmedInput.length && !defaultTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newTag = tags.find(t => t.name === trimmedInput.toLocaleLowerCase())
|
const newTag = tags.find(t => t.name === trimmedInput.toLocaleLowerCase())
|
||||||
newTag && setLocalTags(prevState => [...prevState, newTag]);
|
newTag && onUpdate(prevState => [...prevState, newTag]);
|
||||||
!newTag && setLocalTags(prevState => [...prevState, { id: crypto.randomUUID(), name: trimmedInput.toLocaleLowerCase(), color: randomColor() }]);
|
!newTag && onUpdate(prevState => [...prevState, { id: crypto.randomUUID(), name: encodeTag(trimmedInput), color: randomColor() }]);
|
||||||
setInput('');
|
setInput('');
|
||||||
setPushFilteredSuggestions([]);
|
setPushFilteredSuggestions([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "Backspace" && !input.length && localTags.length && isKeyReleased) {
|
if (key === "Backspace" && !input.length && defaultTags.length && isKeyReleased) {
|
||||||
const localTagsCopy = [...localTags];
|
const defaultTagsCopy = [...defaultTags];
|
||||||
const poppedTag = localTagsCopy.pop();
|
const poppedTag = defaultTagsCopy.pop();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLocalTags(localTagsCopy);
|
onUpdate(defaultTagsCopy);
|
||||||
poppedTag && setInput(poppedTag.name);
|
poppedTag && setInput(poppedTag.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,15 +50,15 @@ export const TagsWidget = ({placeholder, containerStyle}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteTag = (tag) => {
|
const deleteTag = (tag) => {
|
||||||
setLocalTags(prevState => prevState.filter((t) => t !== tag))
|
onUpdate(prevState => prevState.filter((t) => t !== tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const onSelected = (tag) => {
|
const onSelected = (tag) => {
|
||||||
if(!localTags.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 === tag.name.toLocaleLowerCase())
|
const newTag = tags.find(t => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())
|
||||||
newTag && setLocalTags(prevState => [...prevState, newTag]);
|
newTag && onUpdate(prevState => [...prevState, newTag]);
|
||||||
!newTag && setLocalTags(prevState => [...prevState, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }]);
|
!newTag && onUpdate(prevState => [...prevState, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }]);
|
||||||
setInput('');
|
setInput('');
|
||||||
setPushFilteredSuggestions([]);
|
setPushFilteredSuggestions([]);
|
||||||
}
|
}
|
||||||
@ -81,11 +81,11 @@ export const TagsWidget = ({placeholder, containerStyle}) => {
|
|||||||
}, 200)
|
}, 200)
|
||||||
}} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}>
|
}} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}>
|
||||||
<div className='tw-flex tw-flex-wrap tw-h-fit'>
|
<div className='tw-flex tw-flex-wrap tw-h-fit'>
|
||||||
{localTags.map((tag) => (
|
{defaultTags.map((tag) => (
|
||||||
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-h-[2.75em] 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-h-[2.75em] tw-mt-3 tw-mr-4' style={{ backgroundColor: tag.color ? tag.color : "#666" }}>
|
||||||
<div className="tw-card-actions tw-justify-end">
|
<div className="tw-card-actions tw-justify-end">
|
||||||
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (deleteTag(tag))}>✕</label>
|
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (deleteTag(tag))}>✕</label>
|
||||||
</div><b>#{tag.name}</b>
|
</div><b>#{decodeTag(tag.name)}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
|
|||||||
15
src/Components/Templates/TagView.tsx
Normal file
15
src/Components/Templates/TagView.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { decodeTag } from '../../Utils/FormatTags'
|
||||||
|
import { Tag } from '../../types'
|
||||||
|
|
||||||
|
export const TagView = ({tag} : {tag: Tag}) => {
|
||||||
|
return (
|
||||||
|
// Use your imagination to render suggestions.
|
||||||
|
|
||||||
|
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-h-[2.75em] tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit' style={{ backgroundColor: tag.color ? tag.color : "#666" }}>
|
||||||
|
<div className="tw-card-actions tw-justify-end">
|
||||||
|
</div><b>#{decodeTag(tag.name)}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
8
src/Utils/FormatTags.ts
Normal file
8
src/Utils/FormatTags.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export function decodeTag(string : string) {
|
||||||
|
let formatedTag = string.replace(/_/g, "\u00A0");
|
||||||
|
return formatedTag = formatedTag.charAt(0).toUpperCase() + formatedTag.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeTag(string : string) {
|
||||||
|
return string.replace(/\s+/, "_");
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
export function getValue(obj, path) {
|
export function getValue(obj, path) {
|
||||||
if (obj) {
|
if (!obj) return undefined; // Return early if obj is falsy
|
||||||
for (var i = 0, path = path.split('.'), len = path.length; i < len; i++) {
|
|
||||||
if(obj) obj = obj[path[i]];
|
var pathArray = path.split('.'); // Use a different variable for the split path
|
||||||
};
|
for (var i = 0, len = pathArray.length; i < len; i++) {
|
||||||
return obj;
|
if (!obj) return undefined; // Check if obj is falsy at each step
|
||||||
|
obj = obj[pathArray[i]]; // Dive one level deeper
|
||||||
}
|
}
|
||||||
};
|
return obj; // Return the final value
|
||||||
|
}
|
||||||
@ -26,10 +26,13 @@ export interface LayerProps {
|
|||||||
itemAvatarField?: string,
|
itemAvatarField?: string,
|
||||||
itemColorField?: string,
|
itemColorField?: string,
|
||||||
itemOwnerField?: string,
|
itemOwnerField?: string,
|
||||||
itemTagField?: string,
|
itemTagsField?: string,
|
||||||
itemLatitudeField?: any,
|
itemLatitudeField?: any,
|
||||||
itemLongitudeField?: any,
|
itemLongitudeField?: any,
|
||||||
|
itemOffersField?: string,
|
||||||
|
itemNeedsField?: string,
|
||||||
onlyOnePerOwner?: boolean,
|
onlyOnePerOwner?: boolean,
|
||||||
|
customEditLink?: string,
|
||||||
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>,
|
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>,
|
||||||
itemFormPopup?: ItemFormPopupProps | null,
|
itemFormPopup?: ItemFormPopupProps | null,
|
||||||
clusterRef?: React.MutableRefObject<any>
|
clusterRef?: React.MutableRefObject<any>
|
||||||
@ -71,6 +74,7 @@ export interface Tag {
|
|||||||
color: string;
|
color: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
offer_or_need?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemsApi<T> {
|
export interface ItemsApi<T> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user