diff --git a/backend/alembic/versions/7f60f0211af1_add_top_events_period_to_user.py b/backend/alembic/versions/7f60f0211af1_add_top_events_period_to_user.py new file mode 100644 index 00000000..b1746fda --- /dev/null +++ b/backend/alembic/versions/7f60f0211af1_add_top_events_period_to_user.py @@ -0,0 +1,36 @@ +"""add_top_events_period_to_user + +Revision ID: 7f60f0211af1 +Revises: feeb1c78c0a2 +Create Date: 2024-09-26 21:57:33.021370 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "7f60f0211af1" +down_revision: Union[str, None] = "feeb1c78c0a2" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "user", + sa.Column( + "top_events_period", sa.Integer(), nullable=False, server_default="7" + ), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("user", "top_events_period") + # ### end Alembic commands ### diff --git a/backend/src/auth/models.py b/backend/src/auth/models.py index 745070f9..9d432fe1 100644 --- a/backend/src/auth/models.py +++ b/backend/src/auth/models.py @@ -1,5 +1,5 @@ from enum import Enum -from sqlalchemy import Column, ForeignKey, Table +from sqlalchemy import Column, ForeignKey, Integer, Table from sqlalchemy.orm import Mapped, mapped_column, relationship from src.common.base import Base from src.events.models import Category @@ -29,6 +29,7 @@ class User(Base): categories: Mapped[list[Category]] = relationship(secondary=user_category_table) notes: Mapped[list[Note]] = relationship("Note", backref="user") + top_events_period: Mapped[int] = mapped_column(Integer, default=7) class PasswordReset(Base): diff --git a/backend/src/auth/schemas.py b/backend/src/auth/schemas.py index 335a9e86..c978ad9e 100644 --- a/backend/src/auth/schemas.py +++ b/backend/src/auth/schemas.py @@ -9,6 +9,7 @@ class UserPublic(BaseModel): email: EmailStr categories: list[CategoryDTO] + top_events_period: int = 7 class Token(BaseModel): diff --git a/backend/src/profile/router.py b/backend/src/profile/router.py index fb72c4a5..7eee3599 100644 --- a/backend/src/profile/router.py +++ b/backend/src/profile/router.py @@ -18,15 +18,19 @@ def update_profile( user: Annotated[User, Depends(get_current_user)], session=Depends(get_session), ) -> UserPublic: - categories = session.scalars( - select(Category).where(Category.id.in_(data.category_ids)) - ).all() - user = session.get(User, user.id) - user.categories = categories - session.add(user) - session.commit() - session.refresh(user) + if data.category_ids: + categories = session.scalars( + select(Category).where(Category.id.in_(data.category_ids)) + ).all() + user.categories = categories + if data.top_events_period: + user.top_events_period = data.top_events_period + + if data.category_ids or data.top_events_period: + session.add(user) + session.commit() + session.refresh(user) return user diff --git a/backend/src/profile/schemas.py b/backend/src/profile/schemas.py index 12a71008..b2586b1a 100644 --- a/backend/src/profile/schemas.py +++ b/backend/src/profile/schemas.py @@ -2,4 +2,5 @@ class ProfileUpdate(BaseModel): - category_ids: list[int] + category_ids: list[int] = None + top_events_period: int = 7 diff --git a/frontend/app/home.tsx b/frontend/app/home.tsx index 236feb01..deef72f1 100644 --- a/frontend/app/home.tsx +++ b/frontend/app/home.tsx @@ -1,35 +1,65 @@ import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; +import { ChevronDown } from "lucide-react"; import { getEventsEventsGet, MiniEventDTO } from "@/client"; import ScrollToTopButton from "@/components/navigation/scroll-to-top-button"; import ArticleLoading from "@/components/news/article-loading"; import NewsArticle from "@/components/news/news-article"; +import { useUpdateTopEventsPeriod } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; import { parseDate } from "@/utils/date"; +const enum Period { + Day = 1, + Week = 7, + Month = 30, +} + +const getDisplayValueFor = (period: Period) => { + switch (period) { + case Period.Day: + return "past day"; + case Period.Month: + return "month"; + case Period.Week: + default: + return "week"; + } +}; + const NUM_TOP_EVENTS = 10; -const DAYS_PER_WEEK = 7; +const DEFAULT_EVENT_PERIOD = Period.Week; /* This component should only be rendered to authenticated users */ const Home = () => { - const eventStartDate = useMemo(() => { - const date = new Date(); - date.setDate(date.getDate() - DAYS_PER_WEEK); - return date; - }, []); - const [topEvents, setTopEvents] = useState([]); const [isLoaded, setIsLoaded] = useState(false); - const user = useUserStore((state) => state.user); + const [showDropdown, setShowDropdown] = useState(false); + const [selectedPeriod, setSelectedPeriod] = + useState(DEFAULT_EVENT_PERIOD); + const user = useUserStore((state) => state.user); + const updateTopEventsMutation = useUpdateTopEventsPeriod(); const router = useRouter(); - if (!user!.categories.length) { - router.push("/onboarding"); - } + + const eventPeriod = useMemo( + () => + user?.top_events_period ? user.top_events_period : DEFAULT_EVENT_PERIOD, + [user?.top_events_period], + ); + + const eventStartDate = useMemo(() => { + const eventStartDate = new Date(); + eventStartDate.setDate(eventStartDate.getDate() - eventPeriod); + return eventStartDate; + }, [eventPeriod]); + + useEffect(() => setSelectedPeriod(eventPeriod), [eventPeriod]); useEffect(() => { const fetchTopEvents = async () => { + setIsLoaded(false); const formattedEventStartDate = eventStartDate .toISOString() .split("T")[0]; @@ -51,8 +81,8 @@ const Home = () => { }, }; } - const response = await getEventsEventsGet(eventQuery); + const response = await getEventsEventsGet(eventQuery); if (response.error) { console.log(response.error); } else { @@ -61,9 +91,25 @@ const Home = () => { } }; + if (user?.top_events_period) setSelectedPeriod(user.top_events_period); fetchTopEvents(); }, [user, eventStartDate]); + // Handle the option selection and close dropdown + const handleSelection = (period: Period) => { + if (period != selectedPeriod) { + // Update the text + setSelectedPeriod(period); + updateTopEventsMutation.mutate({ timePeriod: period }); + } + // Close dropdown + setShowDropdown(false); + }; + + if (!user!.categories.length) { + router.push("/onboarding"); + } + return (
{ className="flex flex-col mb-4 gap-y-2 px-4 md:px-8 xl:px-12" id="homePage" > -

- What happened this week -

+
+ + What happened this + +
+ + {showDropdown && ( +
+
+ + + +
+
+ )} +
+
{parseDate(eventStartDate)} - {parseDate(new Date())} diff --git a/frontend/client/schemas.gen.ts b/frontend/client/schemas.gen.ts index b314fa65..3c7a4477 100644 --- a/frontend/client/schemas.gen.ts +++ b/frontend/client/schemas.gen.ts @@ -576,9 +576,13 @@ export const ProfileUpdateSchema = { type: "array", title: "Category Ids", }, + top_events_period: { + type: "integer", + title: "Top Events Period", + default: 7, + }, }, type: "object", - required: ["category_ids"], title: "ProfileUpdate", } as const; @@ -655,6 +659,11 @@ export const UserPublicSchema = { type: "array", title: "Categories", }, + top_events_period: { + type: "integer", + title: "Top Events Period", + default: 7, + }, }, type: "object", required: ["id", "email", "categories"], diff --git a/frontend/client/types.gen.ts b/frontend/client/types.gen.ts index 41937891..40b88a11 100644 --- a/frontend/client/types.gen.ts +++ b/frontend/client/types.gen.ts @@ -139,7 +139,8 @@ export type PointMiniDTO = { }; export type ProfileUpdate = { - category_ids: Array; + category_ids?: Array; + top_events_period?: number; }; export type ReadDTO = { @@ -162,6 +163,7 @@ export type UserPublic = { id: number; email: string; categories: Array; + top_events_period?: number; }; export type UserQuestionMiniDTO = { diff --git a/frontend/queries/user.ts b/frontend/queries/user.ts index 77b899cb..7b32e17a 100644 --- a/frontend/queries/user.ts +++ b/frontend/queries/user.ts @@ -37,6 +37,22 @@ export const useUpdateProfile = () => { }); }; +export const useUpdateTopEventsPeriod = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ timePeriod }: { timePeriod: number }) => { + return updateProfileProfilePut({ + body: { + top_events_period: timePeriod, + }, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QueryKeys.UserProfile] }); + }, + }); +}; + export const useChangePassword = () => { return useMutation({ mutationFn: ({