common components extraction.
This commit is contained in:
parent
e4da88b98b
commit
3fd769a36f
12 changed files with 218 additions and 153 deletions
19
apps/pharmanager/src/components/ui/BackLink.tsx
Normal file
19
apps/pharmanager/src/components/ui/BackLink.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Link } from "@tanstack/react-router";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
interface BackLinkProps {
|
||||
to: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function BackLink({ to, label }: BackLinkProps) {
|
||||
return (
|
||||
<Link
|
||||
to={to}
|
||||
className="inline-flex items-center gap-1.5 text-sm text-blue-600 hover:underline mb-5"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Back to {label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
29
apps/pharmanager/src/components/ui/DetailRow.tsx
Normal file
29
apps/pharmanager/src/components/ui/DetailRow.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { cn } from "#/lib/cn";
|
||||
|
||||
interface DetailRowProps {
|
||||
label: string;
|
||||
value: string;
|
||||
valueClass?: string;
|
||||
last?: boolean;
|
||||
}
|
||||
|
||||
export function DetailRow({
|
||||
label,
|
||||
value,
|
||||
valueClass = "",
|
||||
last = false,
|
||||
}: DetailRowProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex justify-between items-center py-2.5 text-sm",
|
||||
!last && "border-b border-slate-200",
|
||||
)}
|
||||
>
|
||||
<span className="text-slate-600 text-[13px]">{label}</span>
|
||||
<span className={cn("font-medium text-right", valueClass)}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
apps/pharmanager/src/components/ui/EmptyState.tsx
Normal file
38
apps/pharmanager/src/components/ui/EmptyState.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { Link } from "@tanstack/react-router";
|
||||
import { cn } from "#/lib/cn";
|
||||
import { buttonVariants } from "./Button";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
description: string;
|
||||
actionLabel?: string;
|
||||
actionTo?: string;
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
icon: Icon,
|
||||
title,
|
||||
description,
|
||||
actionLabel,
|
||||
actionTo,
|
||||
}: EmptyStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center py-16 px-6 text-slate-600">
|
||||
<Icon className="w-12 h-12 mb-4 opacity-40" />
|
||||
<h3 className="text-base font-semibold text-slate-900 mb-1.5">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm">{description}</p>
|
||||
{actionLabel && actionTo && (
|
||||
<Link
|
||||
to={actionTo}
|
||||
className={cn(buttonVariants({ variant: "primary" }), "mt-4")}
|
||||
>
|
||||
{actionLabel}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
apps/pharmanager/src/components/ui/FormField.tsx
Normal file
29
apps/pharmanager/src/components/ui/FormField.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { ReactNode } from "react";
|
||||
import { cn } from "#/lib/cn";
|
||||
|
||||
interface FormFieldProps {
|
||||
label: string;
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function FormField({
|
||||
label,
|
||||
required = false,
|
||||
error,
|
||||
className,
|
||||
children,
|
||||
}: FormFieldProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
||||
{label}
|
||||
{required && <span className="text-red-600 ml-0.5">*</span>}
|
||||
</label>
|
||||
{children}
|
||||
{error && <p className="text-sm text-red-600 mt-1">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
apps/pharmanager/src/components/ui/SearchToolbar.tsx
Normal file
38
apps/pharmanager/src/components/ui/SearchToolbar.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { Link } from "@tanstack/react-router";
|
||||
import { Search, Plus } from "lucide-react";
|
||||
import { buttonVariants } from "./Button";
|
||||
|
||||
interface SearchToolbarProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder: string;
|
||||
addLink: string;
|
||||
addLabel: string;
|
||||
}
|
||||
|
||||
export function SearchToolbar({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
addLink,
|
||||
addLabel,
|
||||
}: SearchToolbarProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 mb-5 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[200px] max-w-[480px] px-3.5 py-2 bg-white rounded-md border border-slate-200 transition-all duration-200 focus-within:border-blue-600 focus-within:shadow-[0_0_0_3px_rgba(37,99,235,0.1)]">
|
||||
<Search className="w-4 h-4 text-slate-600 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
<Link to={addLink} className={buttonVariants({ variant: "primary" })}>
|
||||
<Plus className="w-[15px] h-[15px]" />
|
||||
{addLabel}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
export { Button, type ButtonProps, buttonVariants } from "./Button";
|
||||
export { Input, type InputProps, inputVariants } from "./Input";
|
||||
export { Textarea, type TextareaProps, textareaVariants } from "./Textarea";
|
||||
export { Combobox } from "./Combobox";
|
||||
export type { ComboboxOption } from "./Combobox";
|
||||
export { Checkbox } from "./Checkbox";
|
||||
export type { CheckboxProps } from "./Checkbox";
|
||||
export { Combobox } from "./Combobox";
|
||||
export type { ComboboxOption } from "./Combobox";
|
||||
export { DetailRow } from "./DetailRow";
|
||||
export { BackLink } from "./BackLink";
|
||||
export { FormField } from "./FormField";
|
||||
export { EmptyState } from "./EmptyState";
|
||||
export { SearchToolbar } from "./SearchToolbar";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useState, useMemo, useCallback } from "react";
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { Search, Plus, Pencil, Trash2, Truck } from "lucide-react";
|
||||
import { Pencil, Trash2, Truck } from "lucide-react";
|
||||
import { GridTable } from "#/components/GridTable";
|
||||
import type { GridTableColumn } from "#/components/GridTable";
|
||||
import { Button, buttonVariants } from "#/components/ui";
|
||||
import { Button, SearchToolbar } from "#/components/ui";
|
||||
import {
|
||||
useListDistributors,
|
||||
useRemoveDistributor,
|
||||
|
|
@ -149,25 +149,13 @@ function DistributorsIndexPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-5 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[200px] max-w-[400px] px-3.5 py-2 bg-white rounded-md border border-slate-200 transition-all duration-200 focus-within:border-blue-600 focus-within:shadow-[0_0_0_3px_rgba(37,99,235,0.1)]">
|
||||
<Search className="w-4 h-4 text-slate-600 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
<SearchToolbar
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={setSearchQuery}
|
||||
placeholder="Search distributors..."
|
||||
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400"
|
||||
addLink="/distributors/add"
|
||||
addLabel="Add Distributor"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to="/distributors/add"
|
||||
className={buttonVariants({ variant: "primary" })}
|
||||
>
|
||||
<Plus className="w-[15px] h-[15px]" />
|
||||
Add Distributor
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<GridTable
|
||||
columns={columns}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { ArrowLeft, Pencil, Trash2, Pill, Package, Layers, DollarSign, EyeOff } from "lucide-react";
|
||||
import { Button } from "#/components/ui";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Pencil, Trash2, Pill, Package, Layers, DollarSign, EyeOff } from "lucide-react";
|
||||
import { Button, DetailRow, BackLink, EmptyState } from "#/components/ui";
|
||||
import { useGetProductById, useRemoveProduct, trpc } from "shared-react";
|
||||
|
||||
export const Route = createFileRoute("/products/$id")({
|
||||
|
|
@ -38,17 +38,13 @@ function ProductDetailsPage() {
|
|||
|
||||
if (error || !product) {
|
||||
return (
|
||||
<div className="flex flex-col items-center py-16 px-6 text-slate-600">
|
||||
<Pill className="w-12 h-12 mb-4 opacity-40" />
|
||||
<h3 className="text-base font-semibold text-slate-900 mb-1.5">Product not found</h3>
|
||||
<p className="text-sm mb-4">The product you're looking for doesn't exist.</p>
|
||||
<Link
|
||||
to="/products"
|
||||
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Back to Products
|
||||
</Link>
|
||||
</div>
|
||||
<EmptyState
|
||||
icon={Pill}
|
||||
title="Product not found"
|
||||
description="The product you're looking for doesn't exist."
|
||||
actionLabel="Back to Products"
|
||||
actionTo="/products"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -64,16 +60,9 @@ function ProductDetailsPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
to="/products"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-blue-600 hover:underline mb-5"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Back to Products
|
||||
</Link>
|
||||
<BackLink to="/products" label="Products" />
|
||||
|
||||
<div className="grid grid-cols-2 gap-6 max-w-[960px]">
|
||||
{/* Basic Info */}
|
||||
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] overflow-hidden">
|
||||
<div className="px-5 py-4 border-b border-slate-200 flex items-center gap-2">
|
||||
<Pill className="w-[14px] h-[14px] text-slate-600" />
|
||||
|
|
@ -89,7 +78,6 @@ function ProductDetailsPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Inventory */}
|
||||
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] overflow-hidden">
|
||||
<div className="px-5 py-4 border-b border-slate-200 flex items-center gap-2">
|
||||
<Package className="w-[14px] h-[14px] text-slate-600" />
|
||||
|
|
@ -101,7 +89,6 @@ function ProductDetailsPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Composition */}
|
||||
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] overflow-hidden">
|
||||
<div className="px-5 py-4 border-b border-slate-200 flex items-center gap-2">
|
||||
<Layers className="w-[14px] h-[14px] text-slate-600" />
|
||||
|
|
@ -126,7 +113,6 @@ function ProductDetailsPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pricing */}
|
||||
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] overflow-hidden">
|
||||
<div className="px-5 py-4 border-b border-slate-200 flex items-center gap-2">
|
||||
<DollarSign className="w-[14px] h-[14px] text-slate-600" />
|
||||
|
|
@ -140,7 +126,6 @@ function ProductDetailsPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visibility */}
|
||||
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] overflow-hidden col-span-full">
|
||||
<div className="px-5 py-4 border-b border-slate-200 flex items-center gap-2">
|
||||
<EyeOff className="w-[14px] h-[14px] text-slate-600" />
|
||||
|
|
@ -153,7 +138,6 @@ function ProductDetailsPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2.5 py-5 mt-2 max-w-[960px]">
|
||||
<Button variant="primary">
|
||||
<Pencil className="w-[15px] h-[15px]" />
|
||||
|
|
@ -167,22 +151,3 @@ function ProductDetailsPage() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({
|
||||
label,
|
||||
value,
|
||||
valueClass = "",
|
||||
last = false,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
valueClass?: string;
|
||||
last?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={`flex justify-between items-center py-2.5 text-sm ${last ? "" : "border-b border-slate-200"}`}>
|
||||
<span className="text-slate-600 text-[13px]">{label}</span>
|
||||
<span className={`font-medium text-right ${valueClass}`}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useState, useMemo, useCallback } from "react";
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { Search, Plus, Pencil, Trash2, Pill } from "lucide-react";
|
||||
import { Pencil, Trash2, Pill } from "lucide-react";
|
||||
import { GridTable } from "#/components/GridTable";
|
||||
import type { GridTableColumn } from "#/components/GridTable";
|
||||
import { Button, buttonVariants } from "#/components/ui";
|
||||
import { Button, SearchToolbar } from "#/components/ui";
|
||||
import { useListProducts, useRemoveProduct, trpc } from "shared-react";
|
||||
|
||||
interface ProductRow {
|
||||
|
|
@ -200,25 +200,13 @@ function ProductsIndexPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-5 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[200px] max-w-[480px] px-3.5 py-2 bg-white rounded-md border border-slate-200 transition-all duration-200 focus-within:border-blue-600 focus-within:shadow-[0_0_0_3px_rgba(37,99,235,0.1)]">
|
||||
<Search className="w-4 h-4 text-slate-600 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
<SearchToolbar
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={setSearchQuery}
|
||||
placeholder="Search by product name, brand, or distributor..."
|
||||
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400"
|
||||
addLink="/products/add"
|
||||
addLabel="Add Product"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to="/products/add"
|
||||
className={buttonVariants({ variant: "primary" })}
|
||||
>
|
||||
<Plus className="w-[15px] h-[15px]" />
|
||||
Add Product
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<GridTable
|
||||
columns={columns}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { ArrowLeft, Pencil, Trash2, Package, Calendar, MapPin, Truck } from "lucide-react";
|
||||
import { Button } from "#/components/ui";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Pencil, Trash2, Package, Calendar, MapPin, Truck } from "lucide-react";
|
||||
import { Button, DetailRow, BackLink, EmptyState } from "#/components/ui";
|
||||
import { useGetStockBatchById, useRemoveStockBatch, trpc } from "shared-react";
|
||||
|
||||
function daysUntil(expiry: string): number {
|
||||
|
|
@ -47,14 +47,13 @@ function StockDetailsPage() {
|
|||
if (isLoading) return <div className="text-sm text-slate-600 py-8">Loading batch details...</div>;
|
||||
if (error || !batch) {
|
||||
return (
|
||||
<div className="flex flex-col items-center py-16 px-6 text-slate-600">
|
||||
<Package className="w-12 h-12 mb-4 opacity-40" />
|
||||
<h3 className="text-base font-semibold text-slate-900 mb-1.5">Batch not found</h3>
|
||||
<p className="text-sm mb-4">The batch you're looking for doesn't exist.</p>
|
||||
<Link to="/stock" className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors">
|
||||
Back to Stock
|
||||
</Link>
|
||||
</div>
|
||||
<EmptyState
|
||||
icon={Package}
|
||||
title="Batch not found"
|
||||
description="The batch you're looking for doesn't exist."
|
||||
actionLabel="Back to Stock"
|
||||
actionTo="/stock"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -66,10 +65,7 @@ function StockDetailsPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<Link to="/stock" className="inline-flex items-center gap-1.5 text-sm text-blue-600 hover:underline mb-5">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Back to Stock
|
||||
</Link>
|
||||
<BackLink to="/stock" label="Stock" />
|
||||
|
||||
<div className="grid grid-cols-2 gap-6 max-w-[800px]">
|
||||
<div className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] overflow-hidden">
|
||||
|
|
@ -146,12 +142,3 @@ function StockDetailsPage() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({ label, value, valueClass = "", last = false }: { label: string; value: string; valueClass?: string; last?: boolean }) {
|
||||
return (
|
||||
<div className={`flex justify-between items-center py-2.5 text-sm ${last ? "" : "border-b border-slate-200"}`}>
|
||||
<span className="text-slate-600 text-[13px]">{label}</span>
|
||||
<span className={`font-medium text-right ${valueClass}`}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useState, useMemo, useCallback } from "react";
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { Search, Plus, Pencil, Trash2, Package, Star } from "lucide-react";
|
||||
import { Pencil, Trash2, Package, Star } from "lucide-react";
|
||||
import { GridTable } from "#/components/GridTable";
|
||||
import type { GridTableColumn } from "#/components/GridTable";
|
||||
import { Button, buttonVariants } from "#/components/ui";
|
||||
import { Button, SearchToolbar } from "#/components/ui";
|
||||
import { useListStockBatches, useRemoveStockBatch, trpc } from "shared-react";
|
||||
|
||||
interface StockRow {
|
||||
|
|
@ -186,22 +186,13 @@ function StockIndexPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-5 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[200px] max-w-[480px] px-3.5 py-2 bg-white rounded-md border border-slate-200 transition-all duration-200 focus-within:border-blue-600 focus-within:shadow-[0_0_0_3px_rgba(37,99,235,0.1)]">
|
||||
<Search className="w-4 h-4 text-slate-600 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
<SearchToolbar
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={setSearchQuery}
|
||||
placeholder="Search by product, brand, batch, or rack..."
|
||||
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400"
|
||||
addLink="/stock/add"
|
||||
addLabel="Add Stock"
|
||||
/>
|
||||
</div>
|
||||
<Link to="/stock/add" className={buttonVariants({ variant: "primary" })}>
|
||||
<Plus className="w-[15px] h-[15px]" />
|
||||
Add Stock
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<GridTable
|
||||
columns={columns}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useState, useMemo, useCallback } from "react";
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { Search, Plus, Pencil, Trash2, Warehouse } from "lucide-react";
|
||||
import { Pencil, Trash2, Warehouse } from "lucide-react";
|
||||
import { GridTable } from "#/components/GridTable";
|
||||
import type { GridTableColumn } from "#/components/GridTable";
|
||||
import { Button, buttonVariants } from "#/components/ui";
|
||||
import { Button, SearchToolbar } from "#/components/ui";
|
||||
import { useListStorage, useRemoveStorage, trpc } from "shared-react";
|
||||
|
||||
interface Rack {
|
||||
|
|
@ -141,25 +141,13 @@ function StorageIndexPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-5 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[200px] max-w-[400px] px-3.5 py-2 bg-white rounded-md border border-slate-200 transition-all duration-200 focus-within:border-blue-600 focus-within:shadow-[0_0_0_3px_rgba(37,99,235,0.1)]">
|
||||
<Search className="w-4 h-4 text-slate-600 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
<SearchToolbar
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={setSearchQuery}
|
||||
placeholder="Search by name, alias, or description..."
|
||||
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400"
|
||||
addLink="/storage/add"
|
||||
addLabel="Add Rack"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to="/storage/add"
|
||||
className={buttonVariants({ variant: "primary" })}
|
||||
>
|
||||
<Plus className="w-[15px] h-[15px]" />
|
||||
Add Rack
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<GridTable
|
||||
columns={columns}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue