mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 22:14:08 +01:00
change to use the tannstack table
This commit is contained in:
parent
7b69c68056
commit
2b694a1b44
@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user