From 1be278bffe77351bb1e390973690b1d1a020ada1 Mon Sep 17 00:00:00 2001 From: Bianca Date: Wed, 15 Jul 2026 15:39:39 +0200 Subject: [PATCH 1/3] feat: add Groups page with group listings and search functionality --- src/app/groups/page.tsx | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/app/groups/page.tsx diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx new file mode 100644 index 0000000..9621fe6 --- /dev/null +++ b/src/app/groups/page.tsx @@ -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: "/matricole", + icon: FiBook, + }, + { + title: "Gruppi Extra", + caption: "Affitti, mercatino, eventi, hobby e tutto ciò che riguarda la vita studentesca.", + href: "/projects", + icon: FiStar, + }, +] as const + +export default function Home() { + return ( +
+
+
+

+ Groups +

+ +
+ +
+ {groups.map((group) => ( + + ))} +
+
+
+ ) +} From 111739f6a405a038b8361ad098d7b9cc167638c5 Mon Sep 17 00:00:00 2001 From: Bianca Date: Wed, 15 Jul 2026 15:51:16 +0200 Subject: [PATCH 2/3] fix: update layout and spacing for Groups page --- src/app/groups/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx index 9621fe6..fc30615 100644 --- a/src/app/groups/page.tsx +++ b/src/app/groups/page.tsx @@ -20,15 +20,15 @@ const groups = [ export default function Home() { return (
-
+
-

+

Groups

-
+
{groups.map((group) => ( Date: Thu, 23 Jul 2026 01:56:22 +0200 Subject: [PATCH 3/3] feat: implement groups page with faculty and course selection wizard --- src/app/groups/didattica/page.tsx | 146 +++++++++++++++++++++++ src/app/groups/page.tsx | 4 +- src/components/card-path-selection.tsx | 4 +- src/components/groups/constants.ts | 44 +++++++ src/components/groups/course-filters.tsx | 67 +++++++++++ src/components/groups/types.ts | 14 +++ src/components/groups/wizard-shell.tsx | 64 ++++++++++ 7 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 src/app/groups/didattica/page.tsx create mode 100644 src/components/groups/constants.ts create mode 100644 src/components/groups/course-filters.tsx create mode 100644 src/components/groups/types.ts create mode 100644 src/components/groups/wizard-shell.tsx diff --git a/src/app/groups/didattica/page.tsx b/src/app/groups/didattica/page.tsx new file mode 100644 index 0000000..a50cf19 --- /dev/null +++ b/src/app/groups/didattica/page.tsx @@ -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 = { + architettura: FiPenTool, + design: FiFeather, + ingegneria: FiBox, +} + +function stepHref(params: Pick) { + 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 }) { + const { school, level, course, campus, lang } = await searchParams + + if (school && level && course) return + if (school && level) return + if (school) return + + return +} + +function FacultyStep() { + return ( + +
+ {FACULTIES.map((faculty) => ( + + ))} +
+
+ ) +} + +function LevelStep({ school }: { school: string }) { + const faculty = getFaculty(school) + if (!faculty) notFound() + + return ( + +
+ {LEVELS.map((level) => ( + + + + ))} +
+
+ ) +} + +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 ( + } + > +
+ {courses.length === 0 && ( +

Nessun corso corrisponde ai filtri.

+ )} + {courses.map((course) => ( + + + + ))} +
+
+ ) +} + +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 ( +
+
+

{currentCourse.name}

+

Laurea {currentLevel.name}

+
+ +
+ + +
+
+ ) +} diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx index fc30615..809f526 100644 --- a/src/app/groups/page.tsx +++ b/src/app/groups/page.tsx @@ -6,13 +6,13 @@ const groups = [ { title: "Gruppi Didattici", caption: "Ingegneria, Architettura, Design, e tutti i gruppi del tuo corso di studi.", - href: "/matricole", + href: "/groups/didattica", icon: FiBook, }, { title: "Gruppi Extra", caption: "Affitti, mercatino, eventi, hobby e tutto ciò che riguarda la vita studentesca.", - href: "/projects", + href: "/groups/extra", icon: FiStar, }, ] as const diff --git a/src/components/card-path-selection.tsx b/src/components/card-path-selection.tsx index f615c3e..57c211d 100644 --- a/src/components/card-path-selection.tsx +++ b/src/components/card-path-selection.tsx @@ -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 ( - + {caption} diff --git a/src/components/groups/constants.ts b/src/components/groups/constants.ts new file mode 100644 index 0000000..24a32ab --- /dev/null +++ b/src/components/groups/constants.ts @@ -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 = { + "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))], + } +} diff --git a/src/components/groups/course-filters.tsx b/src/components/groups/course-filters.tsx new file mode 100644 index 0000000..2d09e19 --- /dev/null +++ b/src/components/groups/course-filters.tsx @@ -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 ( + + ) +} + +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 ( +
+
+ update("campus", value)} + /> +
+
+ update("lang", value)} + /> +
+
+ ) +} diff --git a/src/components/groups/types.ts b/src/components/groups/types.ts new file mode 100644 index 0000000..95beeec --- /dev/null +++ b/src/components/groups/types.ts @@ -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 +} diff --git a/src/components/groups/wizard-shell.tsx b/src/components/groups/wizard-shell.tsx new file mode 100644 index 0000000..dd7cb74 --- /dev/null +++ b/src/components/groups/wizard-shell.tsx @@ -0,0 +1,64 @@ +"use client" + +import { useRouter } from "next/navigation" +import type { ReactNode } from "react" +import { FiArrowLeft, FiX } from "react-icons/fi" +import { Glass } from "@/components/glass" +import { cn } from "@/lib/utils" + +const STEPS = ["Facoltà", "Triennale/Magistrale", "Corso"] as const + +const iconButton = "grid size-10 shrink-0 place-items-center rounded-full bg-white/60" + +type WizardShellProps = { + activeStep: number + title: string + caption?: string + action?: ReactNode + children: ReactNode +} + +export function WizardShell({ activeStep, title, caption, action, children }: WizardShellProps) { + const router = useRouter() + + return ( +
+ +
+ + +
    + {STEPS.map((label, index) => ( +
  1. + {label} +
  2. + ))} +
+ + +
+ +
+
+
+ {caption &&

{caption}

} +

{title}

+
+ {action &&
{action}
} +
+
{children}
+
+
+
+ ) +}