profile abstraction and customizable user type

This commit is contained in:
Anton Tranelis 2024-07-15 11:40:55 +02:00
parent d2ef363192
commit d74374d291
4 changed files with 139 additions and 114 deletions

View File

@ -16,7 +16,7 @@ import { LeafletRefsProvider } from '../Map/hooks/useLeafletRefs'
import { SelectPositionProvider } from '../Map/hooks/useSelectPosition'
import { ClusterRefProvider } from '../Map/hooks/useClusterRef'
export function AppShell({ appName, children, assetsApi }: { appName: string, children: React.ReactNode, assetsApi: AssetsApi }) {
export function AppShell({ appName, children, assetsApi, userType }: { appName: string, children: React.ReactNode, assetsApi: AssetsApi, userType: string }) {
// Create a client
const queryClient = new QueryClient()
@ -47,7 +47,7 @@ export function AppShell({ appName, children, assetsApi }: { appName: string, ch
pauseOnHover
theme="light" />
<div className='tw-flex tw-flex-col tw-h-full'>
<NavBar appName={appName}></NavBar>
<NavBar userType={userType} appName={appName}></NavBar>
<div id="app-content" className="tw-flex-grow">
{children}
</div>

View File

@ -7,7 +7,7 @@ import { useItems } from "../Map/hooks/useItems";
import { Item } from "../../types";
export default function NavBar({ appName}: { appName: string }) {
export default function NavBar({ appName, userType}: { appName: string, userType: string }) {
const { isAuthenticated, user, logout } = useAuth();
@ -16,7 +16,7 @@ export default function NavBar({ appName}: { appName: string }) {
const items = useItems();
useEffect(() => {
const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === "user");
const profile = user && items.find(i => (i.user_created?.id === user.id) && i.layer?.itemType.name === userType);
profile ? setUserProfile(profile) : setUserProfile({id: crypto.randomUUID(), name: user?.first_name, text: ""});
}, [user, items])

View File

@ -7,7 +7,7 @@ import { useMap } from 'react-leaflet';
import { LatLng } from 'leaflet';
import { StartEndView, TextView } from '../Map';
import { useAddTag, useTags } from '../Map/hooks/useTags';
import { useAddFilterTag, useResetFilterTags } from '../Map/hooks/useFilter';
import { useAddFilterTag, useResetFilterTags } from '../Map/hooks/useFilter';
import { useHasUserPermission } from '../Map/hooks/usePermissions';
import { hashTagRegex } from '../../Utils/HashTagRegex';
import { randomColor } from '../../Utils/RandomColor';
@ -26,7 +26,7 @@ import RelationCard from "./RelationCard";
import ContactInfo from "./ContactInfo";
import ProfileSubHeader from "./ProfileSubHeader";
export function OverlayItemProfile() {
export function OverlayItemProfile({ userType }: { userType: string }) {
const [updatePermission, setUpdatePermission] = useState<boolean>(false);
const [relations, setRelations] = useState<Array<Item>>([]);
@ -72,9 +72,16 @@ export function OverlayItemProfile() {
useEffect(() => {
setProfile(items.find(i => (i.user_created?.id === item.user_created?.id) && i.layer?.itemType.name === "user"));
console.log(userType);
setProfile(items.find(i => (i.user_created?.id === item.user_created?.id) && i.layer?.itemType.name === userType));
}, [item, items])
useEffect(() => {
console.log(profile);
}, [profile])
const updateActiveTab = (id: number) => {
@ -171,7 +178,7 @@ export function OverlayItemProfile() {
useEffect(() => {
item && hasUserPermission("items", "update", item) && setUpdatePermission(true);
item && hasUserPermission("items", "update", item) && setUpdatePermission(true);
}, [item])
@ -290,142 +297,154 @@ export function OverlayItemProfile() {
let groupType = item.group_type ? item.group_type : 'default';
let groupTypeText = typeMapping[groupType];
const [template, setTemplate] = useState<string>("")
useEffect(() => {
setTemplate(item.layer?.itemType.template || userType);
}, [userType, item])
return (
<>
{item &&
<MapOverlayPage key={item.id}
className={`${item.layer?.itemType.onepager && '!tw-p-0'} 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-min-w-80 tw-max-w-3xl !tw-left-0 sm:!tw-left-auto tw-top-0 tw-bottom-0 tw-transition-opacity tw-duration-500 ${!selectPosition ? 'tw-opacity-100 tw-pointer-events-auto' : 'tw-opacity-0 tw-pointer-events-none'}`}>
className={`${template == "onepager" && '!tw-p-0'} 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-min-w-80 tw-max-w-3xl !tw-left-0 sm:!tw-left-auto tw-top-0 tw-bottom-0 tw-transition-opacity tw-duration-500 ${!selectPosition ? 'tw-opacity-100 tw-pointer-events-auto' : 'tw-opacity-0 tw-pointer-events-none'}`}>
<>
<div className="tw-px-6 tw-pt-6">
<div className={`${template=="onepager" && "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
{template=="onepager" && <ProfileSubHeader
type={groupTypeText}
status={item.status}
url={window.location.href}
title={item.name}
/>
/>}
</div>
<div className='tw-h-full tw-overflow-y-auto fade'>
{item.layer?.itemType.onepager &&
<>
{item.user_created.first_name && (
<ContactInfo name={profile?.name ? profile.name : item.user_created.first_name} avatar={profile?.image ? profile.image : item.user_created.avatar} email={item.contact} telephone={item.telephone} />
)}
{template == "onepager" &&
<>
{item.user_created.first_name && (
<ContactInfo name={profile?.name ? profile.name : item.user_created.first_name} avatar={profile?.image ? profile.image : item.user_created.avatar} email={item.contact} telephone={item.telephone} />
)}
{/* Description Section */}
<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>
{/* Next Appointment Section */}
{item.next_appointment && (
<div className="tw-my-10 tw-px-6">
<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>
{/* Description Section */}
<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 && (*/}
{/* <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>*/}
{/*)}*/}
</>
}
{/* Next Appointment Section */}
{item.next_appointment && (
<div className="tw-my-10 tw-px-6">
<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>
)};
{!item.layer?.itemType.onepager &&
{/* 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>*/}
{/*)}*/}
</>
}
{template == "simple" &&
<div className='tw-mt-8'>
<TextView item={item} />
</div>
}
{template == "tabs" &&
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2 tw-mb-2">
<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="Info" checked={activeTab == 1 && true}
onChange={() => updateActiveTab(1)}/>
className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`}
aria-label="Info" checked={activeTab == 1 && true}
onChange={() => updateActiveTab(1)} />
<div role="tabpanel"
className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
{item.layer?.itemType.show_start_end &&
<div className='tw-max-w-xs'><StartEndView item={item}></StartEndView></div>
}
<TextView item={item}/>
<TextView item={item} />
</div>
{item.layer?.itemType.offers_and_needs &&
{item.layer?.itemType.offers_and_needs &&
<>
<>
<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 == 3 && true} onChange={() => updateActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-overflow-y-auto fade tw-pt-4 tw-pb-1" >
<div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1'>
{
offers.length > 0 ?
<div className='tw-col-span-1'>
<h3 className='-tw-mb-2'>Offers</h3>
< div className='tw-flex tw-flex-wrap tw-mb-4'>
{
offers.map(o => <TagView key={o?.id} tag={o} onClick={() => {
console.log(o);
addFilterTag(o)
}} />)
}
</div>
</div> : ""
}
{
needs.length > 0 ?
<div className='tw-col-span-1'>
<h3 className='-tw-mb-2 tw-col-span-1'>Needs</h3>
< div className='tw-flex tw-flex-wrap tw-mb-4'>
{
needs.map(n => <TagView key={n?.id} tag={n} onClick={() => addFilterTag(n)} />)
}
</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 == 3 && true} onChange={() => updateActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-overflow-y-auto fade tw-pt-4 tw-pb-1" >
<div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1'>
{
offers.length > 0 ?
<div className='tw-col-span-1'>
<h3 className='-tw-mb-2'>Offers</h3>
< div className='tw-flex tw-flex-wrap tw-mb-4'>
{
offers.map(o => <TagView key={o?.id} tag={o} onClick={() => {
console.log(o);
addFilterTag(o)
}} />)
}
</div>
</div> : ""
}
{
needs.length > 0 ?
<div className='tw-col-span-1'>
<h3 className='-tw-mb-2 tw-col-span-1'>Needs</h3>
< div className='tw-flex tw-flex-wrap tw-mb-4'>
{
needs.map(n => <TagView key={n?.id} tag={n} onClick={() => addFilterTag(n)} />)
}
</div>
</div> : ""
}
</div>
</div>
</div>
</div>
</>
</>
}
}
{item.layer?.itemType.relations &&
<>
<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="Relations" checked={activeTab == 7 && true} onChange={() => updateActiveTab(7)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mx-4 tw-overflow-x-hidden">
<div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2'>
{relations && relations.map(i =>
{item.layer?.itemType.relations &&
<>
<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="Relations" checked={activeTab == 7 && true} onChange={() => updateActiveTab(7)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto tw-pt-4 tw-pb-1 -tw-mx-4 tw-overflow-x-hidden">
<div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2'>
{relations && relations.map(i =>
<div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-mx-4 tw-p-6 tw-mb-4' onClick={() => navigate('/item/' + i.id)}>
<LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={unlinkItem} loading={loading} />
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
<TextView truncate item={i} />
<div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-mx-4 tw-p-6 tw-mb-4' onClick={() => navigate('/item/' + i.id)}>
<LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={unlinkItem} loading={loading} />
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
<TextView truncate item={i} />
</div>
</div>
</div>
)}
{updatePermission && <ActionButton collection="items" item={item} existingRelations={relations} triggerItemSelected={linkItem} colorField={item.layer.itemColorField}></ActionButton>}
)}
{updatePermission && <ActionButton collection="items" item={item} existingRelations={relations} triggerItemSelected={linkItem} colorField={item.layer.itemColorField}></ActionButton>}
</div>
</div>
</div>
</div>
</>
}
</div>
</>
}
</div>
}
</div>
</>

View File

@ -23,7 +23,7 @@ import { useHasUserPermission } from '../Map/hooks/usePermissions';
export function OverlayItemProfileSettings() {
export function OverlayItemProfileSettings({ userType }: { userType: string }) {
const typeMapping = [
{ value: 'wuerdekompass', label: 'Regional-Gruppe' },
@ -112,7 +112,7 @@ export function OverlayItemProfileSettings() {
const item = items.find(i => i.id === itemId);
item && setItem(item);
const layer = layers.find(l => l.itemType.name == "user")
const layer = layers.find(l => l.itemType.name == userType)
!item && setItem({ id: crypto.randomUUID(), name: user ? user.first_name : "", text: "", layer: layer, new: true })
@ -203,7 +203,7 @@ export function OverlayItemProfileSettings() {
position: item.position,
contact: contact,
telephone: telephone,
...markerIcon && {markerIcon: markerIcon},
...markerIcon && { markerIcon: markerIcon },
next_appointment: nextAppointment,
...image.length > 10 && { image: image },
...offers.length > 0 && { offers: offer_updates },
@ -314,10 +314,11 @@ export function OverlayItemProfileSettings() {
}
useEffect(() => {
console.log(item);
const [template, setTemplate] = useState<string>("")
}, [item])
useEffect(() => {
setTemplate(item.layer?.itemType.template || userType);
}, [userType, item])
@ -335,7 +336,7 @@ export function OverlayItemProfileSettings() {
</div>
</div>
{item.layer?.itemType.onepager && (
{template == "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>
@ -410,13 +411,18 @@ export function OverlayItemProfileSettings() {
</div>
)}
{!item.layer?.itemType.onepager &&
{template == "simple" &&
<TextAreaInput placeholder="About me ..." defaultValue={item?.text ? item.text : ""} updateFormValue={(v) => { console.log(v); setText(v) }} containerStyle='tw-mt-8 tw-h-full' inputStyle='tw-h-full' />
}
{template == "tabs" &&
<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="Info" checked={activeTab == 1 && true} onChange={() => updateActiveTab(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 tw-border-none">
<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' />
<TextAreaInput placeholder="About me ..." 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>
{item.layer?.itemType.offers_and_needs &&
<>