mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Profile & its Settings:
- added ComboBoxInput.tsx - added new fields: groupType, status, email, phone, next_appointment - added settings layout ContactInfo: - added Telephone - changed show elements only if required
This commit is contained in:
parent
b191466f39
commit
3b7aa5722f
34
src/Components/Input/ComboBoxInput.tsx
Normal file
34
src/Components/Input/ComboBoxInput.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { useState } from "react"
|
||||
import * as React from "react"
|
||||
|
||||
interface ComboBoxProps {
|
||||
id?: string;
|
||||
options: { value: string, label: string }[];
|
||||
value: string;
|
||||
onValueChange: (newValue: string) => void;
|
||||
}
|
||||
|
||||
const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => {
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState(value);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value;
|
||||
setSelectedValue(value);
|
||||
onValueChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<select
|
||||
id={id}
|
||||
className="tw-form-select tw-block tw-w-full tw-py-2 tw-px-4 tw-border tw-border-gray-300 rounded-md tw-shadow-sm tw-text-sm focus:tw-outline-none focus:tw-ring-indigo-500 focus:tw-border-indigo-500 sm:tw-text-sm"
|
||||
onChange={handleChange}
|
||||
>
|
||||
{options.map((o) =>
|
||||
<option value={o.value} key={o.value} selected={o.value == value}>{o.label}</option>
|
||||
)}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
export default ComboBoxInput;
|
||||
@ -1,48 +1,56 @@
|
||||
import { useAssetApi } from "../AppShell/hooks/useAssets";
|
||||
|
||||
const ContactInfo = ({ email, name, avatar } : {email: string, name: string, avatar: string}) => {
|
||||
const ContactInfo = ({ email, telephone, name, avatar } : {email: string, telephone: string, name: string, avatar: string}) => {
|
||||
const assetsApi = useAssetApi();
|
||||
|
||||
|
||||
return(
|
||||
<div className="tw-bg-gray-100 tw-my-10 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">
|
||||
<div className="tw-mr-5 tw-flex tw-items-center tw-justify-center">
|
||||
<div className="tw-avatar">
|
||||
<div
|
||||
className="tw-w-20 tw-h-20 tw-bg-gray-200 rounded-full tw-flex tw-items-center tw-justify-center overflow-hidden">
|
||||
{avatar ? (
|
||||
<img src={assetsApi.url + avatar} alt={name}
|
||||
className="tw-w-full tw-h-full tw-object-cover"/>
|
||||
) : (
|
||||
<div className="tw-flex tw-items-center tw-justify-center tw-w-full tw-h-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="tw-w-12 tw-h-12"
|
||||
fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
||||
strokeLinejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
<div className="tw-bg-gray-100 tw-my-10 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">
|
||||
{avatar && (
|
||||
<div className="tw-mr-5 tw-flex tw-items-center tw-justify-center">
|
||||
<div className="tw-avatar">
|
||||
<div className="tw-w-20 tw-h-20 tw-bg-gray-200 rounded-full tw-flex tw-items-center tw-justify-center overflow-hidden">
|
||||
<img src={assetsApi.url + avatar} alt={name}
|
||||
className="tw-w-full tw-h-full tw-object-cover"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="tw-text-sm tw-flex-grow">
|
||||
<p className="tw-font-semibold">{name}</p>
|
||||
{email && (
|
||||
<p>
|
||||
<a href={`mailto:${email}`}
|
||||
className="tw-mt-2 tw-text-green-500 tw-inline-flex tw-items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
className="tw-w-4 tw-h-4 tw-mr-1">
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
{email}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{telephone && (
|
||||
<p>
|
||||
<a href={`tel:${telephone}`}
|
||||
className="tw-mt-2 tw-text-green-500 tw-inline-flex tw-items-center tw-whitespace-nowrap">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
className="tw-w-4 tw-h-4 tw-mr-1">
|
||||
<path
|
||||
d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z"/>
|
||||
</svg>
|
||||
{telephone}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-text-sm">
|
||||
<p className="tw-font-semibold">{name}</p>
|
||||
<a href={`mailto:${email}`} className="tw-mt-2 tw-text-green-500 tw-flex tw-items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
className="tw-w-4 tw-h-4 tw-mr-1">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
|
||||
{email}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -296,7 +296,14 @@ export function OverlayItemProfile() {
|
||||
title: "Gruppe Berlin-Britz"
|
||||
};
|
||||
|
||||
const typeMapping = {
|
||||
'default': 'Würdekompass',
|
||||
'themenkompass': 'Themenkompass-Gruppe',
|
||||
'liebevoll.jetzt': 'liebevoll.jetzt',
|
||||
};
|
||||
|
||||
let groupType = item.group_type ? item.group_type : 'default';
|
||||
let groupTypeText = typeMapping[groupType];
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -308,8 +315,8 @@ export function OverlayItemProfile() {
|
||||
<div className="tw-px-6 tw-pt-6">
|
||||
<HeaderView api={item.layer?.api} item={item} deleteCallback={handleDelete} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate("/") }} big truncateSubname={false} />
|
||||
<ProfileSubHeader
|
||||
location={d.location}
|
||||
type={"Regionalgruppe"}
|
||||
type={groupTypeText}
|
||||
status={item.status}
|
||||
url={d.url}
|
||||
title={d.title}
|
||||
/>
|
||||
@ -320,31 +327,38 @@ export function OverlayItemProfile() {
|
||||
{item.layer?.itemType.onepager &&
|
||||
<>
|
||||
{item.user_created.first_name && (
|
||||
<ContactInfo name={item.user_created.first_name} avatar={item.user_created.avatar} email={item.contact} />
|
||||
<ContactInfo name={item.user_created.first_name} avatar={item.user_created.avatar} email={item.contact} telephone={item.telephone} />
|
||||
)}
|
||||
|
||||
{/* Description Section */}
|
||||
<div className="tw-my-10 tw-px-6">
|
||||
<h2 className="tw-text-lg tw-font-semibold">Beschreibung</h2>
|
||||
<div className="tw-mt-2 tw-text-sm tw-text-gray-600">
|
||||
<TextView rawText={item.text ?? 'Keine Beschreibung vorhanden'}/>
|
||||
</div>
|
||||
<div className="tw-my-10 tw-mt-2 tw-px-6 tw-text-sm tw-text-gray-600">
|
||||
<TextView rawText={item.text || 'Keine Beschreibung vorhanden'}/>
|
||||
</div>
|
||||
|
||||
{/* Relations Section */}
|
||||
{d.relations && (
|
||||
{/* Next Appointment Section */}
|
||||
{item.next_appointment && (
|
||||
<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}
|
||||
/>
|
||||
))}
|
||||
<h2 className="tw-text-lg tw-font-semibold">Nächste Termine</h2>
|
||||
<div className="tw-mt-2 tw-text-sm tw-text-gray-600">
|
||||
<TextView rawText={item.next_appointment}/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)};
|
||||
|
||||
{/* 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>*/}
|
||||
{/*)}*/}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ 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 './ColorPicker';
|
||||
import { hashTagRegex } from '../../Utils/HashTagRegex';
|
||||
import { useAddTag, useGetItemTags, useTags } from '../Map/hooks/useTags';
|
||||
@ -24,10 +25,26 @@ import { useHasUserPermission } from '../Map/hooks/usePermissions';
|
||||
|
||||
export function OverlayItemProfileSettings() {
|
||||
|
||||
const typeMapping = [
|
||||
{value: 'kompass', label: 'Würdekompass'},
|
||||
{value: 'themenkompass', label: 'Themenkompass-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<string>("");
|
||||
const [groupType, setGroupType] = useState<string>("");
|
||||
const [status, setStatus] = useState<string>("");
|
||||
const [name, setName] = useState<string>("");
|
||||
const [subname, setSubname] = useState<string>("");
|
||||
const [text, setText] = useState<string>("");
|
||||
const [contact, setContact] = useState<string>("");
|
||||
const [telephone, setTelephone] = useState<string>("");
|
||||
const [nextAppointment, setNextAppointment] = useState<string>("");
|
||||
const [image, setImage] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("");
|
||||
const [offers, setOffers] = useState<Array<Tag>>([]);
|
||||
@ -96,9 +113,14 @@ export function OverlayItemProfileSettings() {
|
||||
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))
|
||||
|
||||
setId(item?.id ? item.id : "");
|
||||
setGroupType(item?.group_type || "kompass");
|
||||
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 : "");
|
||||
setOffers([]);
|
||||
setNeeds([]);
|
||||
@ -141,13 +163,23 @@ export function OverlayItemProfileSettings() {
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
changedItem = { id: id, name: name, subname: subname, text: text, color: color, position: item.position, ...image.length > 10 && { image: image }, ...offers.length > 0 && { offers: offer_updates }, ...needs.length > 0 && { needs: needs_updates } };
|
||||
// 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,
|
||||
next_appointment: nextAppointment,
|
||||
...image.length > 10 && { image: image },
|
||||
...offers.length > 0 && { offers: offer_updates },
|
||||
...needs.length > 0 && { needs: needs_updates }
|
||||
};
|
||||
|
||||
let offers_state: Array<any> = [];
|
||||
let needs_state: Array<any> = [];
|
||||
@ -274,12 +306,80 @@ export function OverlayItemProfileSettings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{item.layer?.itemType.onepager &&
|
||||
{item.layer?.itemType.onepager && (
|
||||
<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>
|
||||
<label htmlFor="groupType" className="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-1">
|
||||
Gruppenart:
|
||||
</label>
|
||||
<ComboBoxInput
|
||||
id="groupType"
|
||||
options={typeMapping}
|
||||
value={groupType}
|
||||
onValueChange={(v) => setGroupType(v)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="status" className="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-1">
|
||||
Gruppenstatus:
|
||||
</label>
|
||||
<ComboBoxInput
|
||||
id="status"
|
||||
options={statusMapping}
|
||||
value={status}
|
||||
onValueChange={(v) => setStatus(v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TextAreaInput placeholder="My Visino..." defaultValue={item?.text ? item.text : ""} updateFormValue={(v) => { console.log(v); setText(v) }} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
|
||||
<div>
|
||||
<label htmlFor="email" className="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-1">
|
||||
Email-Adresse (Kontakt):
|
||||
</label>
|
||||
<TextInput
|
||||
placeholder="Email"
|
||||
defaultValue={contact}
|
||||
updateFormValue={(v) => setContact(v)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="telephone" className="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-1">
|
||||
Telefonnummer (Kontakt):
|
||||
</label>
|
||||
<TextInput
|
||||
placeholder="Telefonnummer"
|
||||
defaultValue={telephone}
|
||||
updateFormValue={(v) => setTelephone(v)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
}
|
||||
<div>
|
||||
<label htmlFor="nextAppointment" className="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-1">
|
||||
Nächste Termine:
|
||||
</label>
|
||||
<TextAreaInput
|
||||
placeholder="Nächste Termine"
|
||||
defaultValue={nextAppointment}
|
||||
updateFormValue={(v) => setNextAppointment(v)}
|
||||
inputStyle="tw-h-24"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="description" className="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-1">
|
||||
Gruppenbeschreibung:
|
||||
</label>
|
||||
<TextAreaInput
|
||||
placeholder="Beschreibung"
|
||||
defaultValue={item?.text ?? ""}
|
||||
updateFormValue={(v) => setText(v)}
|
||||
inputStyle="tw-h-48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!item.layer?.itemType.onepager &&
|
||||
|
||||
|
||||
@ -18,11 +18,15 @@ const flags = {
|
||||
)
|
||||
};
|
||||
|
||||
const SubHeader = ({ location, type, url, title }) => (
|
||||
const statusMapping = {
|
||||
'in_planning': 'in Planung',
|
||||
'paused': 'pausiert',
|
||||
};
|
||||
|
||||
const SubHeader = ({ type, status, url, title }) => (
|
||||
<div>
|
||||
<div className="tw-flex tw-items-center tw-mt-6">
|
||||
<span className="tw-text-sm tw-text-gray-600">{type}</span>
|
||||
<span className="tw-text-sm tw-text-gray-600 tw-ml-6">{location}</span>
|
||||
<span className="tw-text-sm tw-text-gray-600">{type}{(status && status !== 'active') ? ` (${statusMapping[status]})` : ''}</span>
|
||||
</div>
|
||||
<div className="tw-mt-4">
|
||||
<SocialShareBar url={url} title={title} />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user