profiles, reverse, geocoder, ...

This commit is contained in:
Anton Tranelis 2024-06-27 16:09:09 +02:00
parent c5c6374d6d
commit 4181460ba1
7 changed files with 100 additions and 37 deletions

View File

@ -6,11 +6,13 @@ import { useAssetApi } from '../../../AppShell/hooks/useAssets'
import DialogModal from "../../../Templates/DialogModal";
import { useNavigate } from "react-router-dom";
import { useMap } from "react-leaflet";
import { reverseGeocode } from "../../../../Utils/ReverseGeocoder";
import { useEffect } from "react";
export function HeaderView({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemSubnameField, itemAvatarField, loading, hideMenu = false, big = false, truncateSubname = true, hideSubname = false }: {
export function HeaderView({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemSubnameField, itemAvatarField, loading, hideMenu = false, big = false, truncateSubname = true, hideSubname = false, showAddress = false }: {
item: Item,
api?: ItemsApi<any>,
editCallback?: any,
@ -23,7 +25,8 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
hideMenu?: boolean,
big?: boolean,
hideSubname?: boolean,
truncateSubname?:boolean
truncateSubname?:boolean,
showAddress?: boolean
}) {
@ -37,6 +40,17 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField);
const subtitle = itemSubnameField ? getValue(item, itemSubnameField) : item.layer?.itemSubnameField && item && getValue(item, item.layer?.itemSubnameField);
const [address, setAdress] = React.useState<string>("");
useEffect(() => {
item.position && reverseGeocode(item.position?.coordinates[1],item.position?.coordinates[0]).then(address => {
setAdress(address);
});
}, [item])
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
@ -62,6 +76,9 @@ export function HeaderView({ item, api, editCallback, deleteCallback, setPositio
<div className={`${big ? "xl:tw-text-3xl tw-text-2xl" : "tw-text-xl"} tw-font-semibold tw-truncate`}>
{title}
</div>
{showAddress && address && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && "tw-truncate"}`}>
{address}
</div>}
{subtitle && !hideSubname && <div className={`tw-text-xs tw-text-gray-500 ${truncateSubname && "tw-truncate"}`}>
{subtitle}
</div>}

View File

@ -1,11 +1,16 @@
import { useAssetApi } from "../AppShell/hooks/useAssets";
const ContactInfo = ({ contact }) => (
const ContactInfo = ({ email, name, avatar } : {email: 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-w-20 tw-h-20 tw-bg-gray-200 tw-rounded-full tw-mr-5 tw-flex tw-items-center tw-justify-center">
{contact.avatarSrc ? (
<img src={contact.avatarSrc} alt={contact.name} className="tw-w-full tw-h-full tw-rounded-full" />
{avatar ? (
<img src={assetsApi.url+avatar} alt={name} className="tw-w-full tw-h-full tw-rounded-full" />
) : (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="tw-w-6 tw-h-6"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@ -16,8 +21,8 @@ const ContactInfo = ({ contact }) => (
)}
</div>
<div className="tw-text-sm">
<p className="tw-font-semibold">{contact.name}</p>
<a href={`mailto:${contact.email}`} className="tw-mt-2 tw-text-green-500 tw-flex tw-items-center">
<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"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
className="tw-w-4 tw-h-4 tw-mr-1">
@ -25,11 +30,11 @@ const ContactInfo = ({ contact }) => (
<polyline points="22,6 12,13 2,6"></polyline>
</svg>
{contact.email}
{email}
</a>
</div>
</div>
</div>
);
)}
export default ContactInfo;

View File

@ -25,6 +25,7 @@ import { TagView } from '../Templates/TagView';
import RelationCard from "./RelationCard";
import ContactInfo from "./ContactInfo";
import ProfileSubHeader from "./ProfileSubHeader";
import SocialShareBar from './SocialShareBar';
export function OverlayItemProfile() {
@ -297,41 +298,33 @@ export function OverlayItemProfile() {
};
return (
<>
{item &&
<MapOverlayPage key={item.id} padding={false}
className={`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'}`}>
<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'}`}>
<>
<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} />
<div className={`${item.layer?.itemType.onepager && 'tw-p-4'}`}>
<HeaderView showAddress api={item.layer?.api} item={item} deleteCallback={handleDelete} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate("/") }} big truncateSubname={false} />
<SocialShareBar url={""} title={"title"} />
</div>
<div className='tw-h-full'>
<div className='tw-h-full tw-overflow-y-auto fade'>
{item.layer?.itemType.onepager &&
<>
<ProfileSubHeader
location={d.location}
country={d.country}
countryCode={d.countryCode}
url={d.url}
title={d.title}
/>
{d.contact && (
<ContactInfo contact={d.contact}/>
)}
{/* Description Section */}
<div className="tw-my-10 tw-px-6">
<h2 className="tw-text-lg tw-font-semibold">Beschreibung</h2>
<p className="tw-mt-2 tw-text-sm tw-text-gray-600">
{d.description ?? 'Keine Beschreibung vorhanden'}
</p>
</div>
<TextView item={item}></TextView>
{d.contact && (
<ContactInfo name={item.user_created.first_name} avatar={item.user_created.avatar} email={item.contact}/>
)}
{/* Relations Section */}
{d.relations && (

View File

@ -3,7 +3,6 @@ import SocialShareButton from './SocialShareButton';
const SocialShareBar = ({ url, title, platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email'] }) => {
return (
<div className="tw-flex tw-items-center tw-justify-end tw-space-x-2">
<span className="tw-text-sm tw-font-medium tw-text-gray-700">Teilen:</span>
<div className="tw-flex tw-space-x-2">
{platforms.map((platform) => (
<SocialShareButton

9
src/Components/README.md Normal file
View File

@ -0,0 +1,9 @@
**AppShell** provides componentes to structure the overall layout of a singlepage application including Navbar and Sidebar
**Auth** provides the UI components for Login, Signup, Password Reset and the useAuth hook, which handls all the authentification logic and provides the user context
**Gaming** provides components for gamification
**Input**
**Map**

View File

@ -3,7 +3,7 @@ import * as L from 'leaflet';
import * as React from 'react'
import { useNavigate } from 'react-router-dom';
export function MapOverlayPage({ children, className, backdrop, card = true, padding = true }: { children: React.ReactNode, className?: string, backdrop?: boolean, card?:boolean, padding?:boolean }) {
export function MapOverlayPage({ children, className, backdrop, card = true }: { children: React.ReactNode, className?: string, backdrop?: boolean, card?:boolean }) {
const closeScreen = () => {
@ -32,7 +32,7 @@ export function MapOverlayPage({ children, className, backdrop, card = true, pad
return (
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && "tw-z-[2000]"}`}>
<div ref={backdropRef} className={`${backdrop && "tw-backdrop-brightness-75"} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`} >
<div ref={overlayRef} className={`${card && "tw-card tw-card-body"} ${padding ? "tw-p-6" : "tw-p-0"} tw-shadow-xl tw-bg-base-100 ${className && className} ${!backdrop && "tw-z-[2000]"} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
<div ref={overlayRef} className={`${card && "tw-card tw-card-body"} tw-shadow-xl tw-bg-base-100 tw-p-6 ${className && className} ${!backdrop && "tw-z-[2000]"} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
{children}
<button className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2" onClick={() => closeScreen()}></button>
</div>
@ -40,4 +40,3 @@ export function MapOverlayPage({ children, className, backdrop, card = true, pad
</div>
)
}

View File

@ -0,0 +1,41 @@
import axios from 'axios';
interface ReverseGeocodeResponse {
place_id: number;
licence: string;
osm_type: string;
osm_id: number;
lat: string;
lon: string;
display_name: string;
address: {
road?: string;
house_number?: string;
city?: string;
town?: string;
village?: string;
[key: string]: string | undefined;
};
boundingbox: string[];
}
export async function reverseGeocode(lat: number, lon: number): Promise<string> {
const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&addressdetails=1`;
try {
const response = await axios.get<ReverseGeocodeResponse>(url);
const address = response.data.address;
// Extrahiere Straße, Hausnummer und Ort
const street = address.road || '';
const houseNumber = address.house_number || '';
const city = address.city || address.town || address.village || '';
// Formatiere die Adresse
const formattedAddress = `${street} ${houseNumber}, ${city}`.trim();
return formattedAddress || '';
} catch (error) {
console.error('Error:', error);
return '';
}
}