diff --git a/app/@types/index.d.ts b/app/@types/index.d.ts index 2cc70c0..efe6255 100644 --- a/app/@types/index.d.ts +++ b/app/@types/index.d.ts @@ -7,36 +7,20 @@ interface NavItemProps { children?: NavItemProps[]; } +export interface TContributors { + id: string; + firstName: string; + lastName: string; + email: string; + gender: string; + dependent: boolean; + contributionAmount: number; + contributiionMethod: "monthly" | "annual"; + dateJoined: Date; +} + interface ContributorsDetailsProps { title: string; value: string; color: string; } - -export interface SearchParams { - [key: string]: string | string[] | undefined; -} - -export interface Option { - label: string; - value: string; - icon?: React.ComponentType<{ className?: string }>; - withCount?: boolean; -} - -export interface DataTableFilterField { - label: string; - value: keyof TData; - placeholder?: string; - options?: Option[]; -} - -export interface DataTableFilterOption { - id: string; - label: string; - value: keyof TData; - options: Option[]; - filterValues?: string[]; - filterOperator?: string; - isMulti?: boolean; -} diff --git a/app/components/atoms/collapsible-item.tsx b/app/components/atoms/collapsible-item.tsx index 15c232a..2c82f6f 100644 --- a/app/components/atoms/collapsible-item.tsx +++ b/app/components/atoms/collapsible-item.tsx @@ -6,33 +6,73 @@ import { import { NavLink } from "@remix-run/react"; import { ChevronsUpDown, Pencil } from "lucide-react"; import type { LucideIcon } from "lucide-react"; -import type { ReactNode } from "react"; +import type { ReactNode } from "react"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { buttonVariants } from "../ui/button"; +import { cn } from "@/lib/styles"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; interface CollapsibleItemProps { label: string; icon: LucideIcon; children: ReactNode[]; + withMinimalSidebar: boolean | null; } export const CollapsibleItem = ({ label, icon: Icon, children, + withMinimalSidebar, }: CollapsibleItemProps) => { - return ( + return !withMinimalSidebar ? ( - - + + {label}
{children && - children?.map((child, index) => ( + children.map((child, index) => ( {child} ))}
+ ) : ( + + + + + + + + +

{label}

+
+
+
+
+ + {children && + children.map((child, index) => ( +
+
+ {child} +
+
+ ))} +
+
); }; @@ -41,6 +81,7 @@ interface CollapsibleChildProps { href: string; } +// {TODO: handle active state (glen)} export const CollapsibleChild: React.FC = ({ childLabel, href, diff --git a/app/components/atoms/contributors/table-render.tsx b/app/components/atoms/contributors/table-render.tsx deleted file mode 100644 index 92fcce6..0000000 --- a/app/components/atoms/contributors/table-render.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from "react"; -// import { getTasks } from "./_lib/queries" -// import { searchParamsSchema } from "./_lib/validations" -import { TasksTableProvider } from "./table/task-table-provider"; -import { Skeleton } from "@/components/ui/skeleton"; -import { DateRangePicker } from "@/components/blocks/date-range-picker"; -import { DataTableSkeleton } from "./table/data-table-skeleton"; -import { TasksTable } from "./table/task-table"; -import { Shell } from "@/components/shell"; -import { SearchParams } from "@/@types"; -import { searchParamsSchema } from "@/lib/validations"; -import { getTasks } from "@/lib/queries"; - -export interface IndexPageProps { - searchParams: SearchParams; -} - -export const TableRender = ({ searchParams }: IndexPageProps) => { - const search = searchParamsSchema.parse(searchParams); - const tasksPromise = getTasks(search); - return ( - - {/** - * The `TasksTableProvider` is use to enable some feature flags for the `TasksTable` component. - * Feel free to remove this, as it's not required for the `TasksTable` component to work. - */} - - {/** - * The `DateRangePicker` component is used to render the date range picker UI. - * It is used to filter the tasks based on the selected date range it was created at. - * The business logic for filtering the tasks based on the selected date range is handled inside the component. - */} - }> - - - - } - > - {/** - * Passing promises and consuming them using React.use for triggering the suspense fallback. - * @see https://react.dev/reference/react/use - */} - - - - - ); -}; diff --git a/app/components/atoms/contributors/table/advanced/data-table-advanced-toolbar.tsx b/app/components/atoms/contributors/table/advanced/data-table-advanced-toolbar.tsx deleted file mode 100644 index 956a30f..0000000 --- a/app/components/atoms/contributors/table/advanced/data-table-advanced-toolbar.tsx +++ /dev/null @@ -1,157 +0,0 @@ - -import * as React from "react" -// import { useSearchParams } from "next/navigation" -import { CaretSortIcon, PlusIcon } from "@radix-ui/react-icons" -import type { Table } from "@tanstack/react-table" -import { cn } from "@/lib/styles" -import { Button } from "@/components/ui/button" -import { DataTableFilterItem } from "./data-table-filter-item" -import { DataTableMultiFilter } from "./data-table-multi-filter" -import { DataTableFilterField, DataTableFilterOption } from "@/@types" -import { useSearchParams } from "@remix-run/react" -import { DataTableFilterCombobox } from "./data-table-filter-combobox" -import { DataTableViewOptions } from "../data-table-view-options" - -interface DataTableAdvancedToolbarProps - extends React.HTMLAttributes { - table: Table - filterFields?: DataTableFilterField[] -} - -export function DataTableAdvancedToolbar({ - table, - filterFields = [], - children, - className, - ...props -}: DataTableAdvancedToolbarProps) { - const [searchParams, setSearchParams] = useSearchParams(); - - const options = React.useMemo[]>(() => { - return filterFields.map((field) => { - return { - id: crypto.randomUUID(), - label: field.label, - value: field.value, - options: field.options ?? [], - } - }) - }, [filterFields]) - - const initialSelectedOptions = React.useMemo(() => { - return options - .filter((option) => searchParams.has(option.value as string)) - .map((option) => { - const value = searchParams.get(String(option.value)) as string - const [filterValue, filterOperator] = - value?.split("~").filter(Boolean) ?? [] - - return { - ...option, - filterValues: filterValue?.split(".") ?? [], - filterOperator, - } - }) - }, [options, searchParams]) - - const [selectedOptions, setSelectedOptions] = React.useState< - DataTableFilterOption[] - >(initialSelectedOptions) - const [openFilterBuilder, setOpenFilterBuilder] = React.useState( - initialSelectedOptions.length > 0 || false - ) - const [openCombobox, setOpenCombobox] = React.useState(false) - - function onFilterComboboxItemSelect() { - setOpenFilterBuilder(true) - setOpenCombobox(true) - } - - return ( -
-
- {children} - {(options.length > 0 && selectedOptions.length > 0) || - openFilterBuilder ? ( - - ) : ( - - !selectedOptions.some( - (selectedOption) => selectedOption.value === option.value - ) - )} - selectedOptions={selectedOptions} - setSelectedOptions={setSelectedOptions} - onSelect={onFilterComboboxItemSelect} - /> - )} - -
-
- {selectedOptions - .filter((option) => !option.isMulti) - .map((selectedOption) => ( - - ))} - {selectedOptions.some((option) => option.isMulti) ? ( - option.isMulti)} - setSelectedOptions={setSelectedOptions} - defaultOpen={openCombobox} - /> - ) : null} - {options.length > 0 && options.length > selectedOptions.length ? ( - - - - ) : null} -
-
- ) -} \ No newline at end of file diff --git a/app/components/atoms/contributors/table/advanced/data-table-faceted-filter.tsx b/app/components/atoms/contributors/table/advanced/data-table-faceted-filter.tsx deleted file mode 100644 index d9ee528..0000000 --- a/app/components/atoms/contributors/table/advanced/data-table-faceted-filter.tsx +++ /dev/null @@ -1,129 +0,0 @@ - -import { CheckIcon } from "@radix-ui/react-icons" -import type { Column } from "@tanstack/react-table" -import { Option } from "@/@types" -import { cn } from "@/lib/styles" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, -} from "@/components/ui/command" -import { DataTableFilterOption } from "@/@types" - -interface DataTableAdvancedFacetedFilterProps { - column?: Column - title?: string - options: Option[] - selectedValues: Set - setSelectedOptions: React.Dispatch< - React.SetStateAction[]> - > -} - -export function DataTableAdvancedFacetedFilter({ - column, - title, - options, - selectedValues, - setSelectedOptions, -}: DataTableAdvancedFacetedFilterProps) { - return ( - -
- -
- - No results found. - - {options.map((option) => { - const isSelected = selectedValues.has(option.value) - - return ( - { - if (isSelected) { - selectedValues.delete(option.value) - } else { - selectedValues.add(option.value) - } - const filterValues = Array.from(selectedValues) - column?.setFilterValue( - filterValues.length ? filterValues : undefined - ) - setSelectedOptions((prev) => - prev.map((item) => - item.value === column?.id - ? { - ...item, - filterValues, - } - : item - ) - ) - }} - > -
-
- {option.icon && ( -
- ) - })} -
- {selectedValues.size > 0 && ( - <> - - - { - column?.setFilterValue(undefined) - setSelectedOptions((prev) => - prev.map((item) => - item.value === column?.id - ? { - ...item, - filterValues: [], - } - : item - ) - ) - }} - className="justify-center text-center" - > - Clear filters - - - - )} -
-
- ) -} \ No newline at end of file diff --git a/app/components/atoms/contributors/table/advanced/data-table-filter-combobox.tsx b/app/components/atoms/contributors/table/advanced/data-table-filter-combobox.tsx deleted file mode 100644 index 94c99a4..0000000 --- a/app/components/atoms/contributors/table/advanced/data-table-filter-combobox.tsx +++ /dev/null @@ -1,136 +0,0 @@ -"use client"; - -import * as React from "react"; -import type { DataTableFilterOption } from "@/@types"; -import { - CaretSortIcon, - ChevronDownIcon, - PlusIcon, - TextIcon, -} from "@radix-ui/react-icons"; - -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; - -interface DataTableFilterComboboxProps { - options: DataTableFilterOption[]; - selectedOptions: DataTableFilterOption[]; - setSelectedOptions: React.Dispatch< - React.SetStateAction[]> - >; - onSelect: () => void; - children?: React.ReactNode; -} - -export function DataTableFilterCombobox({ - options, - selectedOptions, - setSelectedOptions, - onSelect, - children, -}: DataTableFilterComboboxProps) { - const [value, setValue] = React.useState(""); - const [open, setOpen] = React.useState(false); - const [selectedOption, setSelectedOption] = React.useState< - DataTableFilterOption - >(options[0] ?? ({} as DataTableFilterOption)); - - return ( - - - {children ?? ( - - )} - - - - - - No item found. - - {options - .filter( - (option) => - !selectedOptions.some( - (selectedOption) => selectedOption.value === option.value, - ), - ) - .map((option) => ( - { - setValue(currentValue === value ? "" : currentValue); - setOpen(false); - setSelectedOption(option); - setSelectedOptions((prev) => { - return [...prev, { ...option }]; - }); - onSelect(); - }} - > - {option.options.length > 0 ? ( - - ))} - - - - { - setOpen(false); - setSelectedOptions([ - ...selectedOptions, - { - id: crypto.randomUUID(), - label: selectedOption?.label ?? "", - value: selectedOption?.value ?? "", - options: selectedOption?.options ?? [], - isMulti: true, - }, - ]); - onSelect(); - }} - > - - - - - - - ); -} diff --git a/app/components/atoms/contributors/table/advanced/data-table-filter-item.tsx b/app/components/atoms/contributors/table/advanced/data-table-filter-item.tsx deleted file mode 100644 index 6b401e9..0000000 --- a/app/components/atoms/contributors/table/advanced/data-table-filter-item.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import * as React from "react"; - -import type { DataTableFilterOption } from "@/@types"; -import { TrashIcon } from "@radix-ui/react-icons"; -import type { Table } from "@tanstack/react-table"; - -import { dataTableConfig } from "@/config/data-table"; -import { cn } from "@/lib/styles"; -import { useDebounce } from "@/hooks/use-debounce"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { useSearchParams, useNavigate } from "@remix-run/react"; -import { useLocation } from "react-router-dom"; -import { DataTableAdvancedFacetedFilter } from "./data-table-faceted-filter"; - - -interface DataTableFilterItemProps { - table: Table; - selectedOption: DataTableFilterOption; - selectedOptions: DataTableFilterOption[]; - setSelectedOptions: React.Dispatch< - React.SetStateAction[]> - >; - defaultOpen: boolean; -} - -export function DataTableFilterItem({ - table, - selectedOption, - selectedOptions, - setSelectedOptions, - defaultOpen, -}: DataTableFilterItemProps) { - const navigate = useNavigate(); - const location = useLocation(); - const currentPathName = location.pathname; - const searchParams = useSearchParams(); - - const column = table.getColumn( - selectedOption.value ? String(selectedOption.value) : "", - ); - - const selectedValues = new Set( - selectedOptions.find((item) => item.value === column?.id)?.filterValues, - ); - - const filterValues = Array.from(selectedValues); - const filterOperator = selectedOptions.find( - (item) => item.value === column?.id, - )?.filterOperator; - - const operators = - selectedOption.options.length > 0 - ? dataTableConfig.selectableOperators - : dataTableConfig.comparisonOperators; - - const [value, setValue] = React.useState(filterValues[0] ?? ""); - const debounceValue = useDebounce(value, 500); - const [open, setOpen] = React.useState(defaultOpen); - const [selectedOperator, setSelectedOperator] = React.useState( - operators.find((c) => c.value === filterOperator) ?? operators[0], - ); - - // Create query string - const createQueryString = React.useCallback( - (params: Record) => { - const newSearchParams = new URLSearchParams(searchParams?.toString()); - - for (const [key, value] of Object.entries(params)) { - if (value === null) { - newSearchParams.delete(key); - } else { - newSearchParams.set(key, String(value)); - } - } - - return newSearchParams.toString(); - }, - [searchParams], - ); - - // Update query string - React.useEffect(() => { - if (selectedOption.options.length > 0) { - // key=value1.value2.value3~operator - const newSearchParams = createQueryString({ - [String(selectedOption.value)]: - filterValues.length > 0 - ? `${filterValues.join(".")}~${selectedOperator?.value}` - : null, - }); - navigate(`${currentPathName}?${newSearchParams}`); - } else { - // key=value~operator - const newSearchParams = createQueryString({ - [String(selectedOption.value)]: - debounceValue.length > 0 - ? `${debounceValue}~${selectedOperator?.value}` - : null, - }); - navigate(`${currentPathName}?${newSearchParams}`); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedOption, debounceValue, selectedOperator]); - - return ( - - - - - -
-
-
- {selectedOption.label} -
- -
- -
- {selectedOption.options.length > 0 ? ( - column && ( - - ) - ) : ( - setValue(event.target.value)} - autoFocus - /> - )} -
-
- ); -} diff --git a/app/components/atoms/contributors/table/advanced/data-table-multi-filter.tsx b/app/components/atoms/contributors/table/advanced/data-table-multi-filter.tsx deleted file mode 100644 index 4ab9002..0000000 --- a/app/components/atoms/contributors/table/advanced/data-table-multi-filter.tsx +++ /dev/null @@ -1,366 +0,0 @@ -import * as React from "react" - -import type { DataTableFilterOption } from "@/@types" -import { - CopyIcon, - CursorArrowIcon, - DotsHorizontalIcon, - TextAlignCenterIcon, - TrashIcon, -} from "@radix-ui/react-icons" -import type { Table } from "@tanstack/react-table" - -import { dataTableConfig, type DataTableConfig } from "@/config/data-table" -import { useDebounce } from "@/hooks/use-debounce" -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { Input } from "@/components/ui/input" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -import { Separator } from "@/components/ui/separator" - -import { DataTableFacetedFilter } from "../data-table-faceted-filter" -import { useLocation, useNavigate, useSearchParams } from "@remix-run/react" - -interface DataTableMultiFilterProps { - table: Table - allOptions: DataTableFilterOption[] - options: DataTableFilterOption[] - setSelectedOptions: React.Dispatch< - React.SetStateAction[]> - > - defaultOpen: boolean -} - -export function DataTableMultiFilter({ - table, - allOptions, - options, - setSelectedOptions, - defaultOpen, -}: DataTableMultiFilterProps) { - const [open, setOpen] = React.useState(defaultOpen) - const [operator, setOperator] = React.useState( - dataTableConfig.logicalOperators[0] - ) - - return ( - - - - - -
- {options.map((option, i) => ( - - ))} -
- -
- -
-
-
- ) -} - -interface MultiFilterRowProps { - i: number - table: Table - allOptions: DataTableFilterOption[] - option: DataTableFilterOption - options: DataTableFilterOption[] - setSelectedOptions: React.Dispatch< - React.SetStateAction[]> - > - operator?: DataTableConfig["logicalOperators"][number] - setOperator: React.Dispatch< - React.SetStateAction< - DataTableConfig["logicalOperators"][number] | undefined - > - > -} - -export function MultiFilterRow({ - i, - table, - option, - allOptions, - options, - setSelectedOptions, - operator, - setOperator, -}: MultiFilterRowProps) { - const navigate = useNavigate(); - const location = useLocation(); - const currentPathName = location.pathname; - const searchParams = useSearchParams(); - const [value, setValue] = React.useState("") - const debounceValue = useDebounce(value, 500) - - const [selectedOption, setSelectedOption] = React.useState< - DataTableFilterOption | undefined - >(options[0]) - - const filterVarieties = selectedOption?.options.length - ? ["is", "is not"] - : ["contains", "does not contain", "is", "is not"] - - const [filterVariety, setFilterVariety] = React.useState(filterVarieties[0]) - - // Update filter variety - React.useEffect(() => { - if (selectedOption?.options.length) { - setFilterVariety("is") - } - }, [selectedOption?.options.length]) - - // Create query string - const createQueryString = React.useCallback( - (params: Record) => { - const newSearchParams = new URLSearchParams(searchParams?.toString()) - - for (const [key, value] of Object.entries(params)) { - if (value === null) { - newSearchParams.delete(key) - } else { - newSearchParams.set(key, String(value)) - } - } - - return newSearchParams.toString() - }, - [searchParams] - ) - - // Update query string - React.useEffect(() => { - if (debounceValue.length > 0) { - navigate( - `${currentPathName}?${createQueryString({ - [selectedOption?.value ?? ""]: `${debounceValue}${ - debounceValue.length > 0 ? `.${filterVariety}` : "" - }`, - })}`, - // { - // scroll: false, - // } - ) - } - - if (debounceValue.length === 0) { - navigate( - `${currentPathName}?${createQueryString({ - [selectedOption?.value ?? ""]: null, - })}`, - // { - // scroll: false, - // } - ) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [debounceValue, filterVariety, selectedOption?.value]) - - // Update operator query string - React.useEffect(() => { - if (operator?.value) { - navigate( - `${currentPathName}?${createQueryString({ - operator: operator.value, - })}`, - // { - // scroll: false, - // } - ) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [operator?.value]) - - return ( -
- {i === 0 ? ( -
Where
- ) : i === 1 ? ( - - ) : ( -
{operator?.label}
- )} - - - {selectedOption?.options.length ? ( - table.getColumn(selectedOption.value ? String(option.value) : "") && ( - - ) - ) : ( - setValue(event.target.value)} - autoFocus - /> - )} - - - - - - { - setSelectedOptions((prev) => - prev.filter((item) => item.id !== option.id) - ) - }} - > - - { - if (!selectedOption) return - - setSelectedOptions((prev) => [ - ...prev, - { - id: crypto.randomUUID(), - label: selectedOption.label, - value: selectedOption.value, - options: selectedOption.options ?? [], - isMulti: true, - }, - ]) - }} - > - - - -
- ) -} \ No newline at end of file diff --git a/app/components/atoms/contributors/table/create-task-dialog.tsx b/app/components/atoms/contributors/table/create-task-dialog.tsx deleted file mode 100644 index aeedbd7..0000000 --- a/app/components/atoms/contributors/table/create-task-dialog.tsx +++ /dev/null @@ -1,222 +0,0 @@ -"use client"; - -import * as React from "react"; -import { tasks } from "@/db/schema"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon, ReloadIcon } from "@radix-ui/react-icons"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; - -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; - -// import { createTask } from "../_lib/actions"; -// import { createTaskSchema, type CreateTaskSchema } from "../_lib/validations"; - -export function CreateTaskDialog() { - const [open, setOpen] = React.useState(false); - const [isCreatePending, startCreateTransition] = React.useTransition(); - - const form = useForm({ - resolver: zodResolver(createTaskSchema), - }); - - // function onSubmit(input: CreateTaskSchema) { - // startCreateTransition(async () => { - // const { error } = await createTask(input); - - // if (error) { - // toast.error(error); - // return; - // } - - // form.reset(); - // setOpen(false); - // toast.success("Task created"); - // }); - // } - - return ( - - - - - - - Create task - - Fill in the details below to create a new task. - - -
- - ( - - Title - -