Link homepage cards to products views, add url params for views and for login redirect
This commit is contained in:
@@ -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,8 +50,11 @@ function App() {
|
||||
<Toaster richColors position="top-center" />
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
{isLoggedIn ? (
|
||||
<Route element={<MainLayout />}>
|
||||
<Route element={
|
||||
<RequireAuth>
|
||||
<MainLayout />
|
||||
</RequireAuth>
|
||||
}>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/products" element={<Products />} />
|
||||
<Route path="/orders" element={<Orders />} />
|
||||
@@ -60,9 +63,6 @@ function App() {
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Route>
|
||||
) : (
|
||||
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||
)}
|
||||
</Routes>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
16
inventory/src/components/auth/RequireAuth.tsx
Normal file
16
inventory/src/components/auth/RequireAuth.tsx
Normal 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}</>
|
||||
}
|
||||
@@ -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}`} />
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user