Skip to content

Commit

Permalink
Merge pull request #94 from wwsalmon/datefiltering
Browse files Browse the repository at this point in the history
add filtering updates by date
  • Loading branch information
wwsalmon authored Aug 16, 2023
2 parents 8231195 + 7bfc860 commit 24bc18a
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 53 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ yarn-error.log*
.vercel

# IDE
.idea
.idea
.vscode
28 changes: 15 additions & 13 deletions components/Activity.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { format, subDays, addDays } from 'date-fns';
import { format, subDays, addDays, parseISO, parse } from 'date-fns';
import ActivityGrid, { ActivityDayMap } from './ActivityGrid';
import { useState } from 'react';
import { User } from '../utils/types';
import classNames from 'classnames';
import { formatInTimeZone } from 'date-fns-tz';

const numCols = 53;

Expand Down Expand Up @@ -37,23 +38,24 @@ function makeGridArr(arr: { date: string }[], year: string): { gridHashmap: Acti
let years = []
let totalCount = 0;
for (let item of arr) {
const year = format(new Date(item.date), "yyyy");
if (!years.includes(year)) years.push(year);
const FORMAT_STR = 'yyyy-MM-dd'
const dateFromDB = formatInTimeZone(parseISO(item.date), 'UTC', FORMAT_STR)

const index = format(new Date(item.date), "yyyy-MM-dd");
try {
gridHashmap[index].count += 1;
totalCount += 1;
} catch (error) {
// the date isn't in the past year
continue;
}
const year = format(parse(dateFromDB, FORMAT_STR, new Date()), "yyyy")
if (!years.includes(year)) years.push(year);
try {
gridHashmap[dateFromDB].count += 1;
totalCount += 1;
} catch (error) {
// the date isn't in the past year
continue;
}
}

return { gridHashmap, years, totalCount };
}

const Activity = ({ updates, pageUser }: { updates: { date: string }[], pageUser: User}) => {
const Activity = ({ updates, pageUser, onClickDate }: { updates: { date: string }[], pageUser: User, onClickDate: (date: string) => void}) => {
const [year, setYear] = useState<string>("last-year"); // a year string OR "last-year"
const { gridHashmap, years, totalCount } = makeGridArr(updates, year);
const joinDate = new Date(pageUser.createdAt);
Expand All @@ -80,7 +82,7 @@ const Activity = ({ updates, pageUser }: { updates: { date: string }[], pageUser
))}
</div >
</div>
<ActivityGrid data={gridHashmap} />
<ActivityGrid data={gridHashmap} onClickDate={onClickDate} />
<div className="text-stone-500 text-sm mt-6">
{totalCount} update{totalCount === 1 ? "" : "s"} in {year === "last-year" ? "the past year" : year}{(year === joinYear.toString()) && `. ${pageUser.name} joined Updately on ${format(joinDate, "MMMM d, yyyy")}`}
</div>
Expand Down
16 changes: 9 additions & 7 deletions components/ActivityGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode } from "react";
import classNames from "classnames";
import { format } from "date-fns";
import { ReactNode } from "react";

