diff --git a/next.config.ts b/next.config.ts index 0910bf7..b78d338 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,6 +10,10 @@ const nextConfig = { protocol: "https", hostname: "i.pravatar.cc", }, + { + protocol: "https", + hostname: "imgs.search.brave.com", + }, ], qualities: [75, 85], }, diff --git a/src/app/favorites/page.tsx b/src/app/favorites/page.tsx index 3a33fa7..ea96acd 100644 --- a/src/app/favorites/page.tsx +++ b/src/app/favorites/page.tsx @@ -1,7 +1,8 @@ "use client"; -import { useState } from "react"; +import { useState, useDeferredValue, useMemo } from "react"; import Link from "next/link"; +import dynamic from "next/dynamic"; import { useFavorites } from "@/lib/hooks/useFavorites"; import { useQuery, keepPreviousData } from "@tanstack/react-query"; import { api } from "@/lib/api"; @@ -9,6 +10,21 @@ import { fromEventSessionDetail } from "@/lib/utils/sessionMappers"; import { sortScheduleSessions } from "@/lib/utils/sortSessions"; import { ScheduleTable } from "@/components/sessions/ScheduleTable"; import { TablePagination } from "@/components/ui/TablePagination"; +import { Select } from "@/components/ui/Select"; +import { Search, X } from "lucide-react"; + +const DateRangePicker = dynamic(() => + import("@/components/ui/DateRangePicker").then((m) => ({ default: m.DateRangePicker })), +); + +type FavStatus = "all" | "live" | "upcoming" | "ended"; + +const STATUS_OPTIONS = [ + { value: "all" as const, label: "All statuses" }, + { value: "live" as const, label: "Live now" }, + { value: "upcoming" as const, label: "Upcoming" }, + { value: "ended" as const, label: "Ended" }, +]; const PAGE_SIZE = 5; @@ -29,17 +45,63 @@ export default function FavoritesPage() { placeholderData: keepPreviousData, }); + const [favSearch, setFavSearch] = useState(""); + const deferredFavSearch = useDeferredValue(favSearch); + const [favStatus, setFavStatus] = useState("all"); + const [dateFrom, setDateFrom] = useState(""); + const [dateTo, setDateTo] = useState(""); + const sorted = sortScheduleSessions( (sessions ?? []).filter(Boolean).map(fromEventSessionDetail), "asc", ); + const filtered = useMemo(() => { + const now = new Date(); + let result = sorted; + + if (deferredFavSearch) { + result = result.filter((s) => + s.title.toLowerCase().includes(deferredFavSearch.toLowerCase()), + ); + } + + if (favStatus !== "all") { + result = result.filter((s) => { + if (favStatus === "live") return s.isLive; + if (favStatus === "upcoming") return s.startTime > now; + if (favStatus === "ended") return s.endTime < now; + return true; + }); + } - const totalFavPages = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE)); + if (dateFrom) { + const from = new Date(dateFrom); + result = result.filter((s) => s.endTime >= from); + } + if (dateTo) { + const to = new Date(dateTo); + to.setHours(23, 59, 59, 999); + result = result.filter((s) => s.startTime <= to); + } + + return result; + }, [sorted, deferredFavSearch, favStatus, dateFrom, dateTo]); + + const totalFavPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const safeFavPage = Math.min(favPage, totalFavPages); - const paginatedSessions = sorted.slice( + const paginatedSessions = filtered.slice( (safeFavPage - 1) * PAGE_SIZE, safeFavPage * PAGE_SIZE, ); + const hasActiveFilters = favStatus !== "all" || !!favSearch || !!dateFrom || !!dateTo; + + const clearAll = () => { + setFavSearch(""); + setFavStatus("all"); + setDateFrom(""); + setDateTo(""); + setFavPage(1); + }; return (
@@ -97,12 +159,43 @@ export default function FavoritesPage() {
) : ( + <> +
+
+ + { setFavSearch(e.target.value); setFavPage(1); }} + placeholder="Search favorites" + className="w-full h-9 pl-8 pr-3 text-sm font-medium text-ivory placeholder-ivory/40 focus:outline-none transition-colors rounded-lg" + style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)" }} + /> +
+
+ { setQuestionSearch(e.target.value); setQuestionPage(1); }} + placeholder="Search questions" + className="w-full h-9 pl-8 pr-3 text-sm font-medium text-ivory placeholder-ivory/40 focus:outline-none transition-colors rounded-lg" + style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)" }} + /> +
- {!questionsBySession ? ( + {!questionsBySession && sessionIds.length > 0 ? (
{Array.from({ length: 3 }).map((_, i) => (
@@ -148,7 +228,7 @@ export default function SpeakerProfilePage() {
))}
- ) : questionsWithSession.length > 0 ? ( + ) : searchedQuestions.length > 0 ? (
{paginatedQuestions.map((q) => (
@@ -207,7 +287,36 @@ export default function SpeakerProfilePage() { SESSIONS
- {sortedSessions.length} TOTAL + {filteredSessions.length} TOTAL +
+
+
+ + { setSessSearch(e.target.value); setSessPage(1); }} + placeholder="Search a session" + className="w-full h-9 pl-8 pr-3 text-sm font-medium text-ivory placeholder-ivory/40 focus:outline-none transition-colors rounded-lg" + style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)" }} + /> +
+
+ setValue(e.target.value)} + placeholder="Search a speaker" + className="w-full h-9 pl-8 pr-3 text-sm font-medium text-ivory placeholder-ivory/40 focus:outline-none transition-colors rounded-lg" + style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)" }} + /> +
+ {hasSearch && ( + + )} + + ); +}