basic calendart ui and tabs in profile

This commit is contained in:
Anton Tranelis 2024-02-22 11:11:08 +01:00
parent 824075af2e
commit c15f7a897d
12 changed files with 232 additions and 27 deletions

10
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@tanstack/react-query": "^5.17.8",
"@types/offscreencanvas": "^2019.7.1",
"axios": "^1.6.5",
"date-fns": "^3.3.1",
"leaflet": "^1.9.4",
"leaflet.locatecontrol": "^0.79.0",
"prop-types": "^15.8.1",
@ -1308,6 +1309,15 @@
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/date-fns": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
"integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",

View File

@ -46,6 +46,7 @@
"@tanstack/react-query": "^5.17.8",
"@types/offscreencanvas": "^2019.7.1",
"axios": "^1.6.5",
"date-fns": "^3.3.1",
"leaflet": "^1.9.4",
"leaflet.locatecontrol": "^0.79.0",
"prop-types": "^15.8.1",

View File

@ -9,11 +9,17 @@ import { getValue } from '../../../../Utils/GetValue';
import remarkBreaks from 'remark-breaks'
import { decodeTag } from '../../../../Utils/FormatTags';
export const TextView = ({ item, truncate = false}: { item?: Item, truncate?: boolean }) => {
export const TextView = ({ item, truncate = false, itemTextField}: { item?: Item, truncate?: boolean,itemTextField?: string }) => {
const tags = useTags();
const addFilterTag = useAddFilterTag();
let text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : "";
console.log(item);
let text = "";
if(itemTextField && item) text = getValue(item, itemTextField);
else text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : ""
if(item && text && truncate) text = truncateString(text, 100, true);

View File

@ -31,6 +31,8 @@ export function OverlayProfile() {
const [offers, setOffers] = useState<Array<Tag>>([]);
const [needs, setNeeds] = useState<Array<Tag>>([]);
const [activeTab, setActiveTab] = useState<number>(1);
const addFilterTag = useAddFilterTag();
@ -85,8 +87,21 @@ export function OverlayProfile() {
}
</div>
<div className='tw-overflow-y-auto tw-h-full tw-pt-4 fade'>
<div className='tw-grid tw-grid-cols-1'>
<div className='tw-h-full'>
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-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="Vision" checked={activeTab == 1 && true} onChange={() => setActiveTab(1)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-min-h-56 tw-overflow-y-auto fade tw-pt-2">
<TextView item={item} />
</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 == 2 && true} onChange={() => setActiveTab(2)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
<div className='tw-h-full'>
<div className='tw-grid tw-grid-cols-1'>
{
offers.length > 0 ?
<div className='tw-col-span-1'>
@ -110,7 +125,18 @@ export function OverlayProfile() {
</div> : ""
}
</div>
<TextView item={item} />
</div>
</div>
<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="Contact" checked={activeTab == 3 && true} onChange={() => setActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-min-h-56 tw-overflow-y-auto fade tw-pt-2">
<TextView item={item} itemTextField='user_created.contact' />
</div>
</div>
</div>
</>
}

View File

@ -30,6 +30,7 @@ export function OverlayProfileSettings() {
const [color, setColor] = useState<string>("");
const [offers, setOffers] = useState<Array<Tag>>([]);
const [needs, setNeeds] = useState<Array<Tag>>([]);
const [contact, setContact] = useState<string>("");
const [activeTab, setActiveTab] = useState<number>(1);
@ -63,6 +64,7 @@ export function OverlayProfileSettings() {
const need = tags.find(t => t.id === o.tags_id);
need && setNeeds(current => [...current,need])
})
setContact(user?.contact ? user.contact : "");
}, [user])
const imgRef = React.useRef<HTMLImageElement>(null)
@ -180,7 +182,7 @@ export function OverlayProfileSettings() {
});
changedUser = { id: id, first_name: name, description: text, color: color, ...avatar.length > 10 && { avatar: avatar }, ... offers.length > 0 && {offers: offer_updates}, ... needs.length > 0 && {needs: needs_updates} };
changedUser = { id: id, first_name: name, description: text, contact: contact, color: color, ...avatar.length > 10 && { avatar: avatar }, ... offers.length > 0 && {offers: offer_updates}, ... needs.length > 0 && {needs: needs_updates} };
// update profile item in current state
const item = items.find(i => i.layer?.itemOwnerField && getValue(i, i.layer?.itemOwnerField).id === id);
@ -259,9 +261,9 @@ export function OverlayProfileSettings() {
<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="Text" checked={activeTab == 1 && true} onChange={() => setActiveTab(1)} />
<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="Vision" checked={activeTab == 1 && true} onChange={() => setActiveTab(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">
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
<TextAreaInput placeholder="My Vision..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
</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 == 2 && true} onChange={() => setActiveTab(2)} />
@ -277,16 +279,8 @@ export function OverlayProfileSettings() {
</div>
<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="Contact" checked={activeTab == 3 && true} onChange={() => setActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
<div className='tw-overflow-y-auto tw-h-full tw-min-h-56"'>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='E-Mail ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Telefon ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Webpage ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Matrix ... '></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Telegram ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Instagram ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Twitter ...'></input>
</div>
<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">
<TextAreaInput placeholder="Contact ..." defaultValue={user?.contact ? user.contact : ""} updateFormValue={(v) => setContact(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
</div>
</div>

View File

@ -0,0 +1,33 @@
import * as React from 'react';
import { useEffect, useRef } from 'react';
export const CircleLayout = ({ items,radius, fontSize }) => {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const container = containerRef.current;
const itemCount = items.length;
if (container) {
for (let i = 0; i < itemCount; i++) {
const startAngle = (Math.PI) / 2;
const angle = startAngle + (i / itemCount) * (2 * Math.PI);
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
const child = container.children[i] as HTMLElement;
child.style.transform = `translate(${x}px, ${y}px)`;
}
}
}, [items]);
return (
<div className="tw-absolute tw-mx-auto tw-flex tw-justify-center tw-items-center tw-h-full tw-w-full" ref={containerRef}>
{items.map((item, index) => (
<div key={index} className="tw-absolute" style={{fontSize: fontSize}}>
{item}
</div>
))}
</div>
);
};

View File

@ -13,6 +13,8 @@ export function MapOverlayPage({ children, className, backdrop }: { children: Re
const navigate = useNavigate();
const overlayRef = React.createRef<HTMLDivElement>()
const backdropRef = React.createRef<HTMLDivElement>()
React.useEffect(() => {
@ -20,12 +22,16 @@ export function MapOverlayPage({ children, className, backdrop }: { children: Re
L.DomEvent.disableClickPropagation(overlayRef.current)
L.DomEvent.disableScrollPropagation(overlayRef.current)
}
}, [overlayRef])
if(backdropRef.current !== null && backdrop) {
L.DomEvent.disableClickPropagation(backdropRef.current)
L.DomEvent.disableScrollPropagation(backdropRef.current)
}
}, [overlayRef, backdropRef])
return (
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && "tw-z-[2000]"}`}>
<div className={`${backdrop && "tw-backdrop-brightness-75"} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`} >
<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={`tw-card tw-shadow-xl tw-bg-base-100 tw-p-4 ${className && className} ${!backdrop && "tw-z-[2000]"} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
<div className="tw-card-body tw-p-2 tw-h-full">
{children}

View File

@ -0,0 +1,95 @@
import * as React from 'react'
import { useState } from 'react'
import {
add,
addDays,
eachDayOfInterval,
endOfMonth,
endOfWeek,
format,
getDay,
isSameMonth,
isToday,
parse,
startOfToday,
startOfWeek,
sub,
} from "date-fns";
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
import { MapOverlayPage } from './MapOverlayPage';
import { CircleLayout } from './CircleLayout';
import { LUNAR_MONTH, getLastNewMoon, getNextNewMoon } from '../../Utils/Moon';
export const MoonCalendar = () => {
const today = startOfToday();
const days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
const colStartClasses = [
"",
"col-start-2",
"col-start-3",
"col-start-4",
"col-start-5",
"col-start-6",
"col-start-7",
];
const [currMonth, setCurrMonth] = useState(() => format(today, "MMM-yyyy"));
let firstDayOfMonth = parse(currMonth, "MMM-yyyy", new Date());
const daysInMonth = eachDayOfInterval({
start: firstDayOfMonth,
end: endOfMonth(firstDayOfMonth),
});
const currentMoonCycle = eachDayOfInterval({
start: getLastNewMoon(),
end: getNextNewMoon()
});
const getPrevMonth = (event: React.MouseEvent<SVGSVGElement>) => {
event.preventDefault();
const firstDayOfPrevMonth = add(firstDayOfMonth, { months: -1 });
setCurrMonth(format(firstDayOfPrevMonth, "MMM-yyyy"));
};
const getNextMonth = (event: React.MouseEvent<SVGSVGElement>) => {
event.preventDefault();
const firstDayOfNextMonth = add(firstDayOfMonth, { months: 1 });
setCurrMonth(format(firstDayOfNextMonth, "MMM-yyyy"));
};
return (
<MapOverlayPage backdrop className='tw-h-96 tw-w-80'>
<p className='tw-self-center tw-text-lg tw-font-bold'>Moon Cycle</p>
<div className='tw-relative tw-h-full'>
<CircleLayout items={["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]} radius={80} fontSize={"3em"} />
<CircleLayout items={ [
format(getLastNewMoon(), "dd.MM hh:mm"),
format(sub(getNextNewMoon(), {seconds: LUNAR_MONTH*86400/4*3}), "dd.MM hh:mm"),
format(sub(getNextNewMoon(), {seconds: LUNAR_MONTH*86400/2}), "dd.MM hh:mm"),
format(sub(getNextNewMoon(), {seconds: LUNAR_MONTH*86400/4}), "dd.MM hh:mm")
]} radius={120} fontSize={"0.8em"}/>
</div>
<div className='tw-flex tw-flex-row'>
<ChevronLeftIcon
className="tw-w-6 tw-h-6 tw-cursor-pointer"
onClick={getPrevMonth}
/>
<p className='tw-text-center tw-p-1 tw-h-full tw-grow'>from {format(getLastNewMoon(), "dd.MM")} - to {format(getNextNewMoon(), "dd.MM")}</p>
<ChevronRightIcon
className="tw-w-6 tw-h-6 tw-cursor-pointer"
onClick={getNextMonth}
/>
</div>
</MapOverlayPage>
);
}
const capitalizeFirstLetter = (string: string) => {
return string
}

View File

@ -1,3 +1,5 @@
export {CardPage} from './CardPage'
export {TitleCard} from './TitleCard'
export {MapOverlayPage} from './MapOverlayPage'
export {MapOverlayPage} from './MapOverlayPage'
export {CircleLayout} from './CircleLayout'
export {MoonCalendar} from './MoonCalendar'

32
src/Utils/Moon.ts Normal file
View File

@ -0,0 +1,32 @@
export const LUNAR_MONTH: number = 29.530588853;
const getJulianDate = (date: Date = new Date()): number => {
const time: number = date.getTime();
const tzoffset: number = date.getTimezoneOffset();
return (time / 86400000) - (tzoffset / 1440) + 2440587.5;
};
const normalize = (value: number): number => {
value = value - Math.floor(value);
if (value < 0) value += 1;
return value;
};
const getLunarAgePercent = (date: Date = new Date()): number => {
return normalize((getJulianDate(date) - 2451550.1) / LUNAR_MONTH);
};
const getLunarAge = (date: Date = new Date()): number => {
const percent: number = getLunarAgePercent(date);
return percent * LUNAR_MONTH;
};
export const getNextNewMoon = (date: Date = new Date()): Date => {
const lunarAge: number = getLunarAge(date);
return new Date(getLastNewMoon().getTime() + LUNAR_MONTH * 86400000);
}
export const getLastNewMoon = (date: Date = new Date()):Date => {
const lunarAge: number = getLunarAge(date);
return new Date(date.getTime() - lunarAge * 86400000);
}

View File

@ -3,7 +3,7 @@
@tailwind utilities;
.fade {
mask-image: linear-gradient(180deg,transparent, #000 3%, #000 97%, transparent);
mask-image: linear-gradient(180deg, transparent, #000 3%, #000 97%, transparent);
}
.tw-modal {
@ -52,7 +52,7 @@
}
.custom-file-upload{
.custom-file-upload {
cursor: pointer;
}
@ -60,11 +60,11 @@ input[type="file"] {
display: none;
}
.custom-file-upload:hover .button{
.custom-file-upload:hover .button {
opacity: 0.8;
}
.custom-file-upload .button{
.custom-file-upload .button {
transition: .5s ease;
opacity: 0;
position: absolute;
@ -74,4 +74,4 @@ input[type="file"] {
.tw-tab-content .container {
height: 100%;
}
}

View File

@ -3,7 +3,7 @@ export {AppShell, Content, SideBar} from "./Components/AppShell"
export {AuthProvider, useAuth, LoginPage, SignupPage, RequestPasswordPage, SetNewPasswordPage} from "./Components/Auth"
export {UserSettings, ProfileSettings, OverlayProfile, OverlayProfileSettings, OverlayUserSettings} from './Components/Profile'
export {Quests, Modal} from './Components/Gaming'
export {TitleCard, CardPage, MapOverlayPage} from './Components/Templates'
export {TitleCard, CardPage, MapOverlayPage, CircleLayout, MoonCalendar} from './Components/Templates'
export {TextInput, TextAreaInput, SelectBox} from './Components/Input'
import "./index.css"