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:
Sebastian Stein 2024-07-10 13:22:42 +02:00
parent b191466f39
commit 3b7aa5722f
5 changed files with 227 additions and 67 deletions

View 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;

View File

@ -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>
)
}

View File

@ -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>*/}
{/*)}*/}
</>
}

View File

@ -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 &&

View File

@ -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} />