feat: added grid layout for person list
Signed-off-by: Varun Raj <mailme@varunraj.in>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
reactStrictMode: false,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@tanstack/react-table": "^8.20.1",
|
||||
"@types/qs": "^6.9.15",
|
||||
"axios": "^1.7.4",
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { Inter as FontSans } from "next/font/google"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const fontSans = FontSans({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
})
|
||||
|
||||
type RootLayoutProps = {
|
||||
children: React.ReactNode
|
||||
@@ -15,7 +10,6 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||
return (
|
||||
<div className={cn(
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
fontSans.variable
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { listPeople } from "@/handlers/api/people.handler";
|
||||
import { IListData } from "@/types/common";
|
||||
import { IPerson } from "@/types/person";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Loader from "../ui/loader";
|
||||
@@ -9,6 +8,9 @@ import { Table } from "../ui/table";
|
||||
import PersonBirthdayCell from "./PersonBirthdayCell";
|
||||
import Link from "next/link";
|
||||
import { ENV } from "@/config/environment";
|
||||
import PersonHideCell from "./PersonHideCell";
|
||||
import { PeoplePagination } from "./PeoplePagination";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const columns: ColumnDef<IPerson>[] = [
|
||||
{
|
||||
@@ -25,7 +27,7 @@ const columns: ColumnDef<IPerson>[] = [
|
||||
header: "Hidden",
|
||||
accessorKey: "isHidden",
|
||||
cell: ({ row }) => (
|
||||
<span>{row.original.isHidden ? "Yes" : "No"}</span>
|
||||
<PersonHideCell person={row.original} />
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -46,15 +48,18 @@ const columns: ColumnDef<IPerson>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
export default function PeopleList() {
|
||||
const router = useRouter()
|
||||
const { page = 1 } = router.query as { page: string }
|
||||
const [people, setPeople] = useState<IPerson[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
return listPeople()
|
||||
setLoading(true);
|
||||
return listPeople({
|
||||
page
|
||||
})
|
||||
.then((response) => {
|
||||
setPeople(response.people);
|
||||
})
|
||||
@@ -67,17 +72,28 @@ export default function PeopleList() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return;
|
||||
fetchData();
|
||||
}, []);
|
||||
}, [page]);
|
||||
|
||||
const renderContent = () => {
|
||||
|
||||
if (loading) return <Loader />;
|
||||
|
||||
if (errorMessage) return <div>{errorMessage}</div>;
|
||||
return (
|
||||
<div className="grid grid-cols-8 gap-4 p-2">
|
||||
{people.map((person) => (
|
||||
<PersonItem person={person} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Table<IPerson> columns={columns} data={people} />
|
||||
{renderContent()}
|
||||
<div className="fixed bottom-0 w-full bg-gray-200 py-2 flex justify-center">
|
||||
<PeoplePagination />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
import { useRouter } from "next/router"
|
||||
import { useMemo } from "react"
|
||||
import { Button } from "../ui/button"
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export function PeoplePagination() {
|
||||
const router = useRouter()
|
||||
const { pathname } = router;
|
||||
const { page = 1 } = router.query as { page: string }
|
||||
|
||||
const [nextPage, prevPage] = useMemo(() => {
|
||||
const pageNum = parseInt(page.toString() || "1", 10)
|
||||
return [pageNum + 1, pageNum - 1]
|
||||
|
||||
}, [page])
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Link href={{
|
||||
pathname,
|
||||
query: { page: prevPage }
|
||||
}}>
|
||||
<Button
|
||||
className="w-[150px]"
|
||||
disabled={prevPage < 1}
|
||||
>
|
||||
<ArrowLeft className="mr-2" size={16} />
|
||||
Previous Page
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href={{
|
||||
pathname,
|
||||
query: { page: nextPage }
|
||||
}}>
|
||||
<Button
|
||||
className="w-[150px]"
|
||||
>
|
||||
Next Page
|
||||
<ArrowRight className="ml-2" size={16} />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -33,9 +33,5 @@ export default function PersonBirthdayCell(
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DatePicker date={person.birthDate} onSelect={handleEdit}/>
|
||||
</div>
|
||||
)
|
||||
return <DatePicker date={person.birthDate} onSelect={handleEdit}/>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { IPerson } from "@/types/person";
|
||||
import React, { useState } from "react";
|
||||
import { DatePicker } from "../ui/datepicker";
|
||||
import { updatePerson } from "@/handlers/api/people.handler";
|
||||
import { formatDate } from "@/helpers/date.helper";
|
||||
import { Button } from "../ui/button";
|
||||
import { Switch } from "../ui/switch";
|
||||
import { Label } from "../ui/label";
|
||||
|
||||
interface IProps {
|
||||
person: IPerson;
|
||||
onUpdate?: (person: IPerson) => any;
|
||||
}
|
||||
|
||||
export default function PersonHideCell({ person, onUpdate }: IProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hidden, setHidden] = useState(person.isHidden);
|
||||
|
||||
const handleEdit = (hidden: boolean) => {
|
||||
setLoading(true);
|
||||
return updatePerson(person.id, {
|
||||
isHidden: hidden,
|
||||
})
|
||||
.then(() => {
|
||||
setHidden(hidden);
|
||||
onUpdate?.({
|
||||
...person,
|
||||
isHidden: hidden,
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => handleEdit(!hidden)}
|
||||
className="bg-gray-200 rounded-lg px-2 py-1 text-sm border"
|
||||
>
|
||||
{hidden ? "Show" : "Hide"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import React, { useState } from "react";
|
||||
import { Avatar } from "../ui/avatar";
|
||||
import { updatePerson } from "@/handlers/api/people.handler";
|
||||
import { PersonMergeDropdown } from "./PersonMergeDropdown";
|
||||
import PersonBirthdayCell from "./PersonBirthdayCell";
|
||||
import PersonHideCell from "./PersonHideCell";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface IProps {
|
||||
person: IPerson;
|
||||
@@ -27,25 +30,44 @@ export default function PersonItem({ person }: IProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar
|
||||
className="w-12 h-12"
|
||||
src={person.thumbnailPath}
|
||||
alt={person.name}
|
||||
/>
|
||||
<div
|
||||
className={clsx("flex flex-col rounded-lg pb-2 border border-2 border-transparent items-center gap-2", {
|
||||
"opacity-50": formData.isHidden,
|
||||
"border border-blue-500": formData.name,
|
||||
})}
|
||||
>
|
||||
<div className="relative w-full h-auto">
|
||||
<Avatar
|
||||
className="w-full h-auto rounded-lg"
|
||||
src={person.thumbnailPath}
|
||||
alt={person.name}
|
||||
/>
|
||||
<div className="absolute top-2 right-2">
|
||||
<PersonHideCell
|
||||
person={person}
|
||||
onUpdate={(newData) => {
|
||||
setFormData(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!editMode ? (
|
||||
<h2
|
||||
className="text-lg font-semibold"
|
||||
className="text-lg text-center font-semibold"
|
||||
onClick={() => {
|
||||
setEditMode((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
{formData.name ? formData.name : <span className="text-gray-400">Unknown</span>}
|
||||
{formData.name ? (
|
||||
formData.name
|
||||
) : (
|
||||
<span className="text-gray-400">Unknown</span>
|
||||
)}
|
||||
</h2>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
className="text-lg font-semibold"
|
||||
className="text-lg font-semibold text-center"
|
||||
defaultValue={formData.name}
|
||||
autoFocus
|
||||
onChange={(e) => {
|
||||
@@ -56,6 +78,7 @@ export default function PersonItem({ person }: IProps) {
|
||||
/>
|
||||
)}
|
||||
<PersonMergeDropdown person={person} />
|
||||
<PersonBirthdayCell person={person} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ export function PersonMergeDropdown(
|
||||
})
|
||||
};
|
||||
|
||||
console.log(searchedPeople);
|
||||
return (
|
||||
<div className="flex items-center space-x-4">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
|
||||
@@ -32,7 +32,7 @@ export function DatePicker(
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] justify-start text-left font-normal",
|
||||
"justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
|
||||
export default function Loader() {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-4">
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -21,5 +22,6 @@ export default function Loader() {
|
||||
</svg>
|
||||
<span className="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
DotsHorizontalIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
||||
|
||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
Pagination.displayName = "Pagination"
|
||||
|
||||
const PaginationContent = React.forwardRef<
|
||||
HTMLUListElement,
|
||||
React.ComponentProps<"ul">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ul
|
||||
ref={ref}
|
||||
className={cn("flex flex-row items-center gap-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
PaginationContent.displayName = "PaginationContent"
|
||||
|
||||
const PaginationItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentProps<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn("", className)} {...props} />
|
||||
))
|
||||
PaginationItem.displayName = "PaginationItem"
|
||||
|
||||
type PaginationLinkProps = {
|
||||
isActive?: boolean
|
||||
} & Pick<ButtonProps, "size"> &
|
||||
React.ComponentProps<"a">
|
||||
|
||||
const PaginationLink = ({
|
||||
className,
|
||||
isActive,
|
||||
size = "icon",
|
||||
...props
|
||||
}: PaginationLinkProps) => (
|
||||
<a
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? "outline" : "ghost",
|
||||
size,
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
PaginationLink.displayName = "PaginationLink"
|
||||
|
||||
const PaginationPrevious = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
size="default"
|
||||
className={cn("gap-1 pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
<span>Previous</span>
|
||||
</PaginationLink>
|
||||
)
|
||||
PaginationPrevious.displayName = "PaginationPrevious"
|
||||
|
||||
const PaginationNext = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
size="default"
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>Next</span>
|
||||
<ChevronRightIcon className="h-4 w-4" />
|
||||
</PaginationLink>
|
||||
)
|
||||
PaginationNext.displayName = "PaginationNext"
|
||||
|
||||
const PaginationEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<DotsHorizontalIcon className="h-4 w-4" />
|
||||
<span className="sr-only">More pages</span>
|
||||
</span>
|
||||
)
|
||||
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||
|
||||
export {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationLink,
|
||||
PaginationItem,
|
||||
PaginationPrevious,
|
||||
PaginationNext,
|
||||
PaginationEllipsis,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
))
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||
|
||||
export { Switch }
|
||||
+92
-81
@@ -1,29 +1,41 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ColumnDef, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, TableOptions, useReactTable } from "@tanstack/react-table"
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
TableOptions,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { ScrollArea } from "./scroll-area";
|
||||
|
||||
const TableRoot = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
TableRoot.displayName = "Table"
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableRoot.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn("[&_tr]:border-b sticky top-0 bg-secondary", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
@@ -34,8 +46,8 @@ const TableBody = React.forwardRef<
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
@@ -49,8 +61,8 @@ const TableFooter = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
@@ -64,8 +76,8 @@ const TableRow = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
@@ -79,8 +91,8 @@ const TableHead = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
@@ -94,8 +106,8 @@ const TableCell = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
@@ -106,16 +118,16 @@ const TableCaption = React.forwardRef<
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
interface ITableProps<T> extends Partial<TableOptions<T>> {
|
||||
columns: ColumnDef<T>[]
|
||||
data: T[]
|
||||
columns: ColumnDef<T>[];
|
||||
data: T[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Table = <T, >(props: ITableProps<T>) => {
|
||||
|
||||
const Table = <T,>(props: ITableProps<T>) => {
|
||||
const table = useReactTable({
|
||||
...props,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
@@ -124,56 +136,55 @@ const Table = <T, >(props: ITableProps<T>) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<TableRoot>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
<ScrollArea className={cn("rounded-md border overflow-y-auto")}>
|
||||
<TableRoot>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody className={cn("overflow-y-auto", props.className)}>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={props.columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
)
|
||||
}
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={props.columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
Table,
|
||||
@@ -185,4 +196,4 @@ export {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,8 +3,11 @@ import { cleanUpPerson } from "@/helpers/person.helper";
|
||||
import API from "@/lib/api"
|
||||
import { IPeopleListResponse, IPerson } from "@/types/person"
|
||||
|
||||
export const listPeople = (): Promise<IPeopleListResponse> => {
|
||||
return API.get(LIST_PEOPLE_PATH).then((response) => {
|
||||
interface IPersonListFilters {
|
||||
page: string | number;
|
||||
}
|
||||
export const listPeople = (filters: IPersonListFilters): Promise<IPeopleListResponse> => {
|
||||
return API.get(LIST_PEOPLE_PATH, filters).then((response) => {
|
||||
return {
|
||||
...response,
|
||||
people: response.people.map(cleanUpPerson),
|
||||
|
||||
@@ -8,7 +8,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const targetUrl = `${ENV.IMMICH_URL}/api${proxyPath}`;
|
||||
|
||||
console.log('targetUrl:', req.method)
|
||||
console.log('targetUrl:', targetUrl)
|
||||
|
||||
try {
|
||||
const response = await fetch(targetUrl, {
|
||||
@@ -17,6 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
'x-api-key': ENV.IMMICH_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
||||
body: req.method !== 'GET' && req.method !== 'HEAD' ? JSON.stringify(req.body) : null,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const config = {
|
||||
prefix: "",
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
|
||||
Reference in New Issue
Block a user