Link homepage cards to products views, add url params for views and for login redirect

This commit is contained in:
2025-01-15 15:07:42 -05:00
parent e9ad2d632b
commit 9e21d593f1
5 changed files with 76 additions and 22 deletions

View File

@@ -11,11 +11,11 @@ import PurchaseOrders from './pages/PurchaseOrders';
import { Login } from './pages/Login';
import { useEffect } from 'react';
import config from './config';
import { RequireAuth } from './components/auth/RequireAuth';
const queryClient = new QueryClient();
function App() {
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
const navigate = useNavigate();
useEffect(() => {
@@ -50,19 +50,19 @@ function App() {
<Toaster richColors position="top-center" />
<Routes>
<Route path="/login" element={<Login />} />
{isLoggedIn ? (
<Route element={<MainLayout />}>
<Route path="/" element={<Dashboard />} />
<Route path="/products" element={<Products />} />
<Route path="/orders" element={<Orders />} />
<Route path="/purchase-orders" element={<PurchaseOrders />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
) : (
<Route path="*" element={<Navigate to="/login" replace />} />
)}
<Route element={
<RequireAuth>
<MainLayout />
</RequireAuth>
}>
<Route path="/" element={<Dashboard />} />
<Route path="/products" element={<Products />} />
<Route path="/orders" element={<Orders />} />
<Route path="/purchase-orders" element={<PurchaseOrders />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</QueryClientProvider>
);

View File

@@ -0,0 +1,16 @@
import { Navigate, useLocation } from "react-router-dom"
export function RequireAuth({ children }: { children: React.ReactNode }) {
const isLoggedIn = sessionStorage.getItem("isLoggedIn") === "true"
const location = useLocation()
if (!isLoggedIn) {
// Redirect to login with the current path in the redirect parameter
return <Navigate
to={`/login?redirect=${encodeURIComponent(location.pathname + location.search)}`}
replace
/>
}
return <>{children}</>
}

View File

@@ -2,6 +2,8 @@ import { useQuery } from "@tanstack/react-query"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { AlertCircle, AlertTriangle, CheckCircle2, PackageSearch } from "lucide-react"
import config from "@/config"
import { useNavigate } from "react-router-dom"
import { cn } from "@/lib/utils"
interface InventoryHealth {
critical: number
@@ -12,6 +14,7 @@ interface InventoryHealth {
}
export function InventoryHealthSummary() {
const navigate = useNavigate();
const { data: summary } = useQuery<InventoryHealth>({
queryKey: ["inventory-health"],
queryFn: async () => {
@@ -31,6 +34,7 @@ export function InventoryHealthSummary() {
icon: AlertCircle,
className: "bg-destructive/10",
iconClassName: "text-destructive",
view: "critical"
},
{
title: "Reorder Soon",
@@ -39,6 +43,7 @@ export function InventoryHealthSummary() {
icon: AlertTriangle,
className: "bg-warning/10",
iconClassName: "text-warning",
view: "reorder"
},
{
title: "Healthy Stock",
@@ -47,6 +52,7 @@ export function InventoryHealthSummary() {
icon: CheckCircle2,
className: "bg-success/10",
iconClassName: "text-success",
view: "healthy"
},
{
title: "Overstock",
@@ -55,13 +61,18 @@ export function InventoryHealthSummary() {
icon: PackageSearch,
className: "bg-muted",
iconClassName: "text-muted-foreground",
view: "overstocked"
},
]
return (
<>
{stats.map((stat) => (
<Card key={stat.title} className={stat.className}>
<Card
key={stat.title}
className={cn(stat.className, "cursor-pointer hover:opacity-90 transition-opacity")}
onClick={() => navigate(`/products?view=${stat.view}`)}
>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{stat.title}</CardTitle>
<stat.icon className={`h-4 w-4 ${stat.iconClassName}`} />

View File

@@ -1,5 +1,5 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
@@ -15,6 +15,7 @@ export function Login() {
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
@@ -57,7 +58,12 @@ export function Login() {
sessionStorage.setItem("token", data.token);
sessionStorage.setItem("isLoggedIn", "true");
toast.success("Successfully logged in");
navigate("/", { replace: true });
// Get the redirect URL from the URL parameters, defaulting to "/"
const redirectTo = searchParams.get("redirect") || "/"
// Navigate to the redirect URL after successful login
navigate(redirectTo)
} catch (error) {
console.error("Login error:", error);
toast.error(

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useMemo } from 'react';
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import { useSearchParams } from 'react-router-dom';
import { ProductFilters } from '@/components/products/ProductFilters';
import { ProductTable } from '@/components/products/ProductTable';
import { ProductTableSkeleton } from '@/components/products/ProductTableSkeleton';
@@ -151,11 +152,12 @@ const VIEW_COLUMNS: Record<string, ColumnKey[]> = {
};
export function Products() {
const [searchParams, setSearchParams] = useSearchParams();
const [filters, setFilters] = useState<Record<string, string | number | boolean>>({});
const [sortColumn, setSortColumn] = useState<ColumnKey>('title');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const [currentPage, setCurrentPage] = useState(1);
const [activeView, setActiveView] = useState("all");
const [activeView, setActiveView] = useState(searchParams.get('view') || "all");
const [pageSize] = useState(50);
const [showNonReplenishable, setShowNonReplenishable] = useState(false);
const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
@@ -369,6 +371,25 @@ export function Products() {
: [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2]
: Array.from({ length: totalPages }, (_, i) => i + 1);
// Update URL when view changes
const handleViewChange = (view: string) => {
setActiveView(view);
setCurrentPage(1);
setSearchParams(prev => {
const newParams = new URLSearchParams(prev);
newParams.set('view', view);
return newParams;
});
};
// Sync with URL params when they change
useEffect(() => {
const viewParam = searchParams.get('view');
if (viewParam && viewParam !== activeView) {
setActiveView(viewParam);
}
}, [searchParams]);
return (
<motion.div
initial={{ opacity: 0 }}
@@ -380,10 +401,10 @@ export function Products() {
<h1 className="text-3xl font-bold tracking-tight">Products</h1>
</div>
<ProductViews activeView={activeView} onViewChange={(view) => {
setActiveView(view);
setCurrentPage(1);
}} />
<ProductViews
activeView={activeView}
onViewChange={handleViewChange}
/>
<div>
<div className="flex items-center justify-between mb-4">