From 48c44e3723d5cfd21171340006f61adbb8b70a69 Mon Sep 17 00:00:00 2001 From: corgifist Date: Sun, 27 Jul 2025 01:20:23 +0300 Subject: [PATCH] ui: implemented project sorting --- src/app/page.tsx | 173 ++++++++++++++++++++++--- src/components/dashboard/Dashboard.tsx | 4 +- src/components/search/Search.tsx | 9 +- src/hooks/use-mobile.ts | 1 - 4 files changed, 161 insertions(+), 26 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 5f20a77..a8ac89f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,7 @@ import { useLiveQuery } from "dexie-react-hooks"; import { addProject, db, deleteProject } from "@/lib/db"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { CopyIcon, EditIcon, EllipsisIcon, Grid2X2CheckIcon, InfoIcon, ListCheckIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react"; +import { ALargeSmallIcon, ArrowDownAZIcon, ArrowDownIcon, ArrowUpAZIcon, ArrowUpIcon, CalendarArrowDownIcon, CalendarArrowUpIcon, CalendarIcon, ClockArrowDownIcon, ClockArrowUpIcon, ClockIcon, CopyIcon, EditIcon, EllipsisIcon, Grid2X2CheckIcon, InfoIcon, ListCheckIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react"; import { Toggle } from "@/components/ui/toggle"; import Search from "@/components/search"; import { useIsMobile } from "@/hooks/use-mobile"; @@ -20,11 +20,64 @@ import { zodResolver } from "@hookform/resolvers/zod" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; import { generateUUID } from "@/lib/uuid"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { useDebounce } from "use-debounce"; import { Checkbox } from "@/components/ui/checkbox"; +import { cn } from "@/lib/utils"; +import { Tooltip, TooltipContent } from "@/components/ui/tooltip"; +import { TooltipTrigger } from "@radix-ui/react-tooltip"; +import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import { Separator } from "@/components/ui/separator"; + +type SortingType = "byCreationDate" + | "byEditDate" + | "byTitle"; + +const defaultSortingType: SortingType = "byCreationDate"; + +const sortProjects = (a: Project, b: Project, sortingType: SortingType) => { + switch (sortingType) { + case "byCreationDate": return b.creationDate - a.creationDate; + case "byEditDate": return b.editDate - a.editDate; + case "byTitle": return a.title.localeCompare(b.title); + } +}; + +const sortingTypeToString = (sortingType: SortingType) => { + switch (sortingType) { + case "byCreationDate": return "By Creation Date"; + case "byEditDate": return "By Edit Date"; + case "byTitle": return "By Title"; + } +}; + +const SortingTypeIcon = ({ + sortingType +}: { + sortingType: SortingType +}) => { + switch (sortingType) { + case "byCreationDate": return ; + case "byEditDate": return ; + case "byTitle": return ; + } +}; + +const SortingTypeMenuItem = ({ + sortingType, + currentSortingType, + setSortingType +}: { + sortingType: SortingType, + currentSortingType: SortingType + setSortingType: Dispatch>, +}) => ( + setSortingType(sortingType)}> + {sortingTypeToString(sortingType)} + +); interface SelectContextData { selecting: boolean; @@ -132,15 +185,15 @@ const DeleteProjectDialog = ({ project }: { project: Project }) => { ); } -const ProjectDropdown = ({ +const ProjectDropdown = ({ project, selected, setSelected -}: { +}: { project: Project, selected: boolean, setSelected: Dispatch> - }): ReactNode => { +}): ReactNode => { const { selecting, setSelecting } = useSelectContext(); const [renameDialogOpen, setRenameDialogOpen] = useState(false); const [deleteAlertOpen, setDeleteAlertOpen] = useState(false); @@ -175,6 +228,52 @@ const ProjectDropdown = ({ setSelected(!selected); }; + if (isMobile) { + return ( + + + + + + +
+ {project.title} + +
+ Manage {project.title} +
+ + + + + + + +
+ + + + + + +
+ ) + } + return ( @@ -183,7 +282,7 @@ const ProjectDropdown = ({ -
+
{project.title} setRenameDialogOpen(true)}> Rename @@ -192,7 +291,7 @@ const ProjectDropdown = ({ - Select + {selected ? "Deselect" : "Select"} console.log("Edit Project")}> Edit @@ -291,7 +390,7 @@ const ProjectContainer = ({
{selecting && (
- setSelected(checked as boolean)}/> + setSelected(checked as boolean)} />
)} @@ -302,13 +401,20 @@ const ProjectContainer = ({ export default function Home(): ReactNode { const isMobile = useIsMobile(); const [search, setSearch] = useState(''); + const [debouncedSearch] = useDebounce(search, 300); const [selecting, setSelecting] = useState(false); const [selectedProjects, setSelectedProjects] = useState([]); - const [debouncedSearch] = useDebounce(search, 300); + const [sortingType, setSortingType] = useState(defaultSortingType); + const [descendingSort, setDescendingSort] = useState(false); const projects = useLiveQuery(() => ( - db.projects.filter((project) => project.title.toLowerCase().includes(debouncedSearch.toLowerCase())).toArray() - ), [debouncedSearch]); + db.projects.toArray() + )); + + const filteredProjects = projects && ( + projects.filter((project) => project.title.toLowerCase().includes(debouncedSearch.toLowerCase())) + .sort((a, b) => sortProjects(a, b, sortingType) * (descendingSort ? -1 : 1)) + ); const newProjectForm = useForm>({ resolver: zodResolver(ProjectInfoFormSchema), @@ -345,8 +451,8 @@ export default function Home(): ReactNode {

Project Library

{projects && }
-
-
+
+
+ setSearch(e.target.value)} className={isMobile ? "w-full" : "w-60"} />
-
- setSelecting(pressed)}> - {!isMobile && "Select Projects"} +
+
+ + + + + + Sort Projects + + + + + + + + + + setDescendingSort(pressed)}> + {descendingSort ? : } + + + + {descendingSort ? "Descending" : "Ascending"} + + +
+ setSelecting(pressed)}> + {isMobile ? "Select" : "Select Projects"} - setSearch(e.target.value)} />
-
- {projects && projects.map((project) => )} +
+ {filteredProjects && filteredProjects.map((project) => )}
{(projects != undefined && projects.length == 0) && (
diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index fd1e3a6..1e59f39 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -92,7 +92,7 @@ export const Dashboard = (): ReactNode => {
- + ClipFusion GitHub @@ -102,7 +102,7 @@ export const Dashboard = (): ReactNode => { - + ClipFusion Git Mirror diff --git a/src/components/search/Search.tsx b/src/components/search/Search.tsx index 1b0dae5..4580211 100644 --- a/src/components/search/Search.tsx +++ b/src/components/search/Search.tsx @@ -1,14 +1,15 @@ +"use client"; import { SearchIcon } from "lucide-react"; import { Input } from "@/components/ui/input"; import { ComponentProps, ReactNode } from "react"; +import { cn } from "@/lib/utils"; export const Search = (props: ComponentProps): ReactNode => ( -
+
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts index 39849ca..0a414a4 100644 --- a/src/hooks/use-mobile.ts +++ b/src/hooks/use-mobile.ts @@ -8,7 +8,6 @@ export function useIsMobile() { React.useEffect(() => { const onResize = () => { setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - console.log("resize"); }; window.addEventListener("resize", onResize) setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)