implemented flex profiles

This commit is contained in:
Anton Tranelis 2024-11-06 20:11:17 +01:00
parent 3f701233a1
commit f296ddd606
18 changed files with 159 additions and 118 deletions

View File

@ -1,20 +1,15 @@
import { useState } from 'react'
import * as React from 'react'
interface ComboBoxProps {
id?: string
options: { value: string; label: string }[]
options: string[]
value: string
onValueChange: (newValue: string) => void
}
const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => {
// eslint-disable-next-line no-unused-vars
const [selectedValue, setSelectedValue] = useState(value)
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value
setSelectedValue(value)
onValueChange(value)
}
@ -25,8 +20,8 @@ const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) =>
onChange={handleChange}
>
{options.map((o) => (
<option value={o.value} key={o.value} selected={o.value === value}>
{o.label}
<option value={o} key={o} selected={o === value}>
{o}
</option>
))}
</select>

View File

@ -16,7 +16,7 @@ export const PopupStartEndInput = ({
updateEndValue,
}: StartEndInputProps) => {
return (
<div className='tw-grid tw-grid-cols-2 tw-gap-2 tw-mb-5'>
<div className='tw-grid tw-grid-cols-2 tw-gap-2'>
<TextInput
type='date'
placeholder='start'

View File

@ -20,7 +20,7 @@ export const PopupTextInput = ({
placeholder={placeholder}
inputStyle={style}
type='text'
containerStyle={'tw-mt-4 tw-mb-4'}
containerStyle={'tw-mt-4'}
></TextInput>
)
}

View File

@ -120,6 +120,20 @@
left: 4px;
width: 24px;
}
.flower-icon {
position: relative;
top: -35px;
left: 4px;
width: 24px;
}
.network-icon {
position: relative;
top: -35px;
left: 4px;
width: 24px;
}
.leaflet-popup-scrolled {
overflow-x: hidden;

View File

@ -108,8 +108,8 @@ export function ProfileForm() {
setState({
color: newColor,
id: item?.id ?? '',
group_type: item?.group_type ?? 'wuerdekompass',
status: item?.status ?? 'active',
group_type: item?.group_type ?? '',
status: item?.status ?? '',
name: item?.name ?? '',
subname: item?.subname ?? '',
text: item?.text ?? '',
@ -165,7 +165,7 @@ export function ProfileForm() {
></TabsForm>
)}
<div className='tw-mt-4 tw-mb-4'>
<div className='tw-mt-4'>
<button
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
onClick={() =>

View File

@ -10,7 +10,7 @@ export const ContactInfoForm = ({
setState: React.Dispatch<React.SetStateAction<any>>
}) => {
return (
<div className='tw-space-y-6'>
<div className='tw-mt-4 tw-space-y-4'>
<div>
<label
htmlFor='email'

View File

@ -4,7 +4,7 @@ import { Item } from '../../../types'
import { useEffect, useState } from 'react'
import { useItems } from '../../Map/hooks/useItems'
const ContactInfo = ({ item }: { item: Item }) => {
export const ContactInfoView = ({ item, heading }: { item: Item; heading: string }) => {
const appState = useAppState()
const [profileOwner, setProfileOwner] = useState<Item>()
const items = useItems()
@ -31,7 +31,7 @@ const ContactInfo = ({ item }: { item: Item }) => {
return (
<div className='tw-bg-base-200 tw-mb-6 tw-mt-6 tw-p-6'>
<h2 className='tw-text-lg tw-font-semibold'>Du hast Fragen?</h2>
<h2 className='tw-text-lg tw-font-semibold'>{heading}</h2>
<div className='tw-mt-4 tw-flex tw-items-center'>
{profileOwner?.image && (
<ConditionalLink url={'/item/' + profileOwner?.id}>
@ -101,8 +101,6 @@ const ContactInfo = ({ item }: { item: Item }) => {
)
}
export default ContactInfo
// eslint-disable-next-line react/prop-types
const ConditionalLink = ({ url, children }) => {
const params = new URLSearchParams(window.location.search)

View File

@ -2,12 +2,6 @@
import { Item } from '../../../types'
import SocialShareBar from './SocialShareBar'
const statusMapping = {
in_planning: 'in Planung',
paused: 'pausiert',
active: 'aktiv',
}
export const GroupSubHeaderView = ({
item,
share_base_url,
@ -20,10 +14,7 @@ export const GroupSubHeaderView = ({
{item.status && (
<div className='tw-mt-1.5'>
<span className='tw-text-sm tw-text-current tw-bg-base-300 tw-rounded tw-py-0.5 tw-px-2 tw-inline-flex tw-items-center tw-mr-2'>
<span
className={`tw-w-2 tw-h-2 ${item.status === 'in_planning' && 'tw-bg-blue-700'} ${item.status === 'paused' && 'tw-bg-orange-400'} ${item.status === 'active' && 'tw-bg-green-500'} tw-rounded-full tw-mr-1.5`}
></span>
{statusMapping[item.status]}
{item.status}
</span>
</div>
)}

View File

@ -4,80 +4,46 @@ import { Item } from '../../../types'
import { useEffect } from 'react'
import { FormState } from '../Templates/OnepagerForm'
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' },
]
type groupType = {
groupTypes_id: {
name: string
color: string
image: string
markerIcon: string
}
}
export const GroupSubheaderForm = ({
state,
setState,
item,
groupStates,
groupTypes,
}: {
state: FormState
setState: React.Dispatch<React.SetStateAction<any>>
item: Item
groupStates?: string[]
groupTypes?: groupType[]
}) => {
useEffect(() => {
switch (state.group_type) {
case 'wuerdekompass':
setState((prevState) => ({
...prevState,
color: item?.layer?.menuColor || '#1A5FB4',
marker_icon: 'group',
image: '59e6a346-d1ee-4767-9e42-fc720fb535c9',
}))
break
case 'themenkompass':
setState((prevState) => ({
...prevState,
color: '#26A269',
marker_icon: 'group',
image: '59e6a346-d1ee-4767-9e42-fc720fb535c9',
}))
break
case 'liebevoll.jetzt':
setState((prevState) => ({
...prevState,
color: '#E8B620',
marker_icon: 'liebevoll.jetzt',
image: 'e735b96c-507b-471c-8317-386ece0ca51d',
}))
break
default:
break
if (groupTypes && groupStates) {
const groupType = groupTypes.find((gt) => gt.groupTypes_id.name === state.group_type)
console.log(state.group_type)
setState((prevState) => ({
...prevState,
color: groupType?.groupTypes_id.color || groupTypes[0].groupTypes_id.color,
marker_icon: groupType?.groupTypes_id.markerIcon || groupTypes[0].groupTypes_id.markerIcon,
image: groupType?.groupTypes_id.image || groupTypes[0].groupTypes_id.image,
status: state.status || groupStates[0],
group_type: state.group_type || groupTypes[0].groupTypes_id.name,
}))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.group_type])
}, [state.group_type, groupTypes])
return (
<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-500 tw-mb-1'
>
Gruppenart:
</label>
<ComboBoxInput
id='groupType'
options={typeMapping}
value={state.group_type}
onValueChange={(v) =>
setState((prevState) => ({
...prevState,
group_type: v,
}))
}
/>
</div>
<div>
<label
htmlFor='status'
@ -87,7 +53,7 @@ export const GroupSubheaderForm = ({
</label>
<ComboBoxInput
id='status'
options={statusMapping}
options={groupStates || []}
value={state.status}
onValueChange={(v) =>
setState((prevState) => ({
@ -97,6 +63,25 @@ export const GroupSubheaderForm = ({
}
/>
</div>
<div>
<label
htmlFor='groupType'
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
>
Gruppenart:
</label>
<ComboBoxInput
id='groupType'
options={groupTypes?.map((gt) => gt.groupTypes_id.name) || []}
value={state.group_type}
onValueChange={(v) =>
setState((prevState) => ({
...prevState,
group_type: v,
}))
}
/>
</div>
</div>
)
}

View File

@ -0,0 +1,30 @@
import * as React from 'react'
import { PopupStartEndInput } from '../../Map'
import { Item } from '../../../types'
export const ProfileStartEndForm = ({
item,
setState,
}: {
item: Item
setState: React.Dispatch<React.SetStateAction<any>>
}) => {
return (
<PopupStartEndInput
item={item}
showLabels={false}
updateEndValue={(e) =>
setState((prevState) => ({
...prevState,
end: e,
}))
}
updateStartValue={(s) =>
setState((prevState) => ({
...prevState,
start: s,
}))
}
></PopupStartEndInput>
)
}

View File

@ -0,0 +1,11 @@
import * as React from 'react'
import { StartEndView } from '../../Map'
import { Item } from '../../../types'
export const ProfileStartEndView = ({ item }: { item: Item }) => {
return (
<div className='tw-mt-2 tw-px-6 tw-max-w-xs'>
<StartEndView item={item}></StartEndView>
</div>
)
}

View File

@ -1,4 +1,3 @@
/* eslint-disable camelcase */
import * as React from 'react'
import { TextAreaInput } from '../../Input'
import { FormState } from '../Templates/OnepagerForm'
@ -8,29 +7,33 @@ import { useEffect, useState } from 'react'
export const ProfileTextForm = ({
state,
setState,
data_field,
section_name,
dataField,
heading,
size,
hideInputLabel,
}: {
state: FormState
setState: React.Dispatch<React.SetStateAction<any>>
data_field?: string
section_name: string
dataField?: string
heading: string
size: string
hideInputLabel: boolean
}) => {
const [field, setField] = useState<string>(data_field || 'text')
const [field, setField] = useState<string>(dataField || 'text')
useEffect(() => {
if (!data_field) {
if (!dataField) {
setField('text')
}
}, [data_field])
}, [dataField])
return (
<div>
<div className='tw-h-full tw-flex tw-flex-col tw-mt-4'>
<label
htmlFor='nextAppointment'
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
>
{section_name || 'Text'}:
{heading || 'Text'}:
</label>
<TextAreaInput
placeholder={'...'}
@ -41,7 +44,9 @@ export const ProfileTextForm = ({
[field]: v,
}))
}
inputStyle='tw-h-24'
labelStyle={hideInputLabel ? 'tw-hidden' : ''}
containerStyle={size === 'full' ? 'tw-grow tw-h-full' : ''}
inputStyle={size === 'full' ? 'tw-h-full' : 'tw-h-24'}
/>
</div>
)

View File

@ -1,22 +1,25 @@
/* eslint-disable camelcase */
import { Item } from '../../../types'
import { getValue } from '../../../Utils/GetValue'
import { TextView } from '../../Map'
export const ProfileTextView = ({
item,
data_field,
section_name,
dataField,
heading,
hideWhenEmpty,
}: {
item: Item
data_field: string
section_name: string
dataField: string
heading: string
hideWhenEmpty: boolean
}) => {
return (
<div className='tw-my-10 tw-mt-2 tw-px-6'>
<h2 className='tw-text-lg tw-font-semibold'>{section_name}</h2>
{!(getValue(item, dataField) === '' && hideWhenEmpty) && (
<h2 className='tw-text-lg tw-font-semibold'>{heading}</h2>
)}
<div className='tw-mt-2 tw-text-sm'>
<TextView rawText={data_field ? getValue(item, data_field) : getValue(item, 'text')} />
<TextView rawText={dataField ? getValue(item, dataField) : getValue(item, 'text')} />
</div>
</div>
)

View File

@ -4,11 +4,13 @@ import { FormState } from './OnepagerForm'
import { GroupSubheaderForm } from '../Subcomponents/GroupSubheaderForm'
import { ContactInfoForm } from '../Subcomponents/ContactInfoForm'
import { ProfileTextForm } from '../Subcomponents/ProfileTextForm'
import { ProfileStartEndForm } from '../Subcomponents/ProfileStartEndForm'
const componentMap = {
group_subheaders: GroupSubheaderForm,
groupSubheaders: GroupSubheaderForm,
texts: ProfileTextForm,
contact_infos: ContactInfoForm,
contactInfos: ContactInfoForm,
startEnd: ProfileStartEndForm,
// weitere Komponenten hier
}
@ -22,14 +24,15 @@ export const FlexForm = ({
item: Item
}) => {
return (
<div className='tw-space-y-6 tw-mt-6'>
{item.layer?.itemType.profile_template.map((templateItem) => {
<div className='tw-mt-6 tw-flex tw-flex-col tw-h-full'>
{item.layer?.itemType.profileTemplate.map((templateItem) => {
const TemplateComponent = componentMap[templateItem.collection]
return TemplateComponent ? (
<TemplateComponent
key={templateItem.id}
state={state}
setState={setState}
item={item}
{...templateItem.item}
/>
) : (

View File

@ -1,12 +1,14 @@
import { GroupSubHeaderView } from '../Subcomponents/GroupSubHeaderView'
import ContactInfo from '../Subcomponents/ContactInfo'
import { ProfileTextView } from '../Subcomponents/ProfileTextView'
import { ContactInfoView } from '../Subcomponents/ContactInfoView'
import { Item } from '../../../types'
import { ProfileStartEndView } from '../Subcomponents/ProfileStartEndView'
const componentMap = {
group_subheaders: GroupSubHeaderView,
groupSubheaders: GroupSubHeaderView,
texts: ProfileTextView,
contact_infos: ContactInfo,
contactInfos: ContactInfoView,
startEnd: ProfileStartEndView,
// weitere Komponenten hier
}
@ -14,7 +16,7 @@ export const FlexView = ({ item }: { item: Item }) => {
console.log(item)
return (
<div className='tw-h-full tw-overflow-y-auto fade'>
{item.layer?.itemType.profile_template.map((templateItem) => {
{item.layer?.itemType.profileTemplate.map((templateItem) => {
const TemplateComponent = componentMap[templateItem.collection]
return TemplateComponent ? (
<TemplateComponent key={templateItem.id} item={item} {...templateItem.item} />

View File

@ -1,6 +1,6 @@
import { Item } from '../../../types'
import { TextView } from '../../Map'
import ContactInfo from '../Subcomponents/ContactInfo'
import { ContactInfoView } from '../Subcomponents/ContactInfoView'
import { GroupSubHeaderView } from '../Subcomponents/GroupSubHeaderView'
export const OnepagerView = ({ item }: { item: Item }) => {
@ -10,7 +10,7 @@ export const OnepagerView = ({ item }: { item: Item }) => {
item={item}
share_base_url={`https://www.wuerdekompass.org/aktivitaeten/gruppensuche/#/gruppe/${item.slug}`}
/>
{item.user_created.first_name && <ContactInfo item={item} />}
{item.user_created.first_name && <ContactInfoView heading='Du hast Fragen?' item={item} />}
{/* Description Section */}
<div className='tw-my-10 tw-mt-2 tw-px-6 tw-text-sm '>
<TextView rawText={item.text || 'Keine Beschreibung vorhanden'} />

View File

@ -168,13 +168,13 @@ export const onUpdateItem = async (
text: state.text,
...(state.color && { color: state.color }),
position: item.position,
...(state.groupType && { group_type: state.groupType }),
...(state.group_type && { group_type: state.group_type }),
...(state.status && { status: state.status }),
contact: state.contact,
telephone: state.telephone,
...(state.end && { end: state.end }),
...(state.start && { start: state.start }),
...(state.markerIcon && { markerIcon: state.markerIcon }),
...(state.marker_icon && { markerIcon: state.marker_icon }),
next_appointment: state.next_appointment,
...(state.image.length > 10 && { image: state.image }),
...(state.offers.length > 0 && { offers: offerUpdates }),

View File

@ -54,6 +54,10 @@ const addIcon = (icon: string) => {
return '<svg class="group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff" width="1.6em" height="1.6em"><path d="M11.25 5.337c0-.355-.186-.676-.401-.959a1.647 1.647 0 0 1-.349-1.003c0-1.036 1.007-1.875 2.25-1.875S15 2.34 15 3.375c0 .369-.128.713-.349 1.003-.215.283-.401.604-.401.959 0 .332.278.598.61.578 1.91-.114 3.79-.342 5.632-.676a.75.75 0 0 1 .878.645 49.17 49.17 0 0 1 .376 5.452.657.657 0 0 1-.66.664c-.354 0-.675-.186-.958-.401a1.647 1.647 0 0 0-1.003-.349c-1.035 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401.31 0 .557.262.534.571a48.774 48.774 0 0 1-.595 4.845.75.75 0 0 1-.61.61c-1.82.317-3.673.533-5.555.642a.58.58 0 0 1-.611-.581c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.035-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959a.641.641 0 0 1-.658.643 49.118 49.118 0 0 1-4.708-.36.75.75 0 0 1-.645-.878c.293-1.614.504-3.257.629-4.924A.53.53 0 0 0 5.337 15c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.036 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.369 0 .713.128 1.003.349.283.215.604.401.959.401a.656.656 0 0 0 .659-.663 47.703 47.703 0 0 0-.31-4.82.75.75 0 0 1 .83-.832c1.343.155 2.703.254 4.077.294a.64.64 0 0 0 .657-.642Z" /></svg>'
case 'staff-snake':
return '<svg class="staff-snake-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 384 512" height="1.4em" width="1.4em" xmlns="http://www.w3.org/2000/svg"><path d="M222.6 43.2l-.1 4.8H288c53 0 96 43 96 96s-43 96-96 96H248V160h40c8.8 0 16-7.2 16-16s-7.2-16-16-16H248 220l-4.5 144H256c53 0 96 43 96 96s-43 96-96 96H240V384h16c8.8 0 16-7.2 16-16s-7.2-16-16-16H213l-3.1 99.5L208.5 495l0 1c-.3 8.9-7.6 16-16.5 16s-16.2-7.1-16.5-16l0-1-1-31H136c-22.1 0-40-17.9-40-40s17.9-40 40-40h36l-1-32H152c-53 0-96-43-96-96c0-47.6 34.6-87.1 80-94.7V256c0 8.8 7.2 16 16 16h16.5L164 128H136 122.6c-9 18.9-28.3 32-50.6 32H56c-30.9 0-56-25.1-56-56S25.1 48 56 48h8 8 89.5l-.1-4.8L161 32c0-.7 0-1.3 0-1.9c.5-16.6 14.1-30 31-30s30.5 13.4 31 30c0 .6 0 1.3 0 1.9l-.4 11.2zM64 112a16 16 0 1 0 0-32 16 16 0 1 0 0 32z"></path></svg>'
case 'flower':
return '<svg class="flower-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M210.35,129.36c-.81-.47-1.7-.92-2.62-1.36.92-.44,1.81-.89,2.62-1.36a40,40,0,1,0-40-69.28c-.81.47-1.65,1-2.48,1.59.08-1,.13-2,.13-3a40,40,0,0,0-80,0c0,.94,0,1.94.13,3-.83-.57-1.67-1.12-2.48-1.59a40,40,0,1,0-40,69.28c.81.47,1.7.92,2.62,1.36-.92.44-1.81.89-2.62,1.36a40,40,0,1,0,40,69.28c.81-.47,1.65-1,2.48-1.59-.08,1-.13,2-.13,2.95a40,40,0,0,0,80,0c0-.94-.05-1.94-.13-2.95.83.57,1.67,1.12,2.48,1.59A39.79,39.79,0,0,0,190.29,204a40.43,40.43,0,0,0,10.42-1.38,40,40,0,0,0,9.64-73.28ZM104,128a24,24,0,1,1,24,24A24,24,0,0,1,104,128Zm74.35-56.79a24,24,0,1,1,24,41.57c-6.27,3.63-18.61,6.13-35.16,7.19A40,40,0,0,0,154.53,98.1C163.73,84.28,172.08,74.84,178.35,71.21ZM128,32a24,24,0,0,1,24,24c0,7.24-4,19.19-11.36,34.06a39.81,39.81,0,0,0-25.28,0C108,75.19,104,63.24,104,56A24,24,0,0,1,128,32ZM44.86,80a24,24,0,0,1,32.79-8.79c6.27,3.63,14.62,13.07,23.82,26.89A40,40,0,0,0,88.81,120c-16.55-1.06-28.89-3.56-35.16-7.18A24,24,0,0,1,44.86,80ZM77.65,184.79a24,24,0,1,1-24-41.57c6.27-3.63,18.61-6.13,35.16-7.19a40,40,0,0,0,12.66,21.87C92.27,171.72,83.92,181.16,77.65,184.79ZM128,224a24,24,0,0,1-24-24c0-7.24,4-19.19,11.36-34.06a39.81,39.81,0,0,0,25.28,0C148,180.81,152,192.76,152,200A24,24,0,0,1,128,224Zm83.14-48a24,24,0,0,1-32.79,8.79c-6.27-3.63-14.62-13.07-23.82-26.89A40,40,0,0,0,167.19,136c16.55,1.06,28.89,3.56,35.16,7.18A24,24,0,0,1,211.14,176Z"></path></svg>'
case 'network':
return '<svg class="network-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M212,200a36,36,0,1,1-69.85-12.25l-53-34.05a36,36,0,1,1,0-51.4l53-34a36.09,36.09,0,1,1,8.67,13.45l-53,34.05a36,36,0,0,1,0,24.5l53,34.05A36,36,0,0,1,212,200Z"></path></svg>'
default:
return ''
}