From 07083b500e14a9d2491a3854b45d63b5aaa1b53a Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Sat, 6 Jan 2024 11:48:20 +0100 Subject: [PATCH 1/8] chore: Notion utility functions grouped together --- app/page.tsx | 13 +++-- app/table/page.tsx | 2 +- hooks/useContributions.tsx | 2 +- lib/notion/index.ts | 63 ------------------------ utils/contribution.ts | 33 ------------- utils/notion.ts | 99 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 111 insertions(+), 101 deletions(-) delete mode 100644 utils/contribution.ts create mode 100644 utils/notion.ts diff --git a/app/page.tsx b/app/page.tsx index 709c960..714f3e3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,8 +8,11 @@ import { INTERESTS_OPTIONS, REPOSITORIES_BY_INTERESTS, } from "@/data/filters"; -import { queryDatabase, createFilter } from "@/lib/notion"; -import { transformNotionDataToContributions } from "@/utils/contribution"; +import { queryDatabase } from "@/lib/notion"; +import { + createNotionFilter, + transformNotionDataToContributions, +} from "@/utils/notion"; import RemoveFilters from "@/components/removeFilters"; export default async function Home({ @@ -25,7 +28,11 @@ export default async function Home({ [languagesFilterIsSelected, interestsFilterIsSelected, hasSearch].filter( Boolean, ).length >= 2; - const filter = await createFilter(params.languages, params.search, REPOSITORIES_BY_INTERESTS[params.interests]) + const filter = await createNotionFilter( + params.languages, + params.search, + REPOSITORIES_BY_INTERESTS[params.interests], + ); const data = await queryDatabase({ page_size: 10, diff --git a/app/table/page.tsx b/app/table/page.tsx index 09159a9..53e155e 100644 --- a/app/table/page.tsx +++ b/app/table/page.tsx @@ -1,6 +1,6 @@ import { getGoodFirstIssues } from "@/lib/notion"; import ContributionsTable from "@/components/contributions-table/table"; -import { transformNotionDataToContributions } from "@/utils/contribution"; +import { transformNotionDataToContributions } from "@/utils/notion"; export default async function Page() { const data = await getGoodFirstIssues({ page_size: 10 }); diff --git a/hooks/useContributions.tsx b/hooks/useContributions.tsx index 78846d7..202bc86 100644 --- a/hooks/useContributions.tsx +++ b/hooks/useContributions.tsx @@ -4,7 +4,7 @@ import { QueryKey, } from "@tanstack/react-query"; import { queryDatabase } from "@/lib/notion"; -import { transformNotionDataToContributions } from "@/utils/contribution"; +import { transformNotionDataToContributions } from "@/utils/notion"; import { PaginatedContributions } from "@/types/contribution"; import { KudosQueryParameters } from "@/lib/notion/types"; diff --git a/lib/notion/index.ts b/lib/notion/index.ts index dcd0bbb..79d8c30 100644 --- a/lib/notion/index.ts +++ b/lib/notion/index.ts @@ -274,66 +274,3 @@ export async function createRepoMap() { } return out; } - -export async function createFilter(languages: string, search: string, interests: string[]) { - if (!languages && !search && !interests) { - // If all parameters are undefined, return undefined - return undefined; - } else { - const filters = []; - - if (languages) { - // If languages is defined, create a filter for Repo Language - filters.push({ - property: "Repo Language", - rollup: { - any: { - multi_select: { - contains: languages, - }, - }, - }, - }); - } - - if (search) { - // If search is defined, create a filter for Project Name - filters.push({ - property: "Project Name", - rollup: { - any: { - rich_text: { - contains: search, - }, - }, - }, - }); - } - if (interests && interests.length > 0) { - // If interests is defined and not empty, create a filter for Interests using "or" - filters.push({ - or: interests.map((interest) => ({ - property: "Project Name", - rollup: { - any: { - rich_text: { - contains: interest, - }, - }, - }, - })), - }); - } - - - if (filters.length === 1) { - // If there's only one filter, return it directly - return filters[0]; - } else if (filters.length > 1) { - // If there are multiple filters, combine them with "and" - return { - and: filters, - }; - } - } -} \ No newline at end of file diff --git a/utils/contribution.ts b/utils/contribution.ts deleted file mode 100644 index 95459ad..0000000 --- a/utils/contribution.ts +++ /dev/null @@ -1,33 +0,0 @@ -import projectLogosJson from "@/public/images/imageMap.json"; -import { Contribution } from "@/types/contribution"; -import { getImagePath } from "./github"; -import { QueryDatabaseResponse } from "@notionhq/client/build/src/api-endpoints"; - -export function transformNotionDataToContributions( - notionData: QueryDatabaseResponse, -): Contribution[] { - return notionData.results.reduce((acc: Contribution[], currentItem: any) => { - const properties = currentItem.properties; - - const [avatarKey, id] = properties["Issue Link"].url.split("/issues/"); - const contribution: Contribution = { - id, - avatar: getImagePath(avatarKey, projectLogosJson), // Assuming getImagePath is a defined function - labels: properties["Issue Labels"].multi_select.map( - (label: any) => label.name, - ), - languages: properties["Repo Language"].rollup.array[0].multi_select.map( - (language: any) => language.name, - ), - project: - properties["Project Name"].rollup.array[0].rich_text[0].plain_text, - repository: avatarKey.split("/").pop(), - title: properties["Issue Title"].title[0].text.content, - timestamp: properties["Opened Date"].date.start, - url: properties["Issue Link"].url, - }; - - acc.push(contribution); - return acc; - }, []); -} diff --git a/utils/notion.ts b/utils/notion.ts new file mode 100644 index 0000000..1beb2c7 --- /dev/null +++ b/utils/notion.ts @@ -0,0 +1,99 @@ +import projectLogosJson from "@/public/images/imageMap.json"; +import { Contribution } from "@/types/contribution"; +import { getImagePath } from "./github"; +import { QueryDatabaseResponse } from "@notionhq/client/build/src/api-endpoints"; + +export function transformNotionDataToContributions( + notionData: QueryDatabaseResponse, +): Contribution[] { + return notionData.results.reduce((acc: Contribution[], currentItem: any) => { + const properties = currentItem.properties; + + const [avatarKey, id] = properties["Issue Link"].url.split("/issues/"); + const contribution: Contribution = { + id, + avatar: getImagePath(avatarKey, projectLogosJson), // Assuming getImagePath is a defined function + labels: properties["Issue Labels"].multi_select.map( + (label: any) => label.name, + ), + languages: properties["Repo Language"].rollup.array[0].multi_select.map( + (language: any) => language.name, + ), + project: + properties["Project Name"].rollup.array[0].rich_text[0].plain_text, + repository: avatarKey.split("/").pop(), + title: properties["Issue Title"].title[0].text.content, + timestamp: properties["Opened Date"].date.start, + url: properties["Issue Link"].url, + }; + + acc.push(contribution); + return acc; + }, []); +} + +export async function createNotionFilter( + languages: string, + search: string, + interests: string[], +) { + if (!languages && !search && !interests) { + // If all parameters are undefined, return undefined + return undefined; + } else { + const filters = []; + + if (languages) { + // If languages is defined, create a filter for Repo Language + filters.push({ + property: "Repo Language", + rollup: { + any: { + multi_select: { + contains: languages, + }, + }, + }, + }); + } + + if (search) { + // If search is defined, create a filter for Project Name + filters.push({ + property: "Project Name", + rollup: { + any: { + rich_text: { + contains: search, + }, + }, + }, + }); + } + if (interests && interests.length > 0) { + // If interests is defined and not empty, create a filter for Interests using "or" + filters.push({ + or: interests.map((interest) => ({ + property: "Project Name", + rollup: { + any: { + rich_text: { + contains: interest, + }, + }, + }, + })), + }); + } + + if (filters.length === 1) { + // If there's only one filter, return it directly + return filters[0]; + } else if (filters.length > 1) { + // If there are multiple filters, combine them with "and" + return { + and: filters, + }; + } + } +} From a76c242333838d5cad870730387bee88d4721f49 Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Sat, 6 Jan 2024 12:00:11 +0100 Subject: [PATCH 2/8] chore: Filter component renamed SelectFilter --- app/page.tsx | 6 +++--- .../{filter.tsx => filters/select-filter.tsx} | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) rename components/{filter.tsx => filters/select-filter.tsx} (91%) diff --git a/app/page.tsx b/app/page.tsx index 714f3e3..c888be2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,5 @@ import ContributionsTable from "@/components/contributions-table/table"; -import Filter from "@/components/filter"; +import SelectFilter from "@/components/filters/select-filter"; import { title, subtitle } from "@/components/primitives"; import Search from "@/components/search"; import { @@ -69,13 +69,13 @@ export default async function Home({
- - { +}: ISelectFilterProps) => { const [value, setValue] = React.useState(selectedValue); const router = useRouter(); @@ -29,10 +29,9 @@ export const Filter = ({ setValue(selectedValue); }, [selectedValue]); - useEffect(() => { if (value === undefined) { - return + return; } const optionNameLowerCase = placeholder.toLowerCase(); const optionSearchParams = new URLSearchParams(searchParams.toString()); @@ -64,7 +63,6 @@ export const Filter = ({ key={item.value} value={item.value} startContent={} - > {item.label} @@ -74,4 +72,4 @@ export const Filter = ({ ); }; -export default Filter; +export default SelectFilter; From ce2b448f0410b62e443a512ca24ac688c5804283 Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Sat, 6 Jan 2024 12:04:05 +0100 Subject: [PATCH 3/8] chore: RemoveFilters component renamed ClearFilters --- app/page.tsx | 10 +++++----- .../{removeFilters.tsx => filters/clear-filters.tsx} | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) rename components/{removeFilters.tsx => filters/clear-filters.tsx} (79%) diff --git a/app/page.tsx b/app/page.tsx index c888be2..8a4b1fd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,5 @@ import ContributionsTable from "@/components/contributions-table/table"; +import ClearFilters from "@/components/filters/clear-filters"; import SelectFilter from "@/components/filters/select-filter"; import { title, subtitle } from "@/components/primitives"; import Search from "@/components/search"; @@ -13,7 +14,6 @@ import { createNotionFilter, transformNotionDataToContributions, } from "@/utils/notion"; -import RemoveFilters from "@/components/removeFilters"; export default async function Home({ searchParams, @@ -82,14 +82,14 @@ export default async function Home({ selectedValue={params.interests} /> {languagesFilterIsSelected && ( - + )} {interestsFilterIsSelected && ( - + )} - {hasSearch && } + {hasSearch && } - {showRemoveAllFilters && } + {showRemoveAllFilters && }
diff --git a/components/removeFilters.tsx b/components/filters/clear-filters.tsx similarity index 79% rename from components/removeFilters.tsx rename to components/filters/clear-filters.tsx index 3e5783f..3cc2d18 100644 --- a/components/removeFilters.tsx +++ b/components/filters/clear-filters.tsx @@ -5,16 +5,16 @@ import { useRouter } from "next/navigation"; import { Chip } from "@nextui-org/chip"; import { createUrl } from "@/utils/url"; -interface IRemoveFilters { +interface IClearFilters { param?: string; value: string; } -export const RemoveFilters = ({ param, value }: IRemoveFilters) => { +export const ClearFilters = ({ param, value }: IClearFilters) => { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); - const removeSearchParams = () => { + const clearSearchParams = () => { if (!!param) { const optionNameLowerCase = param.toLowerCase(); const optionSearchParams = new URLSearchParams(searchParams.toString()); @@ -27,11 +27,11 @@ export const RemoveFilters = ({ param, value }: IRemoveFilters) => { }; return (
- + {value}
); }; -export default RemoveFilters; +export default ClearFilters; From 52723f288bdec443d8c5cd542d216fe50638aa21 Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Sun, 7 Jan 2024 14:31:26 +0100 Subject: [PATCH 4/8] feat: Filters controlled logic handle from useFilters hook --- components/filters/clear-filters.tsx | 4 +- components/filters/select-filter.tsx | 73 +++++++++++++--------------- components/filters/toolbar.tsx | 47 ++++++++++++++++++ hooks/useFilters.tsx | 42 ++++++++++++++++ types/filters.ts | 6 ++- 5 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 components/filters/toolbar.tsx create mode 100644 hooks/useFilters.tsx diff --git a/components/filters/clear-filters.tsx b/components/filters/clear-filters.tsx index 3cc2d18..eac6f7d 100644 --- a/components/filters/clear-filters.tsx +++ b/components/filters/clear-filters.tsx @@ -8,9 +8,10 @@ import { createUrl } from "@/utils/url"; interface IClearFilters { param?: string; value: string; + onClear: () => void; } -export const ClearFilters = ({ param, value }: IClearFilters) => { +export const ClearFilters = ({ param, value, onClear }: IClearFilters) => { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); @@ -24,6 +25,7 @@ export const ClearFilters = ({ param, value }: IClearFilters) => { } else { router.replace(pathname); } + onClear(); }; return (
diff --git a/components/filters/select-filter.tsx b/components/filters/select-filter.tsx index 67c5a20..58310ca 100644 --- a/components/filters/select-filter.tsx +++ b/components/filters/select-filter.tsx @@ -1,70 +1,63 @@ "use client"; -import React, { useEffect } from "react"; +import React from "react"; import { usePathname, useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; import { Select, SelectItem } from "@nextui-org/select"; -import { FilterItem } from "@/types/filters"; +import { FilterOption } from "@/types/filters"; import { createUrl } from "@/utils/url"; import Emoji from "../emoji"; interface ISelectFilterProps { placeholder: string; - emoji: string; - items: FilterItem[]; - selectedValue: string; + mainEmoji: string; + options: FilterOption[]; + selectedKey?: string; + onSelect: (value: string) => void; } export const SelectFilter = ({ placeholder, - items, - emoji, - selectedValue, + mainEmoji, + options, + selectedKey, + onSelect, }: ISelectFilterProps) => { - const [value, setValue] = React.useState(selectedValue); - const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); - useEffect(() => { - setValue(selectedValue); - }, [selectedValue]); - - useEffect(() => { - if (value === undefined) { - return; - } - const optionNameLowerCase = placeholder.toLowerCase(); - const optionSearchParams = new URLSearchParams(searchParams.toString()); - optionSearchParams.set(optionNameLowerCase, value); - const optionUrl = createUrl(pathname, optionSearchParams); - router.replace(optionUrl, { scroll: false }); - }, [value]); - - const handleSelectionChange = (e: { - target: { value: React.SetStateAction }; - }) => { - setValue(e.target.value); + const handleSelectionChange = (selection: unknown) => { + const selectedValue = Array.from(selection as Iterable)[0]; + onSelect(selectedValue); }; return (