interface ActivityDay {
date: Date,
Expand All @@ -23,7 +24,7 @@ const GridLabel = ({ row, col, children }: { row: number, col: number, children:
><span>{children}</span></div>
)

export default function ActivityGrid({ data, label, color }: { data: ActivityDayMap, label?: string, color?: string }) {
export default function ActivityGrid({ data, label, color, onClickDate }: { data: ActivityDayMap, label?: string, color?: string, onClickDate: (date: string) => void }) {
const numCols = 53;

const monthChangeDays: ActivityDay[] = Object.values(data).filter((d, i, a) => (
Expand Down Expand Up @@ -53,15 +54,16 @@ export default function ActivityGrid({ data, label, color }: { data: ActivityDay
{Object.values(data).map(dateActivity => (
<div
style={{
backgroundColor: dateActivity.count > 0 ? (color || "#0026ff") : "#000",
opacity: dateActivity.count > 0 ? dateActivity.count / maxCount : 0.05,
width: 13,
height: 13,
opacity: dateActivity.count > 0 ? dateActivity.count / maxCount : 1,
gridRow: dateActivity.day + 2,
gridColumn: dateActivity.week + 2,
borderRadius: 3,
}}
className={classNames(dateActivity.count > 0 ? "bg-tblue cursor-pointer" : "bg-gray-100", "hover:!opacity-100 w-[13px] h-[13px] rounded-[3px]")}
key={format(dateActivity.date, "yyyy-MM-dd")}
onClick={() => {
if (dateActivity.count > 0)
onClickDate(format(dateActivity.date, "yyyy-MM-dd"))
}}
/>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function Navbar() {

useEffect(() => {
setNotificationsIter(notificationsIter + 1);
}, [router.asPath]);
}, [router.asPath.split("?")[0]]);

const {theme, setTheme} = useTheme();

Expand Down
19 changes: 17 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "updately",
"version": "0.11.1",
"version": "0.11.2",
"private": true,
"license": "BSD-3-Clause",
"scripts": {
Expand All @@ -16,6 +16,7 @@
"axios": "^0.21.0",
"classnames": "^2.3.2",
"date-fns": "^2.16.1",
"date-fns-tz": "^2.0.0",
"html-react-parser": "^0.14.2",
"mongoose": "^5.10.16",
"next": "^12.1.6",
Expand Down
99 changes: 77 additions & 22 deletions pages/[username]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {notificationModel, userModel} from "../../models/models";
import {FaSort} from "react-icons/fa";
import getLookup from "../../utils/getLookup";
import Activity from "../../components/Activity";
import { FiX } from "react-icons/fi";

const options = [
{ value: SortBy.Date, label: 'Date' },
Expand All @@ -33,7 +34,8 @@ export default function UserProfile(props: { user: UserAgg, userData: User, foll
const [userData, setUserData] = useState<User>(props.userData);
const [sortBy, setSortBy] = useState<SortBy>(SortBy.Date);
const [filterBy, setFilterBy] = useState<string>("all"); // all, drafts, tag
const {data: updatesObj, error: feedError} = useSWR(`/api/get-curr-user-updates?page=${page}&urlName=${pageUser.urlName}&sortBy=${sortBy}&filter=${filterBy}`, fetcher);
const dateQuery = router.query.date !== undefined ? router.query.date as string : ""; // format in yyyy-MM-dd
const {data: updatesObj, error: feedError} = useSWR(`/api/get-curr-user-updates?page=${page}&urlName=${pageUser.urlName}&sortBy=${sortBy}&filter=${filterBy}&date=${dateQuery}`, fetcher);
const {data: updateActivity, error: updateActivityError} = useSWR(`/api/activity?userId=${pageUser._id}`, fetcher);
const updates: Update[] = (updatesObj && updatesObj.length && updatesObj[0].paginatedResults.length) ? updatesObj[0].paginatedResults : [];
const numUpdates = (updatesObj && updatesObj.length && updatesObj[0].totalCount.length) ? updatesObj[0].totalCount[0].estimatedDocumentCount : 0;
Expand All @@ -60,20 +62,24 @@ export default function UserProfile(props: { user: UserAgg, userData: User, foll
});
}
}, [router.query.notification]);
// useEffect(() => {
// if (router.query.filter && ["all", "published", "draft", ...filterOptions].map(d => d.value).includes(router.query.filter as string)) {
// setFilterBy(router.query.filter as string);
// }
// }, [router.query.filter]);
// if(router.query.date) {
// setDateQuery(router.query.date as string); // this seems bad since we might want multiple dates at some point
// }
// useEffect(() => {
// if (router.query.date) {
// setDateQuery(router.query.date as string);
// }
// }, [router.query.date]);

useEffect(() => {
if (router.query.filter && ["all", "published", "draft", ...filterOptions].map(d => d.value).includes(router.query.filter as string)) {
setFilterBy(router.query.filter as string);
}
}, [router.query.filter]);

useEffect(() => {
router.push(`/@${pageUser.urlName}?filter=${encodeURIComponent(filterBy)}`, undefined, {shallow: true});
}, [filterBy]);

useEffect(() => {
setPage(1);
}, [filterBy]);
// useEffect(() => {
// // if (dateQuery) {setFilterBy("all");}
// // router.push(`/@${pageUser.urlName}?filter=${encodeURIComponent(filterBy)}${dateQuery ? `&date=${dateQuery}` : ""}`, undefined, {shallow: true});
// }, [filterBy, dateQuery]);

const isProfilePrivateToLoggedInUser = (pageUser.private || pageUser.truePrivate) && (!userData || !pageUser.followers.includes(props.userData.email) && !isOwner);

Expand Down Expand Up @@ -138,11 +144,27 @@ export default function UserProfile(props: { user: UserAgg, userData: User, foll

{!isProfilePrivateToLoggedInUser && (
<div className="mt-12">
<Activity updates={updateActivity || []} pageUser={pageUser}/>
<Activity updates={updateActivity || []} pageUser={pageUser} onClickDate={(date) => {
router.push(
{
query: {
filter: filterBy,
date: date,
username: `@${pageUser.urlName}`
},
pathname: router.pathname,
},
undefined,
{
scroll : false,
shallow: true
}
);
setPage(1);
}}/>
</div>
)}


<hr className="my-8"/>

{isProfilePrivateToLoggedInUser ? (
Expand All @@ -162,11 +184,44 @@ export default function UserProfile(props: { user: UserAgg, userData: User, foll
</div>
)}
<div className="flex items-center mb-12">
<select value={filterBy} onChange={e => setFilterBy(e.target.value)} className="up-ui-title">
{filterOptions.map(d => (
<option key={d.value} value={d.value}>{d.label}</option>
))}
</select>
{dateQuery ? (
<>
<p className="up-ui-title">Showing updates for {format(dateOnly(dateQuery), "MMMM d, yyyy")}</p>
<button
className="opacity-50 text-red-500 inline-flex items-center hover:opacity-75 ml-4"
onClick={() =>
router.push(
{
query: {
filter: filterBy,
username: `@${pageUser.urlName}`
},
pathname: router.pathname,
},
undefined,
{
scroll : false,
shallow: true
}
)
}
>
<FiX/>
<span className="ml-1">
Clear
</span>
</button>
</>
) : (
<select value={filterBy} onChange={e => {
setFilterBy(e.target.value)
setPage(1)
}} className="up-ui-title">
{filterOptions.map(d => (
<option key={d.value} value={d.value}>{d.label}</option>
))}
</select>
)}
<div className="flex items-center ml-auto">
<p className="up-ui-title mr-2 text-gray-400"><FaSort/></p>
<select value={sortBy} onChange={e => setSortBy(+e.target.value)}>
Expand Down Expand Up @@ -195,7 +250,7 @@ export default function UserProfile(props: { user: UserAgg, userData: User, foll
</div>
</div>
)) : (
<p className="up-ui-item-subtitle">No updates yet.</p>
<p className="up-ui-item-subtitle">{updatesObj ? "No updates yet." : "Loading..."}</p>
)}
{updates && updates.length > 0 && <PaginationBar page={page} count={numUpdates} label={"updates"} setPage={setPage}/>}
</>
Expand Down
2 changes: 1 addition & 1 deletion pages/api/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default async function getActivityHandler(req: NextApiRequest, res: NextA
await dbConnect();

const updateActivity = await updateModel.aggregate([
{$match: {userId: new mongoose.Types.ObjectId(`${req.query.userId}`)}},
{$match: {userId: new mongoose.Types.ObjectId(`${req.query.userId}`), published: true}},
{$sort: {date: -1}},
{$project: {date: 1}}
])
Expand Down
3 changes: 2 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module.exports = {
colors: {
gray: {
900: '#121212',
}
},
tblue: "#0026ff",
},
}
},
Expand Down
12 changes: 8 additions & 4 deletions utils/requests.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import axios from "axios";
import * as mongoose from "mongoose";
import {updateModel, userModel} from "../models/models";
import { getSession } from "next-auth/react";
import short from "short-uuid";
import axios from "axios";
import {getSession} from "next-auth/react";
import { SortBy, Update, User } from "./types";
import { updateModel, userModel } from "../models/models";
import getLookup from "./getLookup";
import { SortBy, Update, User } from "./types";

export interface GetUpdateRequestResponse {user: User, update: Update & {mentionedUsersArr: User[]}};

Expand Down Expand Up @@ -60,6 +60,10 @@ export async function getUpdatesRequest({req}) {

if (req.query.filter && !["all", "drafts", "published"].includes(req.query.filter)) conditions["tags"] = req.query.filter;

// Technically, this is unrobust because it checks for the exact time being 00:00:00.000Z, but all updates have this so :shrug: it works
// (might break in future if we change how we store dates)
if (req.query.date) conditions["date"] = new Date(req.query.date);

const facetStage = {$facet: {
paginatedResults: [{$skip: (+req.query.page - 1) * 10}, {$limit: 10}],
totalCount: [{$count: "estimatedDocumentCount"}],
Expand Down

1 comment on commit 24bc18a

@vercel
Copy link

@vercel vercel bot commented on 24bc18a Aug 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

updately – ./

updately-git-main-wwsalmon.vercel.app
updately-wwsalmon.vercel.app

Please sign in to comment.