diff --git a/apps/pharmanager/src/components/ui/BackLink.tsx b/apps/pharmanager/src/components/ui/BackLink.tsx new file mode 100644 index 0000000..fddbcf5 --- /dev/null +++ b/apps/pharmanager/src/components/ui/BackLink.tsx @@ -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 ( + + + Back to {label} + + ); +} diff --git a/apps/pharmanager/src/components/ui/DetailRow.tsx b/apps/pharmanager/src/components/ui/DetailRow.tsx new file mode 100644 index 0000000..f7b84a5 --- /dev/null +++ b/apps/pharmanager/src/components/ui/DetailRow.tsx @@ -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 ( +
+ {label} + + {value} + +
+ ); +} diff --git a/apps/pharmanager/src/components/ui/EmptyState.tsx b/apps/pharmanager/src/components/ui/EmptyState.tsx new file mode 100644 index 0000000..11c43df --- /dev/null +++ b/apps/pharmanager/src/components/ui/EmptyState.tsx @@ -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 ( +
+ +

+ {title} +

+

{description}

+ {actionLabel && actionTo && ( + + {actionLabel} + + )} +
+ ); +} diff --git a/apps/pharmanager/src/components/ui/FormField.tsx b/apps/pharmanager/src/components/ui/FormField.tsx new file mode 100644 index 0000000..290572f --- /dev/null +++ b/apps/pharmanager/src/components/ui/FormField.tsx @@ -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 ( +
+ + {children} + {error &&

{error}

} +
+ ); +} diff --git a/apps/pharmanager/src/components/ui/SearchToolbar.tsx b/apps/pharmanager/src/components/ui/SearchToolbar.tsx new file mode 100644 index 0000000..87fc66b --- /dev/null +++ b/apps/pharmanager/src/components/ui/SearchToolbar.tsx @@ -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 ( +
+
+ + onChange(e.target.value)} + placeholder={placeholder} + className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400" + /> +
+ + + {addLabel} + +
+ ); +} diff --git a/apps/pharmanager/src/components/ui/index.ts b/apps/pharmanager/src/components/ui/index.ts index 3ce292d..2e1f09c 100644 --- a/apps/pharmanager/src/components/ui/index.ts +++ b/apps/pharmanager/src/components/ui/index.ts @@ -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"; diff --git a/apps/pharmanager/src/routes/distributors/index.tsx b/apps/pharmanager/src/routes/distributors/index.tsx index 247172f..d750062 100644 --- a/apps/pharmanager/src/routes/distributors/index.tsx +++ b/apps/pharmanager/src/routes/distributors/index.tsx @@ -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 (
-
-
- - setSearchQuery(e.target.value)} - placeholder="Search distributors..." - className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-400" - /> -
- - - Add Distributor - -
+ - -

Product not found

-

The product you're looking for doesn't exist.

- - Back to Products - -
+ ); } @@ -64,16 +60,9 @@ function ProductDetailsPage() { return (
- - - Back to Products - +
- {/* Basic Info */}
@@ -89,7 +78,6 @@ function ProductDetailsPage() {
- {/* Inventory */}
@@ -101,7 +89,6 @@ function ProductDetailsPage() {
- {/* Composition */}
@@ -126,7 +113,6 @@ function ProductDetailsPage() {
- {/* Pricing */}
@@ -140,7 +126,6 @@ function ProductDetailsPage() {
- {/* Visibility */}
@@ -153,7 +138,6 @@ function ProductDetailsPage() {
- {/* Actions */}
); } - -function DetailRow({ - label, - value, - valueClass = "", - last = false, -}: { - label: string; - value: string; - valueClass?: string; - last?: boolean; -}) { - return ( -
- {label} - {value} -
- ); -} diff --git a/apps/pharmanager/src/routes/products/index.tsx b/apps/pharmanager/src/routes/products/index.tsx index 92004a4..302111b 100644 --- a/apps/pharmanager/src/routes/products/index.tsx +++ b/apps/pharmanager/src/routes/products/index.tsx @@ -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 (
-
-
- - setSearchQuery(e.target.value)} - 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" - /> -
- - - Add Product - -
+ Loading batch details...
; if (error || !batch) { return ( -
- -

Batch not found

-

The batch you're looking for doesn't exist.

- - Back to Stock - -
+ ); } @@ -66,10 +65,7 @@ function StockDetailsPage() { return (
- - - Back to Stock - +
@@ -146,12 +142,3 @@ function StockDetailsPage() {
); } - -function DetailRow({ label, value, valueClass = "", last = false }: { label: string; value: string; valueClass?: string; last?: boolean }) { - return ( -
- {label} - {value} -
- ); -} diff --git a/apps/pharmanager/src/routes/stock/index.tsx b/apps/pharmanager/src/routes/stock/index.tsx index 14a3710..4382186 100644 --- a/apps/pharmanager/src/routes/stock/index.tsx +++ b/apps/pharmanager/src/routes/stock/index.tsx @@ -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 (
-
-
- - setSearchQuery(e.target.value)} - 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" - /> -
- - - Add Stock - -
+ -
-
- - setSearchQuery(e.target.value)} - 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" - /> -
- - - Add Rack - -
+