Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions src/app/groups/didattica/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import type { IconType } from "react-icons"
import { FiBook, FiBox, FiChevronRight, FiFeather, FiPenTool } from "react-icons/fi"
import { CardCourse } from "@/components/card-course"
import { CardCourseGroup } from "@/components/card-course-group"
import { CardIcon } from "@/components/card-icon"
import { CardPathSelection } from "@/components/card-path-selection"
import {
FACULTIES,
getCourse,
getCourseFilters,
getCourses,
getFaculty,
getLevel,
LEVELS,
} from "@/components/groups/constants"
import { CourseFilters } from "@/components/groups/course-filters"
import type { WizardParams } from "@/components/groups/types"
import { WizardShell } from "@/components/groups/wizard-shell"

const FACULTY_ICONS: Record<string, IconType> = {
architettura: FiPenTool,
design: FiFeather,
ingegneria: FiBox,
}

function stepHref(params: Pick<WizardParams, "school" | "level" | "course">) {
const query = new URLSearchParams()
if (params.school) query.set("school", params.school)
if (params.level) query.set("level", params.level)
if (params.course) query.set("course", params.course)
const search = query.toString()
return search ? `/groups/didattica?${search}` : "/groups/didattica"
}

export default async function DidatticaWizard({ searchParams }: { searchParams: Promise<WizardParams> }) {
const { school, level, course, campus, lang } = await searchParams

if (school && level && course) return <GroupsResult school={school} level={level} course={course} />
if (school && level) return <CourseStep school={school} level={level} campus={campus} lang={lang} />
if (school) return <LevelStep school={school} />

return <FacultyStep />
}

function FacultyStep() {
return (
<WizardShell activeStep={0} title="Seleziona la tua facoltà" caption="Troviamo il tuo gruppo partendo dalla base!">
<div className="grid gap-4 sm:grid-cols-3 sm:gap-12.5">
{FACULTIES.map((faculty) => (
<CardIcon
key={faculty.slug}
title={faculty.name}
icon={FACULTY_ICONS[faculty.slug] ?? FiPenTool}
href={stepHref({ school: faculty.slug })}
hoverEffect
/>
))}
</div>
</WizardShell>
)
}

function LevelStep({ school }: { school: string }) {
const faculty = getFaculty(school)
if (!faculty) notFound()

return (
<WizardShell activeStep={1} title="A che punto del percorso sei?" caption={faculty.name}>
<div className="grid gap-4 sm:grid-cols-2 sm:gap-6">
{LEVELS.map((level) => (
<Link key={level.slug} href={stepHref({ school, level: level.slug })} className="block">
<CardPathSelection caption={level.name} className="w-full" icon={FiBook} />
</Link>
))}
</div>
</WizardShell>
)
}

function CourseStep({
school,
level,
campus,
lang,
}: {
school: string
level: string
campus?: string
lang?: string
}) {
const faculty = getFaculty(school)
const currentLevel = getLevel(level)
if (!faculty || !currentLevel) notFound()

const { campuses, languages } = getCourseFilters(school, level)
const courses = getCourses(school, level).filter(
(course) => (!campus || course.location === campus) && (!lang || course.language === lang)
)

return (
<WizardShell
activeStep={2}
title="Quale corso segui?"
caption={`Perfetto, cerchiamo tra i corsi della ${currentLevel.name.toLowerCase()}!`}
action={<CourseFilters campuses={campuses} languages={languages} />}
>
<div className="flex flex-col gap-3">
{courses.length === 0 && (
<p className="typo-body-medium text-text-secondary">Nessun corso corrisponde ai filtri.</p>
)}
{courses.map((course) => (
<Link key={course.slug} href={stepHref({ school, level, course: course.slug })} className="block">
<CardCourse
courseName={course.name}
location={course.location}
language={course.language}
iconSelect={FiChevronRight}
/>
</Link>
))}
</div>
</WizardShell>
)
}

function GroupsResult({ school, level, course }: { school: string; level: string; course: string }) {
const currentCourse = getCourse(school, level, course)
const currentLevel = getLevel(level)
if (!currentCourse || !currentLevel) notFound()

return (
<main className="mx-auto flex min-h-svh max-w-7xl flex-col items-center gap-26 px-4 pt-42 pb-16">
<header className="flex flex-col items-center gap-2 text-center">
<h1 className="typo-display-medium">{currentCourse.name}</h1>
<p className="typo-headline-small text-text-secondary">Laurea {currentLevel.name}</p>
</header>

<div className="flex flex-row gap-3">
<CardCourseGroup groupName="Gruppo Generale Scuola" />
<CardCourseGroup groupName="Gruppo Generale Community" secondary />
</div>
</main >
)
}
48 changes: 48 additions & 0 deletions src/app/groups/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FiBook, FiStar } from "react-icons/fi"
import { CardIcon } from "@/components/card-icon"
import { GroupSearch } from "@/components/home/group-search"

const groups = [
{
title: "Gruppi Didattici",
caption: "Ingegneria, Architettura, Design, e tutti i gruppi del tuo corso di studi.",
href: "/groups/didattica",
icon: FiBook,
},
{
title: "Gruppi Extra",
caption: "Affitti, mercatino, eventi, hobby e tutto ciò che riguarda la vita studentesca.",
href: "/groups/extra",
icon: FiStar,
},
] as const

