change to use the tannstack table

This commit is contained in:
THIS ONE IS A LITTLE BIT TRICKY KRUB 2025-03-31 21:07:23 +07:00
parent 7b69c68056
commit 2b694a1b44
4 changed files with 282 additions and 180 deletions

View File

@ -18,7 +18,13 @@ import {
CloudRain, CloudRain,
Wind, Wind,
} from "lucide-react"; } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
@ -26,7 +32,11 @@ import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { ChatbotDialog } from "./chatbot-dialog"; import { ChatbotDialog } from "./chatbot-dialog";
import { AnalyticsDialog } from "./analytics-dialog"; import { AnalyticsDialog } from "./analytics-dialog";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import type { Crop, CropAnalytics } from "@/types"; import type { Crop, CropAnalytics } from "@/types";
import GoogleMapWithDrawing from "@/components/google-map-with-drawing"; import GoogleMapWithDrawing from "@/components/google-map-with-drawing";
@ -37,7 +47,11 @@ interface CropDetailPageParams {
cropId: string; cropId: string;
} }
export default function CropDetailPage({ params }: { params: Promise<CropDetailPageParams> }) { export default function CropDetailPage({
params,
}: {
params: Promise<CropDetailPageParams>;
}) {
const router = useRouter(); const router = useRouter();
const [crop, setCrop] = useState<Crop | null>(null); const [crop, setCrop] = useState<Crop | null>(null);
const [analytics, setAnalytics] = useState<CropAnalytics | null>(null); const [analytics, setAnalytics] = useState<CropAnalytics | null>(null);
@ -57,7 +71,9 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
if (!crop || !analytics) { if (!crop || !analytics) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-background text-foreground">Loading...</div> <div className="min-h-screen flex items-center justify-center bg-background text-foreground">
Loading...
</div>
); );
} }
@ -87,7 +103,8 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
icon: ListCollapse, icon: ListCollapse,
description: "View detailed information", description: "View detailed information",
onClick: () => console.log("Details clicked"), onClick: () => console.log("Details clicked"),
color: "bg-purple-50 dark:bg-purple-900 text-purple-600 dark:text-purple-300", color:
"bg-purple-50 dark:bg-purple-900 text-purple-600 dark:text-purple-300",
}, },
{ {
title: "Settings", title: "Settings",
@ -107,7 +124,8 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
<Button <Button
variant="ghost" variant="ghost"
className="gap-2 text-green-700 dark:text-green-300 hover:text-green-800 dark:hover:text-green-200 hover:bg-green-100/50 dark:hover:bg-green-800/50" className="gap-2 text-green-700 dark:text-green-300 hover:text-green-800 dark:hover:text-green-200 hover:bg-green-100/50 dark:hover:bg-green-800/50"
onClick={() => router.back()}> onClick={() => router.back()}
>
<ArrowLeft className="h-4 w-4" /> Back to Farm <ArrowLeft className="h-4 w-4" /> Back to Farm
</Button> </Button>
<HoverCard> <HoverCard>
@ -126,7 +144,9 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
</Avatar> </Avatar>
<div className="space-y-1"> <div className="space-y-1">
<h4 className="text-sm font-semibold">Growth Timeline</h4> <h4 className="text-sm font-semibold">Growth Timeline</h4>
<p className="text-sm text-muted-foreground">Planted on {crop.plantedDate.toLocaleDateString()}</p> <p className="text-sm text-muted-foreground">
Planted on {crop.plantedDate.toLocaleDateString()}
</p>
<div className="flex items-center pt-2"> <div className="flex items-center pt-2">
<Separator className="w-full" /> <Separator className="w-full" />
<span className="mx-2 text-xs text-muted-foreground"> <span className="mx-2 text-xs text-muted-foreground">
@ -150,19 +170,28 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge variant="outline" className={`${healthColors[analytics.plantHealth]} border`}> <Badge
variant="outline"
className={`${healthColors[analytics.plantHealth]} border`}
>
Health Score: {crop.healthScore}% Health Score: {crop.healthScore}%
</Badge> </Badge>
<Badge variant="outline" className="bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-300"> <Badge
variant="outline"
className="bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-300"
>
Growing Growing
</Badge> </Badge>
</div> </div>
{crop.expectedHarvest ? ( {crop.expectedHarvest ? (
<p className="text-sm text-muted-foreground mt-1"> <p className="text-sm text-muted-foreground mt-1">
Expected harvest: {crop.expectedHarvest.toLocaleDateString()} Expected harvest:{" "}
{crop.expectedHarvest.toLocaleDateString()}
</p> </p>
) : ( ) : (
<p className="text-sm text-muted-foreground mt-1">Expected harvest date not available</p> <p className="text-sm text-muted-foreground mt-1">
Expected harvest date not available
</p>
)} )}
</div> </div>
</div> </div>
@ -180,13 +209,18 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
key={action.title} key={action.title}
variant="outline" variant="outline"
className={`h-auto p-4 flex flex-col items-center gap-3 transition-all group ${action.color} hover:scale-105`} className={`h-auto p-4 flex flex-col items-center gap-3 transition-all group ${action.color} hover:scale-105`}
onClick={action.onClick}> onClick={action.onClick}
<div className={`p-3 rounded-lg ${action.color} group-hover:scale-110 transition-transform`}> >
<div
className={`p-3 rounded-lg ${action.color} group-hover:scale-110 transition-transform`}
>
<action.icon className="h-5 w-5" /> <action.icon className="h-5 w-5" />
</div> </div>
<div className="text-center"> <div className="text-center">
<div className="font-medium mb-1">{action.title}</div> <div className="font-medium mb-1">{action.title}</div>
<p className="text-xs text-muted-foreground">{action.description}</p> <p className="text-xs text-muted-foreground">
{action.description}
</p>
</div> </div>
</Button> </Button>
))} ))}
@ -196,7 +230,9 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
<Card className="border-green-100 dark:border-green-700"> <Card className="border-green-100 dark:border-green-700">
<CardHeader> <CardHeader>
<CardTitle>Environmental Conditions</CardTitle> <CardTitle>Environmental Conditions</CardTitle>
<CardDescription>Real-time monitoring of growing conditions</CardDescription> <CardDescription>
Real-time monitoring of growing conditions
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid gap-6"> <div className="grid gap-6">
@ -247,15 +283,22 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
].map((metric) => ( ].map((metric) => (
<Card <Card
key={metric.label} key={metric.label}
className="border-none shadow-none bg-gradient-to-br from-white to-gray-50/50 dark:from-slate-800 dark:to-slate-700/50"> className="border-none shadow-none bg-gradient-to-br from-white to-gray-50/50 dark:from-slate-800 dark:to-slate-700/50"
>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className={`p-2 rounded-lg ${metric.bg}`}> <div className={`p-2 rounded-lg ${metric.bg}`}>
<metric.icon className={`h-4 w-4 ${metric.color}`} /> <metric.icon
className={`h-4 w-4 ${metric.color}`}
/>
</div> </div>
<div> <div>
<p className="text-sm font-medium text-muted-foreground">{metric.label}</p> <p className="text-sm font-medium text-muted-foreground">
<p className="text-2xl font-semibold tracking-tight">{metric.value}</p> {metric.label}
</p>
<p className="text-2xl font-semibold tracking-tight">
{metric.value}
</p>
</div> </div>
</div> </div>
</CardContent> </CardContent>
@ -269,9 +312,14 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="font-medium">Growth Progress</span> <span className="font-medium">Growth Progress</span>
<span className="text-muted-foreground">{analytics.growthProgress}%</span> <span className="text-muted-foreground">
{analytics.growthProgress}%
</span>
</div> </div>
<Progress value={analytics.growthProgress} className="h-2" /> <Progress
value={analytics.growthProgress}
className="h-2"
/>
</div> </div>
{/* Next Action Card */} {/* Next Action Card */}
@ -282,10 +330,15 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
<Timer className="h-4 w-4 text-green-600 dark:text-green-300" /> <Timer className="h-4 w-4 text-green-600 dark:text-green-300" />
</div> </div>
<div> <div>
<p className="font-medium mb-1">Next Action Required</p> <p className="font-medium mb-1">
<p className="text-sm text-muted-foreground">{analytics.nextAction}</p> Next Action Required
</p>
<p className="text-sm text-muted-foreground">
{analytics.nextAction}
</p>
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
Due by {analytics.nextActionDue.toLocaleDateString()} Due by{" "}
{analytics.nextActionDue.toLocaleDateString()}
</p> </p>
</div> </div>
</div> </div>
@ -337,9 +390,14 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
<div key={nutrient.name} className="space-y-2"> <div key={nutrient.name} className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="font-medium">{nutrient.name}</span> <span className="font-medium">{nutrient.name}</span>
<span className="text-muted-foreground">{nutrient.value}%</span> <span className="text-muted-foreground">
{nutrient.value}%
</span>
</div> </div>
<Progress value={nutrient.value} className={`h-2 ${nutrient.color}`} /> <Progress
value={nutrient.value}
className={`h-2 ${nutrient.color}`}
/>
</div> </div>
))} ))}
</div> </div>
@ -372,10 +430,14 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
][i] ][i]
} }
</p> </p>
<p className="text-xs text-muted-foreground">2 hours ago</p> <p className="text-xs text-muted-foreground">
2 hours ago
</p>
</div> </div>
</div> </div>
{i < 4 && <Separator className="my-4 dark:bg-slate-700" />} {i < 4 && (
<Separator className="my-4 dark:bg-slate-700" />
)}
</div> </div>
))} ))}
</ScrollArea> </ScrollArea>
@ -385,8 +447,17 @@ export default function CropDetailPage({ params }: { params: Promise<CropDetailP
</div> </div>
{/* Dialogs */} {/* Dialogs */}
<ChatbotDialog open={isChatOpen} onOpenChange={setIsChatOpen} cropName={crop.name} /> <ChatbotDialog
<AnalyticsDialog open={isAnalyticsOpen} onOpenChange={setIsAnalyticsOpen} crop={crop} analytics={analytics} /> open={isChatOpen}
onOpenChange={setIsChatOpen}
cropName={crop.name}
/>
<AnalyticsDialog
open={isAnalyticsOpen}
onOpenChange={setIsAnalyticsOpen}
crop={crop}
analytics={analytics}
/>
</div> </div>
</div> </div>
); );
@ -399,9 +470,15 @@ function Activity({ icon }: { icon: number }) {
const icons = [ const icons = [
<Droplets key="0" className="h-4 w-4 text-blue-500 dark:text-blue-300" />, <Droplets key="0" className="h-4 w-4 text-blue-500 dark:text-blue-300" />,
<Leaf key="1" className="h-4 w-4 text-green-500 dark:text-green-300" />, <Leaf key="1" className="h-4 w-4 text-green-500 dark:text-green-300" />,
<LineChart key="2" className="h-4 w-4 text-purple-500 dark:text-purple-300" />, <LineChart
key="2"
className="h-4 w-4 text-purple-500 dark:text-purple-300"
/>,
<Sprout key="3" className="h-4 w-4 text-yellow-500 dark:text-yellow-300" />, <Sprout key="3" className="h-4 w-4 text-yellow-500 dark:text-yellow-300" />,
<ThermometerSun key="4" className="h-4 w-4 text-orange-500 dark:text-orange-300" />, <ThermometerSun
key="4"
className="h-4 w-4 text-orange-500 dark:text-orange-300"
/>,
]; ];
return icons[icon]; return icons[icon];
} }

