Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add view for single tree #71

Merged
merged 15 commits into from
Sep 28, 2024
46 changes: 46 additions & 0 deletions frontend/src/components/general/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ReactNode } from '@tanstack/react-router';
import React, { useState } from 'react';

interface Tabs {
tabs: { label: string; icon: ReactNode; view: ReactNode; }[]
}

const Tabs: React.FC<Tabs> = ({ tabs }) => {
const [showTabIndex, setShowTabIndex] = useState(0);

return (
<>
<div role="tablist" className="mb-10 border-b border-b-dark-600 flex items-center w-max gap-x-6">
{tabs.map((tab, key) => (
<button
onClick={() => setShowTabIndex(key)}
key={key}
id={`tab-${key}`}
role="tab"
aria-selected={showTabIndex === key}
aria-controls={`tabpanel-${key}`}
className={`flex items-center gap-x-2 pb-2 group border-b transition-all ease-in-out duration-300 hover:text-dark-800 ${showTabIndex === key ? 'text-dark border-b-dark' : 'text-dark-600 border-b-transparent'}`}
>
{tab.icon}
<span className="hidden group-aria-selected:block lg:block">{tab.label}</span>
</button>
))}
</div>

{tabs.map((tab, key) => (
<div
key={key}
id={`tabpanel-${key}`}
role="tabpanel"
tabIndex={showTabIndex === key ? 0 : -1}
aria-labelledby={`tab-${key}`}
className={`${showTabIndex === key ? 'block': 'hidden'}`}
>
{tab.view}
</div>
))}
</>
);
}

export default Tabs;
20 changes: 20 additions & 0 deletions frontend/src/components/general/cards/EntitiesStatusCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

interface EntitiesStatusCard {
statusDetails: {label: string, color: string, description: string};
label: string;
}

const EntitiesStatusCard: React.FC<EntitiesStatusCard> = ({ statusDetails, label }) => {
return (
<div className={`h-full space-y-3 bg-${statusDetails.color}-100 rounded-xl p-6`}>
<h2 className="text-sm text-dark-700 font-medium">{label}</h2>
<p className={`relative pl-7 font-bold text-xl before:absolute before:w-4 before:h-4 before:rounded-full before:left-0 before:top-2 before:bg-${statusDetails.color}`}>
{statusDetails.label}
</p>
<p className="text-sm">{statusDetails.description}</p>
</div>
);
}

export default EntitiesStatusCard;
5 changes: 3 additions & 2 deletions frontend/src/components/general/cards/GeneralStatusCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ interface GeneralStatusCard {
overline: string;
value: string;
description?: string;
isLarge?: boolean;
}

const GeneralStatusCard: React.FC<GeneralStatusCard> = ({ overline, value, description = '' }) => {
const GeneralStatusCard: React.FC<GeneralStatusCard> = ({ overline, value, description = '', isLarge = false }) => {
return (
<div className="h-full space-y-3 bg-dark-50 rounded-xl p-6">
<h2 className="text-sm text-dark-700 font-medium">{overline}</h2>
<p className="font-bold text-xl">{value}</p>
<p className={`font-bold ${isLarge ? 'text-3xl' : 'text-xl'}`}>{value}</p>
{description && <p className="text-sm">{description}</p>}
</div>
);
Expand Down
71 changes: 19 additions & 52 deletions frontend/src/components/general/cards/TreeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,37 @@
import { getWateringStatusDetails } from '@/hooks/useDetailsForWateringStatus';
import { EntitiesTreeClusterWateringStatus } from '@green-ecolution/backend-client';
import { EntitiesTreeClusterWateringStatus, Tree } from '@green-ecolution/backend-client';
import { Link } from '@tanstack/react-router';
import { MoveRight } from 'lucide-react';
import React from 'react';

interface Tree {
id: number;
species: string;
number: string;
hasSensor: boolean;
status: EntitiesTreeClusterWateringStatus;
}

interface TreeCardContentProps {
tree: Tree;
statusDetails: {
color: string;
label: string;
description: string;
};
}

const TreeCardContent: React.FC<TreeCardContentProps> = ({ tree, statusDetails }) => (
<>
<p className={`relative font-medium pl-7 before:absolute before:w-4 before:h-4 before:rounded-full before:left-0 before:top-[0.22rem]
before:bg-${statusDetails.color}`}>
{statusDetails.label}
</p>
<h3 className="text-lg font-bold font-lato">{tree.species}</h3>
{tree.number &&
<p className="text-dark-700">
<span className="lg:sr-only">Baumnummer: </span>{tree.number}
</p>
}

{tree.hasSensor &&
<p className="font-lato font-semibold text-green-dark flex items-center gap-x-2">
<span>Zur Detailansicht</span>
<MoveRight className="transition-all ease-in-out duration-300 group-hover:translate-x-2"/>
</p>
}
</>
);

interface TreeCardProps {
tree: Tree;
}

const TreeCard: React.FC<TreeCardProps> = ({ tree }) => {
const statusDetails = getWateringStatusDetails(tree.status);
// TODO: Add real status
const statusDetails = getWateringStatusDetails(EntitiesTreeClusterWateringStatus.TreeClusterWateringStatusGood);
const wrapperClasses = 'bg-white group border border-dark-50 p-6 rounded-xl shadow-cards flex flex-col gap-y-4 lg:grid lg:grid-cols-[1fr,2fr,1fr,1fr] lg:items-center lg:gap-5 lg:py-5 xl:px-10';

if (!tree.hasSensor) {
return (
<div className={wrapperClasses}>
<TreeCardContent tree={tree} statusDetails={statusDetails} />
</div>
);
}

return (
<Link
to={`/tree/${tree.id}`}
to={`/trees/${tree.id}`}
className={`transition-all ease-in-out duration-300 hover:bg-green-dark-50 hover:border-green-dark ${wrapperClasses}`}
>
<TreeCardContent tree={tree} statusDetails={statusDetails} />
<p className={`relative font-medium pl-7 before:absolute before:w-4 before:h-4 before:rounded-full before:left-0 before:top-[0.22rem]
before:bg-${statusDetails.color}`}>
{statusDetails.label}
</p>
<h3 className="text-lg font-bold font-lato">{tree.species}</h3>
{tree.number &&
<p className="text-dark-700">
<span className="lg:sr-only">Baumnummer: </span>{tree.number}
</p>
}

<p className="font-lato font-semibold text-green-dark flex items-center gap-x-2">
<span>Zur Detailansicht</span>
<MoveRight className="transition-all ease-in-out duration-300 group-hover:translate-x-2"/>
</p>
</Link>
);
}
Expand Down
23 changes: 0 additions & 23 deletions frontend/src/components/general/cards/WateringStatusCard.tsx

This file was deleted.

19 changes: 19 additions & 0 deletions frontend/src/components/general/error/LoadingInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

interface LoadingInfo {
label: string;
}

const LoadingInfo: React.FC<LoadingInfo> = ({ label }) => {
return (
<div className="mt-20 flex flex-wrap items-center justify-center gap-x-4">
<svg aria-hidden="true" className="w-6 h-6 text-green-dark-500 animate-spin fill-green-dark" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<p className="text-lg text-dark-600">{label}</p>
</div>
);
}

export default LoadingInfo;
2 changes: 1 addition & 1 deletion frontend/src/components/general/filter/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const Dialog: React.FC<DialogProps> = ({ initStatusTags, initRegionTags, headlin
<Option
key={region.id}
label={region.name}
name={region.id}
name={String(region.id)}
checked={regionTags.includes(region.name)}
onChange={handleFilterChange('region')}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Link } from '@tanstack/react-router';
import { MoveRight } from 'lucide-react';
import React from 'react';

interface GeneralLinkProps {
interface ButtonLink {
label: string;
url: string;
asButton?: boolean;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
}

const GeneralLink: React.FC<GeneralLinkProps> = ({ label, icon: Icon = MoveRight, url }) => (
const ButtonLink: React.FC<ButtonLink> = ({ label, icon: Icon = MoveRight, url }) => (
<Link
to={url}
className="bg-green-dark w-fit text-white px-5 py-2 group flex gap-x-3 rounded-xl items-center transition-all ease-in-out duration-300 hover:bg-green-light"
Expand All @@ -18,4 +19,4 @@ const GeneralLink: React.FC<GeneralLinkProps> = ({ label, icon: Icon = MoveRight
</Link>
);

export default GeneralLink;
export default ButtonLink;
17 changes: 17 additions & 0 deletions frontend/src/components/general/links/GeneralLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from '@tanstack/react-router';
import { MoveRight } from 'lucide-react';
import React from 'react';

interface GeneralLink {
label: string;
url: string;
}

const GeneralLink: React.FC<GeneralLink> = ({ label, url }) => (
<Link to={url} className="group flex items-center gap-x-2 text-green-dark font-medium text-base mb-4">
{label}
<MoveRight className="w-4 h-4 transition-all ease-in-out duration-300 group-hover:translate-x-1" />
</Link>
);

export default GeneralLink;
15 changes: 15 additions & 0 deletions frontend/src/components/icons/Sensor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

const Sensor: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}>
<path fillRule="evenodd" clipRule="evenodd" d="M20.27 12C20.27 16.5674 16.5674 20.27 12 20.27C7.4326 20.27 3.73 16.5674 3.73 12C3.73 7.43261 7.4326 3.73 12 3.73C16.5674 3.73 20.27 7.43261 20.27 12ZM21.77 12C21.77 17.3958 17.3958 21.77 12 21.77C6.60417 21.77 2.23 17.3958 2.23 12C2.23 6.60418 6.60417 2.23 12 2.23C17.3958 2.23 21.77 6.60418 21.77 12ZM14.5701 12C14.5701 13.4207 13.419 14.5715 12 14.5715C10.581 14.5715 9.42986 13.4207 9.42986 12C9.42986 10.5793 10.581 9.4285 12 9.4285C13.419 9.4285 14.5701 10.5793 14.5701 12ZM16.0701 12C16.0701 14.2486 14.2479 16.0715 12 16.0715C9.75212 16.0715 7.92986 14.2486 7.92986 12C7.92986 9.75138 9.75212 7.9285 12 7.9285C14.2479 7.9285 16.0701 9.75138 16.0701 12Z" fill="currentColor"/>
</svg>
);

export default Sensor;
80 changes: 80 additions & 0 deletions frontend/src/components/tree/TreeGeneralData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { format } from "date-fns";


interface TreeGeneralData {
tree?: {
id: number;
species: string;
number: number;
heightAboveSeaLevel: number;
plantingYear: number;
age: number;
updatedAt: string;
latitude: number;
longitude: number;
}
}

const TreeGeneralData: React.FC<TreeGeneralData> = ({ tree }) => {
const updatedDate = tree?.updatedAt
? format(new Date(tree?.updatedAt), 'dd.MM.yyyy')
: 'Keine Angabe';

const treeData = [
{
label: 'Baumart',
value: tree?.species ?? 'Keine Angabe',
},
{
label: 'Höhe über NHN',
value: tree?.heightAboveSeaLevel ?? 'Keine Angabe',
},
{
label: 'Standalter',
value: tree?.age ?? 'Keine Angabe',
},
{
label: 'Pflanzjahr',
value: tree?.plantingYear ?? 'Keine Angabe',
},
{
label: 'Bewässerungsgruppe',
value: '@TODO: Implement'
},
{
label: 'Latitude',
value: tree?.latitude ?? 'Keine Angabe',
},
{
label: 'Longitude',
value: tree?.longitude ?? 'Keine Angabe',
},
{
label: 'Letztes Update',
value: updatedDate,
},
];

return (
<>
<dl className="text-lg md:columns-2 md:gap-x-11">
{treeData.map((data, index) => (
<div
key={index}
className={`py-4 border-b border-b-dark-200 group md:last:border-b-transparent
${treeData.length/2 === index + 1 ? 'md:border-b-transparent' : ''}`}
>
<dt className="font-bold sm:inline">{data.label}:</dt>
<dd className="sm:inline sm:px-2">{data.value}</dd>
</div>
))}
</dl>

<section className="mt-16">
@TODO: Add image slider
</section>
</>
);
}

export default TreeGeneralData;
Loading
Loading