much cleaner sorting logic

This commit is contained in:
Daniel 2023-06-14 08:10:23 +03:30
parent 8d094f320a
commit 6323badbaf
16 changed files with 135 additions and 282 deletions

View File

@ -8,7 +8,7 @@ type Props = {
searchFilter: {
name: boolean;
url: boolean;
title: boolean;
description: boolean;
collection: boolean;
tags: boolean;
};
@ -44,10 +44,13 @@ export default function FilterSearchDropdown({
}
/>
<Checkbox
label="Title"
state={searchFilter.title}
label="Description"
state={searchFilter.description}
onClick={() =>
setSearchFilter({ ...searchFilter, title: !searchFilter.title })
setSearchFilter({
...searchFilter,
description: !searchFilter.description,
})
}
/>
<Checkbox

View File

@ -111,7 +111,7 @@ export default function LinkCard({ link, count, className }: Props) {
</p>
</div>
<p className="text-gray-500 text-sm font-medium line-clamp-3 w-4/5">
{link.title}
{link.description}
</p>
<div className="flex gap-3 items-center flex-wrap my-3">
<Link href={`/collections/${link.collection.id}`}>

View File

@ -36,7 +36,7 @@ export default function EditLink({
: {
name: "",
url: "",
title: "",
description: "",
tags: [],
collection: {
name: "",
@ -106,7 +106,7 @@ export default function EditLink({
{method === "UPDATE" ? (
<p className="text-gray-500">
<b>{shortendURL}</b> | {link.title}
<b>{shortendURL}</b> | {link.description}
</p>
) : null}

View File

@ -55,7 +55,9 @@ export default function LinkCard({ link, count }: Props) {
<p className="text-lg text-sky-500 font-bold">{link.name}</p>
</div>
<p className="text-gray-500 text-sm font-medium">{link.title}</p>
<p className="text-gray-500 text-sm font-medium">
{link.description}
</p>
<div className="flex gap-2 items-center flex-wrap mt-2">
<p className="text-gray-500">{formattedDate}</p>

View File

@ -1,18 +1,19 @@
import React, { ChangeEvent } from "react";
import React, { Dispatch, SetStateAction } from "react";
import ClickAwayHandler from "./ClickAwayHandler";
import RadioButton from "./RadioButton";
import { Sort } from "@/types/global";
type Props = {
handleSortChange: (e: Sort) => void;
sortBy: Sort;
setSort: Dispatch<SetStateAction<Sort>>;
toggleSortDropdown: Function;
};
export default function SortLinkDropdown({
handleSortChange,
export default function SortDropdown({
sortBy,
toggleSortDropdown,
setSort,
}: Props) {
return (
<ClickAwayHandler
@ -27,37 +28,37 @@ export default function SortLinkDropdown({
<RadioButton
label="Name (A-Z)"
state={sortBy === Sort.NameAZ}
onClick={() => handleSortChange(Sort.NameAZ)}
onClick={() => setSort(Sort.NameAZ)}
/>
<RadioButton
label="Name (Z-A)"
state={sortBy === Sort.NameZA}
onClick={() => handleSortChange(Sort.NameZA)}
onClick={() => setSort(Sort.NameZA)}
/>
<RadioButton
label="Title (A-Z)"
state={sortBy === Sort.TitleAZ}
onClick={() => handleSortChange(Sort.TitleAZ)}
label="Description (A-Z)"
state={sortBy === Sort.DescriptionAZ}
onClick={() => setSort(Sort.DescriptionAZ)}
/>
<RadioButton
label="Title (Z-A)"
state={sortBy === Sort.TitleZA}
onClick={() => handleSortChange(Sort.TitleZA)}
label="Description (Z-A)"
state={sortBy === Sort.DescriptionZA}
onClick={() => setSort(Sort.DescriptionZA)}
/>
<RadioButton
label="Date (Newest First)"
state={sortBy === Sort.DateNewestFirst}
onClick={() => handleSortChange(Sort.DateNewestFirst)}
onClick={() => setSort(Sort.DateNewestFirst)}
/>
<RadioButton
label="Date (Oldest First)"
state={sortBy === Sort.DateOldestFirst}
onClick={() => handleSortChange(Sort.DateOldestFirst)}
onClick={() => setSort(Sort.DateOldestFirst)}
/>
</div>
</ClickAwayHandler>

52
hooks/useSort.tsx Normal file
View File

@ -0,0 +1,52 @@
import {
CollectionIncludingMembers,
LinkIncludingCollectionAndTags,
Sort,
} from "@/types/global";
import { SetStateAction, useEffect } from "react";
type Props<
T extends CollectionIncludingMembers | LinkIncludingCollectionAndTags
> = {
sortBy: Sort;
data: T[];
setData: (value: SetStateAction<T[]>) => void;
};
export default function useSort<
T extends CollectionIncludingMembers | LinkIncludingCollectionAndTags
>({ sortBy, data, setData }: Props<T>) {
useEffect(() => {
const dataArray = [...data];
if (sortBy === Sort.NameAZ)
setData(dataArray.sort((a, b) => a.name.localeCompare(b.name)));
else if (sortBy === Sort.DescriptionAZ)
setData(
dataArray.sort((a, b) => a.description.localeCompare(b.description))
);
else if (sortBy === Sort.NameZA)
setData(dataArray.sort((a, b) => b.name.localeCompare(a.name)));
else if (sortBy === Sort.DescriptionZA)
setData(
dataArray.sort((a, b) => b.description.localeCompare(a.description))
);
else if (sortBy === Sort.DateNewestFirst)
setData(
dataArray.sort(
(a, b) =>
new Date(b.createdAt as string).getTime() -
new Date(a.createdAt as string).getTime()
)
);
else if (sortBy === Sort.DateOldestFirst)
setData(
dataArray.sort(
(a, b) =>
new Date(a.createdAt as string).getTime() -
new Date(b.createdAt as string).getTime()
)
);
}, [sortBy, data]);
}

View File

@ -72,7 +72,7 @@ export default async function postLink(
},
})),
},
title,
description: title,
},
include: { tags: true, collection: true },
});

View File

@ -14,7 +14,7 @@ export default async function getCollection(collectionId: number) {
id: true,
name: true,
url: true,
title: true,
description: true,
collectionId: true,
createdAt: true,
},

View File

@ -14,8 +14,9 @@ import { useEffect, useState } from "react";
import MainLayout from "@/layouts/MainLayout";
import { useSession } from "next-auth/react";
import ProfilePhoto from "@/components/ProfilePhoto";
import SortLinkDropdown from "@/components/SortLinkDropdown";
import SortDropdown from "@/components/SortDropdown";
import useModalStore from "@/store/modals";
import useSort from "@/hooks/useSort";
export default function Index() {
const { setModal } = useModalStore();
@ -36,46 +37,13 @@ export default function Index() {
const [sortedLinks, setSortedLinks] = useState(links);
const handleSortChange = (e: Sort) => {
setSortBy(e);
};
useSort({ sortBy, setData: setSortedLinks, data: links });
useEffect(() => {
setActiveCollection(
collections.find((e) => e.id === Number(router.query.id))
);
// Sorting logic
const linksArray = [
...links.filter((e) => e.collection.id === Number(router.query.id)),
];
if (sortBy === Sort.NameAZ)
setSortedLinks(linksArray.sort((a, b) => a.name.localeCompare(b.name)));
else if (sortBy === Sort.TitleAZ)
setSortedLinks(linksArray.sort((a, b) => a.title.localeCompare(b.title)));
else if (sortBy === Sort.NameZA)
setSortedLinks(linksArray.sort((a, b) => b.name.localeCompare(a.name)));
else if (sortBy === Sort.TitleZA)
setSortedLinks(linksArray.sort((a, b) => b.title.localeCompare(a.title)));
else if (sortBy === Sort.DateNewestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(b.createdAt as string).getTime() -
new Date(a.createdAt as string).getTime()
)
);
else if (sortBy === Sort.DateOldestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(a.createdAt as string).getTime() -
new Date(b.createdAt as string).getTime()
)
);
}, [links, router, collections, sortBy]);
}, [router, collections]);
return (
<MainLayout>
@ -166,9 +134,9 @@ export default function Index() {
</div>
{sortDropdown ? (
<SortLinkDropdown
handleSortChange={handleSortChange}
<SortDropdown
sortBy={sortBy}
setSort={setSortBy}
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
/>
) : null}

View File

@ -8,68 +8,26 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import CollectionCard from "@/components/CollectionCard";
import Dropdown from "@/components/Dropdown";
import { ChangeEvent, useEffect, useState } from "react";
import { useState } from "react";
import MainLayout from "@/layouts/MainLayout";
import ClickAwayHandler from "@/components/ClickAwayHandler";
import RadioButton from "@/components/RadioButton";
import { useSession } from "next-auth/react";
import useModalStore from "@/store/modals";
import SortDropdown from "@/components/SortDropdown";
import { Sort } from "@/types/global";
import useSort from "@/hooks/useSort";
export default function Collections() {
const { collections } = useCollectionStore();
const [expandDropdown, setExpandDropdown] = useState(false);
const [sortDropdown, setSortDropdown] = useState(false);
const [sortBy, setSortBy] = useState("Name (A-Z)");
const [sortBy, setSortBy] = useState<Sort>(Sort.NameAZ);
const [sortedCollections, setSortedCollections] = useState(collections);
const session = useSession();
const { setModal } = useModalStore();
const handleSortChange = (event: ChangeEvent<HTMLInputElement>) => {
setSortBy(event.target.value);
};
useEffect(() => {
const collectionsArray = [...collections];
if (sortBy === "Name (A-Z)")
setSortedCollections(
collectionsArray.sort((a, b) => a.name.localeCompare(b.name))
);
else if (sortBy === "Description (A-Z)")
setSortedCollections(
collectionsArray.sort((a, b) =>
a.description.localeCompare(b.description)
)
);
else if (sortBy === "Name (Z-A)")
setSortedCollections(
collectionsArray.sort((a, b) => b.name.localeCompare(a.name))
);
else if (sortBy === "Description (Z-A)")
setSortedCollections(
collectionsArray.sort((a, b) =>
b.description.localeCompare(a.description)
)
);
else if (sortBy === "Date (Newest First)")
setSortedCollections(
collectionsArray.sort(
(a, b) =>
new Date(b.createdAt as string).getTime() -
new Date(a.createdAt as string).getTime()
)
);
else if (sortBy === "Date (Oldest First)")
setSortedCollections(
collectionsArray.sort(
(a, b) =>
new Date(a.createdAt as string).getTime() -
new Date(b.createdAt as string).getTime()
)
);
}, [collections, sortBy]);
useSort({ sortBy, setData: setSortedCollections, data: collections });
return (
<MainLayout>
@ -146,54 +104,11 @@ export default function Collections() {
</div>
{sortDropdown ? (
<ClickAwayHandler
onClickOutside={(e: Event) => {
const target = e.target as HTMLInputElement;
if (target.id !== "sort-dropdown") setSortDropdown(false);
}}
className="absolute top-8 right-0 shadow-md bg-gray-50 rounded-md p-2 z-10 border border-sky-100 w-48"
>
<p className="mb-2 text-sky-900 text-center font-semibold">
Sort by
</p>
<div className="flex flex-col gap-2">
<RadioButton
label="Name (A-Z)"
state={sortBy === "Name (A-Z)"}
onClick={handleSortChange}
<SortDropdown
sortBy={sortBy}
setSort={setSortBy}
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
/>
<RadioButton
label="Name (Z-A)"
state={sortBy === "Name (Z-A)"}
onClick={handleSortChange}
/>
<RadioButton
label="Description (A-Z)"
state={sortBy === "Description (A-Z)"}
onClick={handleSortChange}
/>
<RadioButton
label="Description (Z-A)"
state={sortBy === "Description (Z-A)"}
onClick={handleSortChange}
/>
<RadioButton
label="Date (Newest First)"
state={sortBy === "Date (Newest First)"}
onClick={handleSortChange}
/>
<RadioButton
label="Date (Oldest First)"
state={sortBy === "Date (Oldest First)"}
onClick={handleSortChange}
/>
</div>
</ClickAwayHandler>
) : null}
</div>
</div>

View File

@ -1,11 +1,12 @@
import LinkCard from "@/components/LinkCard";
import SortLinkDropdown from "@/components/SortLinkDropdown";
import SortDropdown from "@/components/SortDropdown";
import useSort from "@/hooks/useSort";
import MainLayout from "@/layouts/MainLayout";
import useLinkStore from "@/store/links";
import { Sort } from "@/types/global";
import { faLink, faSort } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ChangeEvent, useEffect, useState } from "react";
import { useState } from "react";
export default function Links() {
const { links } = useLinkStore();
@ -14,38 +15,7 @@ export default function Links() {
const [sortBy, setSortBy] = useState<Sort>(Sort.NameAZ);
const [sortedLinks, setSortedLinks] = useState(links);
const handleSortChange = (e: Sort) => {
setSortBy(e);
};
useEffect(() => {
const linksArray = [...links];
if (sortBy === Sort.NameAZ)
setSortedLinks(linksArray.sort((a, b) => a.name.localeCompare(b.name)));
else if (sortBy === Sort.TitleAZ)
setSortedLinks(linksArray.sort((a, b) => a.title.localeCompare(b.title)));
else if (sortBy === Sort.NameZA)
setSortedLinks(linksArray.sort((a, b) => b.name.localeCompare(a.name)));
else if (sortBy === Sort.TitleZA)
setSortedLinks(linksArray.sort((a, b) => b.title.localeCompare(a.title)));
else if (sortBy === Sort.DateNewestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(b.createdAt as string).getTime() -
new Date(a.createdAt as string).getTime()
)
);
else if (sortBy === Sort.DateOldestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(a.createdAt as string).getTime() -
new Date(b.createdAt as string).getTime()
)
);
}, [links, sortBy]);
useSort({ sortBy, setData: setSortedLinks, data: links });
return (
<MainLayout>
@ -75,9 +45,9 @@ export default function Links() {
</div>
{sortDropdown ? (
<SortLinkDropdown
handleSortChange={handleSortChange}
<SortDropdown
sortBy={sortBy}
setSort={setSortBy}
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
/>
) : null}

View File

@ -1,18 +1,19 @@
import FilterSearchDropdown from "@/components/FilterSearchDropdown";
import LinkCard from "@/components/LinkCard";
import SortLinkDropdown from "@/components/SortLinkDropdown";
import SortDropdown from "@/components/SortDropdown";
import useSort from "@/hooks/useSort";
import MainLayout from "@/layouts/MainLayout";
import useLinkStore from "@/store/links";
import { Sort } from "@/types/global";
import { faFilter, faSearch, faSort } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { ChangeEvent, useEffect, useState } from "react";
import { useEffect, useState } from "react";
type SearchFilter = {
name: boolean;
url: boolean;
title: boolean;
description: boolean;
collection: boolean;
tags: boolean;
};
@ -29,7 +30,7 @@ export default function Links() {
const [searchFilter, setSearchFilter] = useState<SearchFilter>({
name: true,
url: true,
title: true,
description: true,
collection: true,
tags: true,
});
@ -37,20 +38,20 @@ export default function Links() {
const [filterDropdown, setFilterDropdown] = useState(false);
const [sortDropdown, setSortDropdown] = useState(false);
const [sortBy, setSortBy] = useState<Sort>(Sort.NameAZ);
const [sortedLinks, setSortedLinks] = useState(links);
const handleSortChange = (e: Sort) => {
setSortBy(e);
};
const [filteredLinks, setFilteredLinks] = useState(links);
const [sortedLinks, setSortedLinks] = useState(filteredLinks);
useSort({ sortBy, setData: setSortedLinks, data: links });
useEffect(() => {
const linksArray = [
...links.filter((link) => {
setFilteredLinks([
...sortedLinks.filter((link) => {
if (
(searchFilter.name && link.name.toLowerCase().includes(routeQuery)) ||
(searchFilter.url && link.url.toLowerCase().includes(routeQuery)) ||
(searchFilter.title &&
link.title.toLowerCase().includes(routeQuery)) ||
(searchFilter.description &&
link.description.toLowerCase().includes(routeQuery)) ||
(searchFilter.collection &&
link.collection.name.toLowerCase().includes(routeQuery)) ||
(searchFilter.tags &&
@ -60,33 +61,8 @@ export default function Links() {
)
return true;
}),
];
if (sortBy === Sort.NameAZ)
setSortedLinks(linksArray.sort((a, b) => a.name.localeCompare(b.name)));
else if (sortBy === Sort.TitleAZ)
setSortedLinks(linksArray.sort((a, b) => a.title.localeCompare(b.title)));
else if (sortBy === Sort.NameZA)
setSortedLinks(linksArray.sort((a, b) => b.name.localeCompare(a.name)));
else if (sortBy === Sort.TitleZA)
setSortedLinks(linksArray.sort((a, b) => b.title.localeCompare(a.title)));
else if (sortBy === Sort.DateNewestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(b.createdAt as string).getTime() -
new Date(a.createdAt as string).getTime()
)
);
else if (sortBy === Sort.DateOldestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(a.createdAt as string).getTime() -
new Date(b.createdAt as string).getTime()
)
);
}, [links, searchFilter, sortBy, router]);
]);
}, [searchFilter, sortedLinks, router]);
return (
<MainLayout>
@ -141,17 +117,17 @@ export default function Links() {
</div>
{sortDropdown ? (
<SortLinkDropdown
handleSortChange={handleSortChange}
<SortDropdown
sortBy={sortBy}
setSort={setSortBy}
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
/>
) : null}
</div>
</div>
</div>
{sortedLinks[0] ? (
sortedLinks.map((e, i) => {
{filteredLinks[0] ? (
filteredLinks.map((e, i) => {
return <LinkCard key={i} link={e} count={i} />;
})
) : (

View File

@ -3,12 +3,13 @@ import useLinkStore from "@/store/links";
import { faHashtag, faSort } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { ChangeEvent, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import MainLayout from "@/layouts/MainLayout";
import { Tag } from "@prisma/client";
import useTagStore from "@/store/tags";
import SortLinkDropdown from "@/components/SortLinkDropdown";
import SortDropdown from "@/components/SortDropdown";
import { Sort } from "@/types/global";
import useSort from "@/hooks/useSort";
export default function Index() {
const router = useRouter();
@ -23,46 +24,11 @@ export default function Index() {
const [sortedLinks, setSortedLinks] = useState(links);
const handleSortChange = (e: Sort) => {
setSortBy(e);
};
useSort({ sortBy, setData: setSortedLinks, data: links });
useEffect(() => {
setActiveTag(tags.find((e) => e.id === Number(router.query.id)));
// Sorting logic
const linksArray = [
...links.filter((e) =>
e.tags.some((e) => e.id === Number(router.query.id))
),
];
if (sortBy === Sort.NameAZ)
setSortedLinks(linksArray.sort((a, b) => a.name.localeCompare(b.name)));
else if (sortBy === Sort.TitleAZ)
setSortedLinks(linksArray.sort((a, b) => a.title.localeCompare(b.title)));
else if (sortBy === Sort.NameZA)
setSortedLinks(linksArray.sort((a, b) => b.name.localeCompare(a.name)));
else if (sortBy === Sort.TitleZA)
setSortedLinks(linksArray.sort((a, b) => b.title.localeCompare(a.title)));
else if (sortBy === Sort.DateNewestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(b.createdAt as string).getTime() -
new Date(a.createdAt as string).getTime()
)
);
else if (sortBy === Sort.DateOldestFirst)
setSortedLinks(
linksArray.sort(
(a, b) =>
new Date(a.createdAt as string).getTime() -
new Date(b.createdAt as string).getTime()
)
);
}, [links, router, tags, sortBy]);
}, [router, tags]);
return (
<MainLayout>
@ -94,9 +60,9 @@ export default function Index() {
</div>
{sortDropdown ? (
<SortLinkDropdown
handleSortChange={handleSortChange}
<SortDropdown
sortBy={sortBy}
setSort={setSortBy}
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
/>
) : null}

View File

@ -40,7 +40,7 @@ CREATE TABLE "Link" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL DEFAULT '',
"collectionId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@ -58,7 +58,7 @@ model Link {
id Int @id @default(autoincrement())
name String
url String
title String
description String @default("")
pinnedBy User[]

View File

@ -46,8 +46,8 @@ export interface PublicCollectionIncludingLinks
export enum Sort {
NameAZ,
NameZA,
TitleAZ,
TitleZA,
DescriptionAZ,
DescriptionZA,
DateNewestFirst,
DateOldestFirst,
}