export default function Home() {
return (
<main className="w-full">
<div className="mx-auto flex min-h-svh flex-col items-center justify-center gap-16 px-4 py-32 md:gap-34">
<div className="flex w-full flex-col items-center gap-5 text-center">
<h2 className="typo-display-large md:typo-display-extralarge w-fit bg-linear-to-b from-text-primary to-text-secondary bg-clip-text py-4 text-transparent md:py-14">
Groups
</h2>
<GroupSearch />
</div>

<div className="flex w-full flex-col items-center justify-center gap-4 md:flex-row md:gap-13">
{groups.map((group) => (
<CardIcon
key={group.title}
title={group.title}
description={group.caption}
href={group.href}
icon={group.icon}
align="start"
className="w-full max-w-108"
size="compact"
/>
))}
</div>
</div>
</main>
)
}
4 changes: 2 additions & 2 deletions src/components/card-path-selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { IconType } from "react-icons"
import { BsBook } from "react-icons/bs"
import { Card, CardAction, CardContent } from "./ui/card"

export function CardPathSelection({ caption, icon: Icon = BsBook }: { caption: string; icon?: IconType }) {
export function CardPathSelection({ caption, icon: Icon = BsBook, className }: { caption: string; icon?: IconType; className?: string }) {
return (
<Card className="h-fit w-136 px-10 py-7.5" hoverBackground>
<Card className={`h-fit w-136 px-10 py-7.5 ${className || ""}`} hoverBackground>
<CardContent className="typo-body-large flex flex-row items-center justify-center gap-1.25 whitespace-nowrap font-normal leading-6 tracking-[0.03125rem]">
<CardAction icon={Icon} iconSize="xs" /> {caption}
</CardContent>
Expand Down
44 changes: 44 additions & 0 deletions src/components/groups/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Course, Faculty, Level } from "@/components/groups/types"

export const FACULTIES: Faculty[] = [
{ slug: "architettura", name: "Scuola di Architettura" },
{ slug: "design", name: "Scuola di Design" },
{ slug: "ingegneria", name: "Scuole di Ingegneria" },
]

export const LEVELS: Level[] = [
{ slug: "triennale", name: "Triennale (o Ciclo Unico)" },
{ slug: "magistrale", name: "Magistrale" },
]

export const COURSES: Record<string, Course[]> = {
"design/triennale": [
{ slug: "design-prodotto", name: "Design del Prodotto", location: "Milano Bovisa", language: "ITA" },
{ slug: "design-comunicazione", name: "Design della Comunicazione", location: "Milano Bovisa", language: "ITA" },
{ slug: "design-moda", name: "Design della Moda", location: "Milano Bovisa", language: "ITA" },
],
}

export function getFaculty(slug: string) {
return FACULTIES.find((faculty) => faculty.slug === slug)
}

export function getLevel(slug: string) {
return LEVELS.find((level) => level.slug === slug)
}

export function getCourses(faculty: string, level: string) {
return COURSES[`${faculty}/${level}`] ?? []
}

export function getCourse(faculty: string, level: string, slug: string) {
return getCourses(faculty, level).find((course) => course.slug === slug)
}

export function getCourseFilters(faculty: string, level: string) {
const courses = getCourses(faculty, level)
return {
campuses: [...new Set(courses.map((course) => course.location))],
languages: [...new Set(courses.map((course) => course.language))],
}
}
67 changes: 67 additions & 0 deletions src/components/groups/course-filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client"

import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"

const ALL = "all"

type FilterSelectProps = {
label: string
value: string
options: string[]
onChange: (value: string) => void
}

function FilterSelect({ label, value, options, onChange }: FilterSelectProps) {
return (
<Select value={value} onValueChange={onChange}>
{/* larghezza fissa dal wrapper: testo a sinistra, freccia attaccata al bordo destro */}
<SelectTrigger className="w-full justify-between">
<SelectValue placeholder={label} className="truncate" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>{label}</SelectItem>
{options.map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
)
}

export function CourseFilters({ campuses, languages }: { campuses: string[]; languages: string[] }) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()

const update = (key: string, value: string) => {
const params = new URLSearchParams(searchParams.toString())
if (value === ALL) params.delete(key)
else params.set(key, value)
const query = params.toString()
router.push(query ? `${pathname}?${query}` : pathname)
}

return (
<div className="flex flex-wrap gap-3">
<div className="w-56">
<FilterSelect
label="Tutti i campus"
value={searchParams.get("campus") ?? ALL}
options={campuses}
onChange={(value) => update("campus", value)}
/>
</div>
<div className="w-56">
<FilterSelect
label="Tutte le lingue"
value={searchParams.get("lang") ?? ALL}
options={languages}
onChange={(value) => update("lang", value)}
/>
</div>
</div>
)
}
14 changes: 14 additions & 0 deletions src/components/groups/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type Campus = "Milano Leonardo" | "Milano Bovisa" | "Cremona" | "Lecco" | "Mantova" | "Piacenza" | "Xi'an"
export type Language = "ITA" | "ENG"

export type Faculty = { slug: string; name: string }
export type Level = { slug: string; name: string }
export type Course = { slug: string; name: string; location: Campus; language: Language }

export type WizardParams = {
school?: string
level?: string
course?: string
campus?: string
lang?: string
}
Loading
Loading