diff --git a/src/app/(frontend)/detectors/[slug]/page.tsx b/src/app/(frontend)/detectors/[slug]/page.tsx index ccafa03c73571f46a5a17796bc2643e3248a1987..d2f106680c7da6649999e028f823e1a81729231c 100644 --- a/src/app/(frontend)/detectors/[slug]/page.tsx +++ b/src/app/(frontend)/detectors/[slug]/page.tsx @@ -1,8 +1,6 @@ import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical' import config from '@/payload.config' -import { format } from 'date-fns' -import Image from 'next/image' import { notFound } from 'next/navigation' import { getPayload } from 'payload' import React from 'react' diff --git a/src/app/(frontend)/detectors/page.tsx b/src/app/(frontend)/detectors/page.tsx index ac7ec3bb3bef6f58370d0dabe138dd2ddd3c28e3..8b517a0b2e846ca7770d2e5f7095945d7602edd7 100644 --- a/src/app/(frontend)/detectors/page.tsx +++ b/src/app/(frontend)/detectors/page.tsx @@ -1,18 +1,9 @@ import { Card, CardContent } from '@/components/ui/card' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' -import { Input } from '@/components/ui/input' import config from '@/payload.config' -import { format } from 'date-fns' -import { BookOpen, Calendar, ChevronDown, ChevronRight, Filter, Newspaper, Search, User } from 'lucide-react' +import { BookOpen, Newspaper} from 'lucide-react' import Image from 'next/image' import Link from 'next/link' import { getPayload } from 'payload' -import Tag from '../components/Tag' interface Detector { id: string @@ -37,7 +28,7 @@ export default async function DetectorPage() { const articles = result.docs as unknown as Detector[] const remainingArticles = articles.length > 0 ? articles : [] return ( - <main className="bg-gray-50 dark:bg-neutral-950 min-h-screen pb-16 md:pt-5"> + <main className="bg-gray-50 dark:bg-neutral-950 min-h-screen pb-16 md:pt-5 pt-16"> <div className="container mx-auto px-4"> {remainingArticles.length > 0 ? ( diff --git a/src/app/(frontend)/experiments/ExperimentCard.tsx b/src/app/(frontend)/experiments/ExperimentCard.tsx index 1d3090b7855878c1d62ffb015e6f210d7b0eae63..0b622a62db42a00de419418868a2112a9614a7eb 100644 --- a/src/app/(frontend)/experiments/ExperimentCard.tsx +++ b/src/app/(frontend)/experiments/ExperimentCard.tsx @@ -1,7 +1,8 @@ 'use client' +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' import { Card, CardContent } from '@/components/ui/card' import config from '@/payload.config' -import { BookOpen, Calendar, ChevronDown, ChevronRight, Filter, Newspaper, Search, User } from 'lucide-react' +import { BookOpen, Calendar, ChevronDown, ChevronRight, Filter, Info, Newspaper, Search, User } from 'lucide-react' import Image from 'next/image' import ExperimentSection from './ExperimentSection' @@ -34,44 +35,83 @@ interface ExperimentProps { export default function experimentCard({ experiment }: ExperimentProps) { return ( <div> + {experiment && experiment.groupleader + ? ( + <Card key={experiment.id} className="overflow-hidden h-full flex flex-col bg-white dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 group-hover:border-red-300 dark:group-hover:border-red-800 transition-colors shadow-sm group-hover:shadow-md"> + <div className="relative w-full h-48 overflow-hidden bg-gray-200 dark:bg-neutral-700"> + {experiment.featuredImage && experiment.featuredImage.url + ? ( + <Image + src={experiment.featuredImage.url} + alt={experiment.featuredImage.alt || experiment.title} + fill + className="object-cover transition-transform group-hover:scale-105" + /> + ) + : ( + <div className="w-full h-full flex items-center justify-center"> + <Newspaper className="w-12 h-12 text-gray-400 dark:text-neutral-500" /> + </div> + )} + </div> - <Card key={experiment.id} className="overflow-hidden h-full flex flex-col bg-white dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 group-hover:border-red-300 dark:group-hover:border-red-800 transition-colors shadow-sm group-hover:shadow-md"> - <div className="relative w-full h-48 overflow-hidden bg-gray-200 dark:bg-neutral-700"> - {experiment.featuredImage && experiment.featuredImage.url - ? ( - <Image - src={experiment.featuredImage.url} - alt={experiment.featuredImage.alt || experiment.title} - fill - className="object-cover transition-transform group-hover:scale-105" - /> - ) - : ( - <div className="w-full h-full flex items-center justify-center"> - <Newspaper className="w-12 h-12 text-gray-400 dark:text-neutral-500" /> - </div> - )} - </div> + <CardContent className="flex-grow flex flex-col p-5"> - <CardContent className="flex-grow flex flex-col p-5"> + <h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3 line-clamp-2 group-hover:text-red-600 dark:group-hover:text-red-400 transition-colors"> + {experiment.title} + </h3> - <h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3 line-clamp-2 group-hover:text-red-600 dark:group-hover:text-red-400 transition-colors"> - {experiment.title} - </h3> + <h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3 line-clamp-2 group-hover:text-red-600 dark:group-hover:text-red-400 transition-colors"> + {experiment.number} + </h3> - {experiment.previewText && ( - <p className="text-gray-600 dark:text-gray-300 mb-4 line-clamp-3 flex-grow"> - {experiment.previewText} - </p> - )} - - <div className="flex items-center text-red-600 dark:text-red-400 mt-auto pt-2 text-sm font-medium"> - <BookOpen className="w-4 h-4 mr-1" /> - <span>Read more</span> - </div> - </CardContent> - </Card> + <Accordion + className="AccordionRoot border-gray-200 border-t" + type="single" + defaultValue="null" + collapsible + > + <AccordionItem className="AccordionItem" value="item-1"> + <AccordionTrigger> + <div className="flex items-center text-red-600 dark:text-red-400 mt-auto pt-2 text-sm font-medium"> + <Info className="w-4 h-4 mr-1" /> + <span>more Details</span> + </div> + </AccordionTrigger> + <AccordionContent> + <div> + <div> + {'From the year: '} + {experiment.year} + </div> + <div> + {experiment.groupleader.name} + {' '} + {experiment.groupleader.email} + </div> + <div> + {experiment.speaker.name} + {' '} + {experiment.speaker.link} + </div> + <div> + {experiment.previewText && ( + <p className="text-gray-600 dark:text-gray-300 mb-4 flex-grow"> + {experiment.previewText} + </p> + )} + </div> + </div> + </AccordionContent> + </AccordionItem> + </Accordion> + </CardContent> + </Card> + ) + : ( + <div> experiment cant load</div> + )} </div> ) } diff --git a/src/app/(frontend)/experiments/ExperimentSection.tsx b/src/app/(frontend)/experiments/ExperimentSection.tsx index 2b653c33d35acc24d16ebe6a3072ce5cf68e84d6..83fed829699694d9debbdba8c32dff07d9bb2012 100644 --- a/src/app/(frontend)/experiments/ExperimentSection.tsx +++ b/src/app/(frontend)/experiments/ExperimentSection.tsx @@ -1,53 +1,55 @@ 'use client' -import type { Experiment } from '@/payload-types' -import { Card, CardContent } from '@/components/ui/card' -import { debounce } from '@/lib/helperFunctions' -// import { TextInput } from 'flowbite-react' -import { startTransition, useState } from 'react' -import { HiSearch } from 'react-icons/hi' +import { Card } from '@/components/ui/card' import ExperimentCard from './ExperimentCard' +interface Experiment { + id: string + number: string + title: string + year: string + speaker: { + name: string + link: string + } + groupleader: { + name: string + email: string + } + previewText: string + featuredImage?: { + url: string + width: number + height: number + alt: string + } +} + interface ExperimentProps { experiments: Experiment[] } export default function ExperimentSection({ experiments }: ExperimentProps) { - const [searchQuery, setSearchQuery] = useState('') - - const filteredExperiments = experiments.filter(experiment => - Object.values(experiment).some(value => - typeof value === 'string' && value.toLowerCase().includes(searchQuery.trim().toLowerCase()), - ), - ) - return ( <main className="bg-gray-50 dark:bg-neutral-950 min-h-screen pb-16 md:pt-5"> <div className="container mx-auto px-4"> <div className="flex flex-col px-4 items-center justify-center"> - {/* <TextInput - id="search4" - type="search" - icon={HiSearch} - placeholder="Search Experiments..." - onChange={e => debounce(() => { startTransition(() => setSearchQuery(e.target.value)) }, 'experimentSearch')} - className="md:w-2/3 md:pt-8 p-8 w-5/6 pt-20" - /> - */} - - {filteredExperiments.length > 0 - ? ( - filteredExperiments.map(experiment => ( - <ExperimentCard key={experiment.id} experiment={experiment} /> - )) - ) - : ( - <Card className="p-12 text-center bg-white dark:bg-neutral-800"> - <p className="text-gray-500 dark:text-gray-400">No news articles found</p> - </Card> - )} - + <div className="w-full grid grid-cols-1 gap-8 "> + {experiments.length > 0 + ? ( + experiments.map(experiment => ( + <div key={experiment.id} className="group"> + <ExperimentCard experiment={experiment} /> + </div> + )) + ) + : ( + <Card className="p-12 text-center bg-white dark:bg-neutral-800"> + <p className="text-gray-500 dark:text-gray-400">No experiments found</p> + </Card> + )} + </div> </div> </div> </main> diff --git a/src/app/(frontend)/experiments/page.tsx b/src/app/(frontend)/experiments/page.tsx index 97214b1de62e6f20d93a8afb49ccf2c853dbdf96..982f1de85ee9bca4a6e990bcfc82e9eaf9e5b96b 100644 --- a/src/app/(frontend)/experiments/page.tsx +++ b/src/app/(frontend)/experiments/page.tsx @@ -1,9 +1,147 @@ -import type { Experiment } from '@/payload-types' -import { getPublications } from '../../../lib/asyncHelperFunctions' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { Input } from '@/components/ui/input' +import config from '@/payload.config' +import { ChevronDown, Filter, Search } from 'lucide-react' +import Link from 'next/link' +import { getPayload } from 'payload' import ExperimentSection from './ExperimentSection' -export default async function ExperimentPage() { - const experiments = (await getPublications('experiments')).docs as Experiment[] +interface Experiment { + id: string + number: string + title: string + year: string + speaker: { + name: string + link: string + } + groupleader: { + name: string + email: string + } + previewText: string + featuredImage?: { + url: string + width: number + height: number + alt: string + } +} + +export default async function ExperimentPage({ + searchParams, +}: { + searchParams: { sort?: string, q?: string } +}) { + const payload = await getPayload({ config }) + + const params = await searchParams + const sortParam = params.sort || '-year' + const searchQuery = params.q || '' + + const query: any = { + collection: 'experiments', + sort: sortParam, + depth: 1, + } + + // Add search functionality if query exists + if (searchQuery) { + query.where = { + or: [ + { title: { contains: searchQuery } }, + { number: { contains: searchQuery } }, + { year: { contains: searchQuery } }, + // { speaker: { contains: searchQuery } }, + { previewText: { contains: searchQuery } }, + ], + } + } + + const result = await payload.find(query) + + const experiments = result.docs as unknown as Experiment[] + + // const featuredExperiments = articles.length > 0 ? articles[0] : null + const remainingExperiments = experiments.length > 0 ? experiments : [] + + const sortOptions = [ + { label: 'Newest first', value: '-year' }, + { label: 'Oldest first', value: 'year' }, + { label: 'A-Z', value: 'title' }, + { label: 'Z-A', value: '-title' }, + ] + + const currentSortOption = sortOptions.find(option => option.value === sortParam)?.label || 'Newest first' + + // const experiments = (await getPublications('experiments')).docs as unknown as Experiment[] + + return ( + // searchbar + <div className="container mx-auto px-4 md:pt-5 pt-16"> + {/* Search and filters bar */} + <div className="bg-white dark:bg-neutral-800 p-4 rounded-lg shadow-sm mb-8 border border-gray-200 dark:border-neutral-700"> + <form className="flex flex-col md:flex-row gap-4"> + <div className="relative flex-grow"> + <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" /> + <Input + type="search" + name="q" + placeholder="Search experiments..." + className="pl-10 bg-gray-50 dark:bg-neutral-900 border-gray-200 dark:border-neutral-700" + defaultValue={searchQuery} + /> + </div> + <div className="flex gap-2"> + <DropdownMenu modal={false}> + <DropdownMenuTrigger className="flex items-center gap-1 px-4 py-2 bg-gray-100 dark:bg-neutral-700 rounded-md border border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-gray-200 outline-none"> + <Filter className="h-4 w-4" /> + <span className="text-sm hidden sm:inline">Sort by:</span> + <span className="text-sm font-medium">{currentSortOption}</span> + <ChevronDown className="h-4 w-4 ml-1" /> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + {sortOptions.map(option => ( + <DropdownMenuItem key={option.value} asChild> + <Link href={`?sort=${option.value}${searchQuery ? `&q=${searchQuery}` : ''}`}> + {option.label} + </Link> + </DropdownMenuItem> + ))} + </DropdownMenuContent> + </DropdownMenu> + <button + type="submit" + className="bg-r3b-orange hover:bg-orange-700 text-white px-4 py-2 rounded-md transition-colors" + > + Search + </button> + </div> + </form> + </div> + + <div className="flex justify-between items-center mb-8"> + <h2 className="text-2xl md:text-3xl font-bold text-gray-800 dark:text-white"> + {searchQuery ? `Search Results: "${searchQuery}"` : 'Experiments'} + </h2> + <div className="text-sm text-gray-500 dark:text-gray-400"> + {experiments.length} + {' '} + experiment + {experiments.length !== 1 ? 's' : ''} + {' '} + found + </div> + </div> - return <ExperimentSection experiments={experiments} /> + {// every experiment that matches the search and sort parameters will be rendered here + } + <ExperimentSection experiments={remainingExperiments} /> + </div> + ) } diff --git a/src/collections/Experiments.ts b/src/collections/Experiments.ts index 5327bc3eba2dc2daab04e0126b4125cb603a9365..129a2f0ab097193d0dea95b596b3fe378cd225ce 100644 --- a/src/collections/Experiments.ts +++ b/src/collections/Experiments.ts @@ -63,7 +63,7 @@ export const Experiments: CollectionConfig = { name: 'previewText', type: 'textarea', required: true, - label: 'Kleiner Beschreibungstext', + label: 'Beschreibungstext', }, { name: 'featuredImage',