From 86b442aabfc1c6c4da28536199aab8dd358813d6 Mon Sep 17 00:00:00 2001 From: Anton Tranelis Date: Tue, 23 Jul 2024 18:29:10 +0200 Subject: [PATCH] cleanup profile form --- src/Components/Profile/ProfileForm.tsx | 473 +++--------------- src/Components/Profile/ProfileView.tsx | 33 +- .../Profile/Subcomponents/AvatarWidget.tsx | 2 +- .../Profile/Subcomponents/FormHeader.tsx | 28 ++ .../Profile/Templates/OnepagerForm.tsx | 164 ++++++ .../{Onepager.tsx => OnepagerView.tsx} | 2 +- .../Profile/Templates/SimpleForm.tsx | 10 + .../Templates/{Simple.tsx => SimpleView.tsx} | 2 +- src/Components/Profile/Templates/TabsForm.tsx | 89 ++++ .../Templates/{Tabs.tsx => TabsView.tsx} | 15 +- src/Components/Profile/itemFunctions.ts | 110 +++- 11 files changed, 494 insertions(+), 434 deletions(-) create mode 100644 src/Components/Profile/Subcomponents/FormHeader.tsx create mode 100644 src/Components/Profile/Templates/OnepagerForm.tsx rename src/Components/Profile/Templates/{Onepager.tsx => OnepagerView.tsx} (96%) create mode 100644 src/Components/Profile/Templates/SimpleForm.tsx rename src/Components/Profile/Templates/{Simple.tsx => SimpleView.tsx} (81%) create mode 100644 src/Components/Profile/Templates/TabsForm.tsx rename src/Components/Profile/Templates/{Tabs.tsx => TabsView.tsx} (88%) diff --git a/src/Components/Profile/ProfileForm.tsx b/src/Components/Profile/ProfileForm.tsx index a867e4ff..74ce8ad0 100644 --- a/src/Components/Profile/ProfileForm.tsx +++ b/src/Components/Profile/ProfileForm.tsx @@ -1,106 +1,53 @@ import { useItems, useUpdateItem, useAddItem } from '../Map/hooks/useItems' import { useEffect, useState } from 'react'; import { getValue } from '../../Utils/GetValue'; -import { toast } from 'react-toastify'; import { useAuth } from '../Auth'; -import { TextInput, TextAreaInput } from '../Input'; -import ComboBoxInput from '../Input/ComboBoxInput'; -import { ColorPicker } from './Subcomponents/ColorPicker'; -import { hashTagRegex } from '../../Utils/HashTagRegex'; import { useAddTag, useGetItemTags, useTags } from '../Map/hooks/useTags'; -import { randomColor } from '../../Utils/RandomColor'; import { useLocation, useNavigate } from 'react-router-dom'; import { Item, Tag } from '../../types'; import { MapOverlayPage } from '../Templates'; -import { AvatarWidget } from './Subcomponents/AvatarWidget'; -import { encodeTag } from '../../Utils/FormatTags'; import { useLayers } from '../Map/hooks/useLayers'; -import { TagsWidget } from './Subcomponents/TagsWidget'; -import { LinkedItemsHeaderView } from './Subcomponents/LinkedItemsHeaderView'; -import { TextView } from '../Map'; -import { ActionButton } from './Subcomponents/ActionsButton'; 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 }) { - 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 [id, setId] = useState(""); - const [groupType, setGroupType] = useState(""); - const [status, setStatus] = useState(""); - const [name, setName] = useState(""); - const [subname, setSubname] = useState(""); - const [text, setText] = useState(""); - const [contact, setContact] = useState(""); - const [telephone, setTelephone] = useState(""); - const [nextAppointment, setNextAppointment] = useState(""); - const [markerIcon, setMarkerIcon] = useState(""); - const [image, setImage] = useState(""); - const [color, setColor] = useState(""); - const [offers, setOffers] = useState>([]); - const [needs, setNeeds] = useState>([]); - const [relations, setRelations] = useState>([]); + 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[] + }); const [updatePermission, setUpdatePermission] = useState(false); - - - const [activeTab, setActiveTab] = useState(1); const [loading, setLoading] = useState(false); + const [item, setItem] = useState({} 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(); - - useEffect(() => { - switch (groupType) { - case "wuerdekompass": - setColor(item?.layer?.menuColor || "#1A5FB4"); - setMarkerIcon("group"); - setImage("59e6a346-d1ee-4767-9e42-fc720fb535c9") - - break; - case "themenkompass": - setColor("#26A269"); - setMarkerIcon("group"); - setImage("59e6a346-d1ee-4767-9e42-fc720fb535c9") - - break; - case "liebevoll.jetzt": - setColor("#E8B620"); - setMarkerIcon("liebevoll.jetzt"); - setImage("e735b96c-507b-471c-8317-386ece0ca51d") - break; - - default: - break; - } - }, [groupType]) - - - - const items = useItems(); - const [item, setItem] = useState({} as Item) useEffect(() => { item && hasUserPermission("items", "update", item) && setUpdatePermission(true); @@ -118,201 +65,49 @@ export function ProfileForm({ userType }: { userType: string }) { }, [items]) - const updateActiveTab = (id: number) => { - setActiveTab(id); - - let params = new URLSearchParams(window.location.search); - let urlTab = params.get("tab"); - if (!urlTab?.includes(id.toString())) - params.set("tab", `${id ? id : ""}`) - window.history.pushState('', '', "?" + params.toString()); - } - useEffect(() => { - let params = new URLSearchParams(location.search); - let urlTab = params.get("tab"); - urlTab ? setActiveTab(Number(urlTab)) : setActiveTab(1); - }, [location]) + 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 needs = (item?.needs ?? []).reduce((acc: Tag[], o) => { + const need = tags.find(t => t.id === o.tags_id); + if (need) acc.push(need); + return acc; + }, []); - useEffect(() => { - setColor(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)) + 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; + }, []); - setId(item?.id ? item.id : ""); - setGroupType(item?.group_type || "wuerdekompass"); - setStatus(item?.status || "active"); - setName(item?.name ? item.name : ""); - setSubname(item?.subname ? item.subname : ""); - setText(item?.text ? item.text : ""); - setContact(item?.contact || ""); - setTelephone(item?.telephone || ""); - setNextAppointment(item?.next_appointment || ""); - setImage(item?.image ? item?.image : ""); - setMarkerIcon(item?.marker_icon ? item.marker_icon : ""); - setOffers([]); - setNeeds([]); - setRelations([]); - item?.offers?.map(o => { - const offer = tags?.find(t => t.id === o.tags_id); - offer && setOffers(current => [...current, offer]) - }) - item?.needs?.map(o => { - const need = tags?.find(t => t.id === o.tags_id); - need && setNeeds(current => [...current, need]) - }) - item.relations?.map(r => { - const item = items.find(i => i.id == r.related_items_id) - item && setRelations(current => [...current, item]) - }) - - }, [item]) - - - const onUpdateItem = async () => { - let changedItem = {} as Item; - - let offer_updates: Array = []; - //check for new offers - 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 }) + 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 }); - - let needs_updates: Array = []; - - 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 }) - }); - - - // update profile item in current state - changedItem = { - id: id, - group_type: groupType, - status: status, - name: name, - subname: subname, - text: text, - color: color, - position: item.position, - contact: contact, - telephone: telephone, - ...markerIcon && { markerIcon: markerIcon }, - next_appointment: nextAppointment, - ...image.length > 10 && { image: image }, - ...offers.length > 0 && { offers: offer_updates }, - ...needs.length > 0 && { needs: needs_updates } - }; - - let offers_state: Array = []; - let needs_state: Array = []; - - await offers.map(o => { - offers_state.push({ items_id: item?.id, tags_id: o.id }) - }); - - await needs.map(n => { - needs_state.push({ items_id: item?.id, tags_id: n.id }) - }); - - changedItem = { ...changedItem, offers: offers_state, needs: needs_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() }) - } - }); - - setLoading(true); - console.log(item.layer); - - - 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}` - }, - }, - }) - .then(() => item && updateItem({ ...item, ...changedItem })) - .then(() => { - setLoading(false); - navigate(`/item/${item.id}${params && "?"+params}`) - }); - - } - else { - item.layer?.api?.createItem && toast.promise( - item.layer?.api?.createItem(changedItem), - { - pending: 'updating Item ...', - success: 'Item updated', - error: { - render({ data }) { - return `${data}` - }, - }, - }) - .then(() => item && addItem({ ...item, ...changedItem, layer: item.layer, user_created: user, type: item.layer?.itemType })) - .then(() => { - setLoading(false); - navigate(`/${params && "?"+params}`) - }); - } - } - - const linkItem = async (id: string) => { - let new_relations = item.relations || []; - new_relations?.push({ items_id: item.id, 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 linked"); - } - } - - const unlinkItem = async (id: string) => { - console.log(id); - - let new_relations = item.relations?.filter(r => r.related_items_id !== id) - console.log(new_relations); - - 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"); - } - - } + }, [item, tags, items]); const [template, setTemplate] = useState("") @@ -322,161 +117,33 @@ export function ProfileForm({ userType }: { userType: string }) { let params = new URLSearchParams(window.location.search); - - return ( <> +
-
- - -
- setName(v)} containerStyle='tw-grow tw-input-md' /> - setSubname(v)} containerStyle='tw-grow tw-input-sm tw-px-4 tw-mt-1' /> -
-
+ + {template == "onepager" && ( -
-
-
- - setGroupType(v)} - /> -
-
- - setStatus(v)} - /> -
-
- -
- - setContact(v)} - /> -
- -
- - setTelephone(v)} - /> -
- -
- - setNextAppointment(v)} - inputStyle="tw-h-24" - /> -
- -
- - setText(v)} - inputStyle="tw-h-48" - /> -
-
+ )} {template == "simple" && - { console.log(v); setText(v) }} containerStyle='tw-mt-8 tw-h-full' inputStyle='tw-h-full' /> - + } {template == "tabs" && - - -
- updateActiveTab(1)} /> -
- { console.log(v); setText(v) }} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' /> -
- {item.layer?.itemType.offers_and_needs && - <> - updateActiveTab(3)} /> -
-
-
- 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' /> -
-
- 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' /> -
-
-
- - } - {item.layer?.itemType.relations && - <> - updateActiveTab(7)} /> -
-
-
- {relations && relations.map(i => - - -
navigate('/item/' + i.id)}> - -
- -
-
- )} - {updatePermission && } - -
-
-
- - } - - -
+ linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}> } - - -
+
+ +
) -} - - +} \ No newline at end of file diff --git a/src/Components/Profile/ProfileView.tsx b/src/Components/Profile/ProfileView.tsx index 6061896d..37b2756f 100644 --- a/src/Components/Profile/ProfileView.tsx +++ b/src/Components/Profile/ProfileView.tsx @@ -1,7 +1,7 @@ import { MapOverlayPage } from '../Templates' import { useItems, useRemoveItem, useUpdateItem } from '../Map/hooks/useItems' import { useLocation, useNavigate } from 'react-router-dom' -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Item, Tag } from '../../types'; import { useMap } from 'react-leaflet'; import { LatLng } from 'leaflet'; @@ -11,26 +11,25 @@ import { useSelectPosition, useSetSelectPosition } from '../Map/hooks/useSelectP import { useClusterRef } from '../Map/hooks/useClusterRef'; import { useLeafletRefs } from '../Map/hooks/useLeafletRefs'; import { getValue } from '../../Utils/GetValue'; -import { Tabs } from './Templates/Tabs'; -import { Onepager } from './Templates/Onepager'; -import { Simple } from './Templates/Simple'; +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 }: { userType: string }) { + const [item, setItem] = useState({} as Item) const [updatePermission, setUpdatePermission] = useState(false); const [relations, setRelations] = useState>([]); const [offers, setOffers] = useState>([]); const [needs, setNeeds] = useState>([]); const [loading, setLoading] = useState(false); - - const [addItemPopupType, setAddItemPopupType] = useState(""); + const [template, setTemplate] = useState(""); const location = useLocation(); const items = useItems(); const updateItem = useUpdateItem(); - const [item, setItem] = useState({} as Item) const map = useMap(); const selectPosition = useSelectPosition(); const removeItem = useRemoveItem(); @@ -41,16 +40,6 @@ export function ProfileView({ userType }: { userType: string }) { const clusterRef = useClusterRef(); const leafletRefs = useLeafletRefs(); - const tabRef = useRef(null); - - function scroll() { - tabRef.current?.scrollIntoView(); - } - - useEffect(() => { - scroll(); - }, [addItemPopupType]) - useEffect(() => { const itemId = location.pathname.split("/")[2]; const item = items.find(i => i.id === itemId); @@ -126,9 +115,6 @@ export function ProfileView({ userType }: { userType: string }) { selectPosition && map.closePopup(); }, [selectPosition]) - - const [template, setTemplate] = useState("") - useEffect(() => { setTemplate(item.layer?.itemType.template || userType); }, [userType, item]) @@ -142,17 +128,16 @@ export function ProfileView({ userType }: { userType: string }) { handleDelete(e, item, setLoading, removeItem, map, navigate)} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate("/") }} big truncateSubname={false} /> - {template == "onepager" && - + } {template == "simple" && - + } {template == "tabs" && - linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}/> + linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}/> } diff --git a/src/Components/Profile/Subcomponents/AvatarWidget.tsx b/src/Components/Profile/Subcomponents/AvatarWidget.tsx index 82e52142..e513bb94 100644 --- a/src/Components/Profile/Subcomponents/AvatarWidget.tsx +++ b/src/Components/Profile/Subcomponents/AvatarWidget.tsx @@ -6,7 +6,7 @@ import DialogModal from "../../Templates/DialogModal"; import 'react-image-crop/dist/ReactCrop.css' -export const AvatarWidget = ({avatar, setAvatar}:{avatar:string, setAvatar : React.Dispatch>}) => { +export const AvatarWidget = ({avatar, setAvatar}:{avatar:string, setAvatar : React.Dispatch>}) => { const [crop, setCrop] = useState(); diff --git a/src/Components/Profile/Subcomponents/FormHeader.tsx b/src/Components/Profile/Subcomponents/FormHeader.tsx new file mode 100644 index 00000000..a33ac7af --- /dev/null +++ b/src/Components/Profile/Subcomponents/FormHeader.tsx @@ -0,0 +1,28 @@ +import { TextInput } from "utopia-ui" +import { AvatarWidget } from "./AvatarWidget" +import { ColorPicker } from "./ColorPicker" + +export const FormHeader = ({item, state, setState}) => { + return ( +
+ setState(prevState => ({ + ...prevState, + image: i + }))} /> + setState(prevState => ({ + ...prevState, + color: c + }))} className={"-tw-left-6 tw-top-14 -tw-mr-6"} /> +
+ setState(prevState => ({ + ...prevState, + name: v + }))} containerStyle='tw-grow tw-input-md' /> + setState(prevState => ({ + ...prevState, + subname: v + }))} containerStyle='tw-grow tw-input-sm tw-px-4 tw-mt-1' /> +
+
+ ) +} diff --git a/src/Components/Profile/Templates/OnepagerForm.tsx b/src/Components/Profile/Templates/OnepagerForm.tsx new file mode 100644 index 00000000..2542e5e4 --- /dev/null +++ b/src/Components/Profile/Templates/OnepagerForm.tsx @@ -0,0 +1,164 @@ +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: { + color: string; + id: string; + groupType: string; + status: string; + name: string; + subname: string; + text: string; + contact: string; + telephone: string; + nextAppointment: string; + image: string; + markerIcon: string; + offers: Tag[]; + needs: Tag[]; + relations: Item[]; + }, + setState: React.Dispatch>, + 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" + })); + + break; + default: + break; + } + }, [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' } + ]; + + return ( +
+
+
+ + setState(prevState => ({ + ...prevState, + groupType: v + }))} + /> +
+
+ + setState(prevState => ({ + ...prevState, + status: v + }))} + /> +
+
+ +
+ + setState(prevState => ({ + ...prevState, + contact: v + }))} + /> +
+ +
+ + setState(prevState => ({ + ...prevState, + telephone: v + }))} + /> +
+ +
+ + setState(prevState => ({ + ...prevState, + nextAppointment: v + }))} + inputStyle="tw-h-24" + /> +
+ +
+ + setState(prevState => ({ + ...prevState, + text: v + }))} + inputStyle="tw-h-48" + /> +
+
+ ) +} diff --git a/src/Components/Profile/Templates/Onepager.tsx b/src/Components/Profile/Templates/OnepagerView.tsx similarity index 96% rename from src/Components/Profile/Templates/Onepager.tsx rename to src/Components/Profile/Templates/OnepagerView.tsx index a158cf85..4b30a217 100644 --- a/src/Components/Profile/Templates/Onepager.tsx +++ b/src/Components/Profile/Templates/OnepagerView.tsx @@ -5,7 +5,7 @@ import ProfileSubHeader from "../Subcomponents/ProfileSubHeader" import { useEffect, useState } from "react" import { useItems } from "../../Map/hooks/useItems" -export const Onepager = ({item, userType}:{item: Item, userType: string}) => { +export const OnepagerView = ({item, userType}:{item: Item, userType: string}) => { const [profile_owner, setProfileOwner] = useState(); const items = useItems(); diff --git a/src/Components/Profile/Templates/SimpleForm.tsx b/src/Components/Profile/Templates/SimpleForm.tsx new file mode 100644 index 00000000..4b060a96 --- /dev/null +++ b/src/Components/Profile/Templates/SimpleForm.tsx @@ -0,0 +1,10 @@ +import { TextAreaInput } from "../../Input" + +export const SimpleForm = (item, setState) => { + return ( + setState(prevState => ({ + ...prevState, + text: v + }))} containerStyle='tw-mt-8 tw-h-full' inputStyle='tw-h-full' /> + ) +} diff --git a/src/Components/Profile/Templates/Simple.tsx b/src/Components/Profile/Templates/SimpleView.tsx similarity index 81% rename from src/Components/Profile/Templates/Simple.tsx rename to src/Components/Profile/Templates/SimpleView.tsx index 228622bf..3ae5f5a8 100644 --- a/src/Components/Profile/Templates/Simple.tsx +++ b/src/Components/Profile/Templates/SimpleView.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { TextView } from '../../Map' import { Item } from '../../../types' -export const Simple = ({item}:{item: Item}) => { +export const SimpleView = ({item}:{item: Item}) => { return (
diff --git a/src/Components/Profile/Templates/TabsForm.tsx b/src/Components/Profile/Templates/TabsForm.tsx new file mode 100644 index 00000000..870883d8 --- /dev/null +++ b/src/Components/Profile/Templates/TabsForm.tsx @@ -0,0 +1,89 @@ +import { useEffect, useState } from "react" +import { TextAreaInput } from "../../Input" +import { 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" + +export const TabsForm = ({item, state, setState, updatePermission, linkItem, unlinkItem, loading}) => { + + const [activeTab, setActiveTab] = useState(1); + const navigate = useNavigate(); + const updateItem = useUpdateItem(); + + const updateActiveTab = (id: number) => { + setActiveTab(id); + + let params = new URLSearchParams(window.location.search); + let urlTab = params.get("tab"); + if (!urlTab?.includes(id.toString())) + params.set("tab", `${id ? id : ""}`) + window.history.pushState('', '', "?" + params.toString()); + } + + useEffect(() => { + let params = new URLSearchParams(location.search); + let urlTab = params.get("tab"); + urlTab ? setActiveTab(Number(urlTab)) : setActiveTab(1); + }, [location]) + + return ( +
+ updateActiveTab(1)} /> +
+ setState(prevState => ({ + ...prevState, + text: v +}))} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' /> +
+ {item.layer?.itemType.offers_and_needs && + <> + updateActiveTab(3)} /> +
+
+
+ setState(prevState => ({ + ...prevState, + offers: v + }))} placeholder="enter your offers" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto' /> +
+
+ setState(prevState => ({ + ...prevState, + needs: v + }))} placeholder="enter your needs" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2 tw-overflow-auto' /> +
+
+
+ + } + {item.layer?.itemType.relations && + <> + updateActiveTab(7)} /> +
+
+
+ {state.relations && state.relations.map(i => + + +
navigate('/item/' + i.id)}> + unlinkItem(id, item, updateItem)} loading={loading} /> +
+ +
+
+ )} + {updatePermission && linkItem(id, item, updateItem)} colorField={item.layer.itemColorField}>} + +
+
+
+ + } + + +
+ ) +} diff --git a/src/Components/Profile/Templates/Tabs.tsx b/src/Components/Profile/Templates/TabsView.tsx similarity index 88% rename from src/Components/Profile/Templates/Tabs.tsx rename to src/Components/Profile/Templates/TabsView.tsx index da97728b..a79f7d59 100644 --- a/src/Components/Profile/Templates/Tabs.tsx +++ b/src/Components/Profile/Templates/TabsView.tsx @@ -2,17 +2,28 @@ import { StartEndView, TextView } from '../../Map' import { TagView } from '../../Templates/TagView' import { LinkedItemsHeaderView } from '../Subcomponents/LinkedItemsHeaderView' import { ActionButton } from '../Subcomponents/ActionsButton' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useAddFilterTag } from '../../Map/hooks/useFilter' import { Item, Tag } from 'utopia-ui/dist/types' import { useNavigate } from 'react-router-dom' -export const Tabs = ({ item, offers, needs, relations, updatePermission, loading, linkItem, unlinkItem }: { item: Item, offers: Array, needs: Array, relations: Array, updatePermission: boolean, loading: boolean, linkItem: (id: string) => Promise, unlinkItem: (id: string) => Promise }) => { +export const TabsView = ({ item, offers, needs, relations, updatePermission, loading, linkItem, unlinkItem }: { item: Item, offers: Array, needs: Array, relations: Array, updatePermission: boolean, loading: boolean, linkItem: (id: string) => Promise, unlinkItem: (id: string) => Promise }) => { const addFilterTag = useAddFilterTag(); const [activeTab, setActiveTab] = useState(1); const navigate = useNavigate(); + const [addItemPopupType, setAddItemPopupType] = useState(""); + + useEffect(() => { + scroll(); + }, [addItemPopupType]) + + function scroll() { + tabRef.current?.scrollIntoView(); + } + + const tabRef = useRef(null); const updateActiveTab = (id: number) => { setActiveTab(id); diff --git a/src/Components/Profile/itemFunctions.ts b/src/Components/Profile/itemFunctions.ts index 4620aa0e..f611f9e1 100644 --- a/src/Components/Profile/itemFunctions.ts +++ b/src/Components/Profile/itemFunctions.ts @@ -1,9 +1,10 @@ -import { Item } from '../../types'; +import { Item, Tag } from '../../types'; +import { encodeTag } from '../../Utils/FormatTags'; import { hashTagRegex } from '../../Utils/HashTagRegex'; import { randomColor } from '../../Utils/RandomColor'; import { toast } from 'react-toastify'; -export const submitNewItem = async (evt: any, type: string, item, user, setLoading, tags, addTag, addItem, linkItem, resetFilterTags, layers, addItemPopupType, setAddItemPopupType,) => { +export const submitNewItem = async (evt: any, type: string, item, user, setLoading, tags, addTag, addItem, linkItem, resetFilterTags, layers, addItemPopupType, setAddItemPopupType) => { evt.preventDefault(); const formItem: Item = {} as Item; Array.from(evt.target).forEach((input: HTMLInputElement) => { @@ -98,4 +99,109 @@ export const handleDelete = async (event: React.MouseEvent, item, s let 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 offer_updates: Array = []; + //check for new offers + 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 }) + }); + + let needs_updates: Array = []; + + 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 }) + }); + + + // update profile item in current state + changedItem = { + id: state.id, + group_type: state.groupType, + status: state.status, + name: state.name, + subname: state.subname, + text: state.text, + color: state.color, + position: item.position, + contact: state.contact, + telephone: state.telephone, + ...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 } + }; + + let offers_state: Array = []; + let needs_state: Array = []; + + await state.offers.map(o => { + offers_state.push({ items_id: item?.id, tags_id: o.id }) + }); + + await state.needs.map(n => { + needs_state.push({ items_id: item?.id, tags_id: n.id }) + }); + + changedItem = { ...changedItem, offers: offers_state, needs: needs_state }; + + + 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() }) + } + }); + + setLoading(true); + console.log(item.layer); + + + 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}` + }, + }, + }) + .then(() => item && updateItem({ ...item, ...changedItem })) + .then(() => { + setLoading(false); + navigate(`/item/${item.id}${params && "?" + params}`) + }); + + } + else { + item.layer?.api?.createItem && toast.promise( + item.layer?.api?.createItem(changedItem), + { + pending: 'updating Item ...', + success: 'Item updated', + error: { + render({ data }) { + return `${data}` + }, + }, + }) + .then(() => item && addItem({ ...item, ...changedItem, layer: item.layer, user_created: user, type: item.layer?.itemType })) + .then(() => { + setLoading(false); + navigate(`/${params && "?" + params}`) + }); + } } \ No newline at end of file