View File

@ -1,19 +1,25 @@
"use client"; "use client";
import { useState } from "react"; import {
JSXElementConstructor,
ReactElement,
ReactNode,
ReactPortal,
useState,
} from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Calendar, ChevronDown, Plus, Search } from "lucide-react"; import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
flexRender,
SortingState,
PaginationState,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { import {
Table, Table,
TableBody, TableBody,
@ -26,14 +32,8 @@ import {
Pagination, Pagination,
PaginationContent, PaginationContent,
PaginationItem, PaginationItem,
PaginationLink,
} from "@/components/ui/pagination"; } from "@/components/ui/pagination";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Calendar as CalendarComponent } from "@/components/ui/calendar";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { fetchInventoryItems } from "@/api/inventory"; import { fetchInventoryItems } from "@/api/inventory";
@ -44,11 +44,14 @@ import { DeleteInventoryItem } from "./delete-inventory-item";
export default function InventoryPage() { export default function InventoryPage() {
const [date, setDate] = useState<Date>(); const [date, setDate] = useState<Date>();
const [inventoryType, setInventoryType] = useState("all"); const [inventoryType, setInventoryType] = useState("all");
const [currentPage, setCurrentPage] = useState(1); const [sorting, setSorting] = useState<SortingState>([]);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
// Fetch inventory items using react-query.
const { const {
data: inventoryItems, data: inventoryItems = [],
isLoading, isLoading,
isError, isError,
} = useQuery({ } = useQuery({
@ -57,155 +60,156 @@ export default function InventoryPage() {
staleTime: 60 * 1000, staleTime: 60 * 1000,
}); });
if (isLoading) {
return (
<div className="flex min-h-screen bg-background items-center justify-center">
Loading...
</div>
);
}
if (isError || !inventoryItems) {
return (
<div className="flex min-h-screen bg-background items-center justify-center">
Error loading inventory data.
</div>
);
}
// Filter items based on selected type.
const filteredItems = const filteredItems =
inventoryType === "all" inventoryType === "all"
? inventoryItems ? inventoryItems
: inventoryItems.filter((item) => : inventoryItems.filter(
inventoryType === "plantation" (item) => item.type.toLowerCase() === inventoryType
? item.type === "Plantation"
: item.type === "Fertilizer",
); );
const columns = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "category", header: "Category" },
{ accessorKey: "type", header: "Type" },
{ accessorKey: "quantity", header: "Quantity" },
{ accessorKey: "lastUpdated", header: "Last Updated" },
{
accessorKey: "status",
header: "Status",
cell: (info: {
getValue: () =>
| string
| number
| bigint
| boolean
| ReactElement<unknown, string | JSXElementConstructor<any>>
| Iterable<ReactNode>
| ReactPortal
| Promise<
| string
| number
| bigint
| boolean
| ReactPortal
| ReactElement<unknown, string | JSXElementConstructor<any>>
| Iterable<ReactNode>
| null
| undefined
>
| null
| undefined;
}) => <Badge>{info.getValue()}</Badge>,
},
{ accessorKey: "edit", header: "Edit", cell: () => <EditInventoryItem /> },
{
accessorKey: "delete",
header: "Delete",
cell: () => <DeleteInventoryItem />,
},
];
const table = useReactTable({
data: filteredItems,
columns,
state: { sorting, pagination },
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
onPaginationChange: setPagination,
});
if (isLoading)
return (
<div className="flex min-h-screen items-center justify-center">
Loading...
</div>
);
if (isError)
return (
<div className="flex min-h-screen items-center justify-center">
Error loading inventory data.
</div>
);
return ( return (
<div className="flex min-h-screen bg-background"> <div className="flex min-h-screen bg-background">
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
<main className="flex-1 p-6"> <main className="flex-1 p-6">
<h1 className="text-2xl font-bold tracking-tight mb-6">Inventory</h1> <h1 className="text-2xl font-bold tracking-tight mb-6">Inventory</h1>
{/* Filters and search */}
<div className="flex flex-col sm:flex-row gap-4 mb-6"> <div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="flex gap-2"> <Button onClick={() => setInventoryType("all")} className="w-24">
<Button All
variant={inventoryType === "all" ? "default" : "outline"} </Button>
onClick={() => setInventoryType("all")} <Input type="search" placeholder="Search the name" />
className="w-24" <AddInventoryItem />
>
All
</Button>
<Select value={inventoryType} onValueChange={setInventoryType}>
<SelectTrigger className="w-32">
<SelectValue placeholder="Crop" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="all">All</SelectItem>
<SelectItem value="plantation">Plantation</SelectItem>
<SelectItem value="fertilizer">Fertilizer</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="flex flex-1 gap-4">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="flex-1 justify-between">
<div className="flex items-center">
<Calendar className="mr-2 h-4 w-4" />
{date ? date.toLocaleDateString() : "Time filter"}
</div>
<ChevronDown className="h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<CalendarComponent
mode="single"
selected={date}
onSelect={setDate}
initialFocus
/>
</PopoverContent>
</Popover>
<div className="relative flex-1">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search Farms"
className="pl-8"
/>
</div>
<AddInventoryItem />
</div>
</div> </div>
{/* Table */}
<div className="border rounded-md"> <div className="border rounded-md">
<h3 className="px-4 py-2 border-b font-medium">Table Fields</h3>
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> {table.getHeaderGroups().map((headerGroup) => (
<TableHead>Name</TableHead> <TableRow key={headerGroup.id}>
<TableHead>Category</TableHead> {headerGroup.headers.map((header) => (
<TableHead>Type</TableHead> <TableHead
<TableHead className="text-right">Quantity</TableHead> key={header.id}
<TableHead>Last Updated</TableHead> onClick={header.column.getToggleSortingHandler()}
<TableHead>Status</TableHead> >
<TableHead>Edit</TableHead> {flexRender(
<TableHead>Delete</TableHead> header.column.columnDef.header,
</TableRow> header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filteredItems.length === 0 ? ( {table.getRowModel().rows.map((row) => (
<TableRow> <TableRow key={row.id}>
<TableCell {row.getVisibleCells().map((cell) => (
colSpan={6} <TableCell key={cell.id}>
className="text-center py-8 text-muted-foreground" {flexRender(
> cell.column.columnDef.cell,
No inventory items found cell.getContext()
</TableCell> )}
</TableCell>
))}
</TableRow> </TableRow>
) : ( ))}
filteredItems.map((item) => (
<TableRow key={item.id}>
<TableCell className="font-medium">{item.name}</TableCell>
<TableCell>{item.category}</TableCell>
<TableCell>{item.type}</TableCell>
<TableCell className="text-right">
{item.quantity} {item.unit}
</TableCell>
<TableCell>{item.lastUpdated}</TableCell>
<TableCell>
<Badge
variant={
item.status === "Low Stock"
? "destructive"
: "default"
}
>
{item.status}
</Badge>
</TableCell>
<TableCell>
<EditInventoryItem />
</TableCell>
<TableCell>
<DeleteInventoryItem />
</TableCell>
</TableRow>
))
)}
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
<Pagination className="mt-5">
<PaginationContent>
<PaginationItem>
<Button
className="flex w-24"
onClick={() =>
setPagination((prev) => ({
...prev,
pageIndex: Math.max(0, prev.pageIndex - 1),
}))
}
>
Previous
</Button>
</PaginationItem>
<PaginationItem>
<Button
className="flex w-24"
onClick={() =>
setPagination((prev) => ({
...prev,
pageIndex: prev.pageIndex + 1,
}))
}
>
Next
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
</main> </main>
</div> </div>
</div> </div>

View File

@ -31,6 +31,7 @@
"@react-oauth/google": "^0.12.1", "@react-oauth/google": "^0.12.1",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@tanstack/react-query": "^5.66.0", "@tanstack/react-query": "^5.66.0",
"@tanstack/react-table": "^8.21.2",
"axios": "^1.7.9", "axios": "^1.7.9",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",

View File

@ -71,6 +71,9 @@ dependencies:
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^5.66.0 specifier: ^5.66.0
version: 5.67.3(react@19.0.0) version: 5.67.3(react@19.0.0)
'@tanstack/react-table':
specifier: ^8.21.2
version: 8.21.2(react-dom@19.0.0)(react@19.0.0)
axios: axios:
specifier: ^1.7.9 specifier: ^1.7.9
version: 1.8.3 version: 1.8.3
@ -1600,6 +1603,23 @@ packages:
react: 19.0.0 react: 19.0.0
dev: false dev: false
/@tanstack/react-table@8.21.2(react-dom@19.0.0)(react@19.0.0):
resolution: {integrity: sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==}
engines: {node: '>=12'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@tanstack/table-core': 8.21.2
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
dev: false
/@tanstack/table-core@8.21.2:
resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
engines: {node: '>=12'}
dev: false
/@types/d3-array@3.2.1: /@types/d3-array@3.2.1:
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
dev: false dev: false