Skip to content
Snippets Groups Projects
Commit 828f8e7c authored by Linus Schmueser's avatar Linus Schmueser
Browse files

Second version of the experiment page, with searchbar and extendable information field on the cards

parent 63a0c0d8
Branches
No related tags found
2 merge requests!31Improved CD Pipeline which now works for automated deployment via http.,!23Feature/experiment page3
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'
......
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
? (
......
'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>
)
}
'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>
......
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>
)
}
......@@ -63,7 +63,7 @@ export const Experiments: CollectionConfig = {
name: 'previewText',
type: 'textarea',
required: true,
label: 'Kleiner Beschreibungstext',
label: 'Beschreibungstext',
},
{
name: 'featuredImage',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment