added shell screens
This commit is contained in:
parent
7c9fb6b060
commit
17f1f8e4d2
21 changed files with 667 additions and 154 deletions
1
agents.md
Normal file
1
agents.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
don't try to compile or run or build any project until explicitly asked for
|
||||||
66
apps/pharmanager/.vscode/settings.json
vendored
66
apps/pharmanager/.vscode/settings.json
vendored
|
|
@ -1,35 +1,35 @@
|
||||||
{
|
{
|
||||||
"files.watcherExclude": {
|
"files.watcherExclude": {
|
||||||
"**/routeTree.gen.ts": true
|
"**/routeTree.gen.ts": true
|
||||||
},
|
},
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/routeTree.gen.ts": true
|
"**/routeTree.gen.ts": true
|
||||||
},
|
},
|
||||||
"files.readonlyInclude": {
|
"files.readonlyInclude": {
|
||||||
"**/routeTree.gen.ts": true
|
"**/routeTree.gen.ts": true
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"[javascriptreact]": {
|
"[javascriptreact]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"[jsonc]": {
|
"[jsonc]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports.biome": "explicit"
|
"source.organizeImports.biome": "explicit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
"**/index.html",
|
"**/index.html",
|
||||||
"**/vite.config.ts",
|
"**/vite.config.ts",
|
||||||
"!**/src/routeTree.gen.ts",
|
"!**/src/routeTree.gen.ts",
|
||||||
"!**/src/styles.css"
|
"!**/src/styles.css",
|
||||||
|
"!**/inspiration"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
|
|
@ -33,4 +34,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
67
apps/pharmanager/src/components/AppLayout.tsx
Normal file
67
apps/pharmanager/src/components/AppLayout.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { useState, useEffect, type ReactNode } from "react";
|
||||||
|
import { useMatches } from "@tanstack/react-router";
|
||||||
|
import { Sidebar } from "#/components/Sidebar";
|
||||||
|
import { Header } from "#/components/Header";
|
||||||
|
|
||||||
|
interface RouteStaticData {
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AppLayoutProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppLayout({ children }: AppLayoutProps) {
|
||||||
|
const [minimized, setMinimized] = useState(false);
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|
||||||
|
const matches = useMatches();
|
||||||
|
const currentMatch = matches[matches.length - 1];
|
||||||
|
const staticData = currentMatch?.staticData as RouteStaticData | undefined;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
if (window.innerWidth > 768) {
|
||||||
|
setMobileOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mobileOpen && window.innerWidth <= 768) {
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
};
|
||||||
|
}, [mobileOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen">
|
||||||
|
<Sidebar
|
||||||
|
minimized={minimized}
|
||||||
|
mobileOpen={mobileOpen}
|
||||||
|
onToggleMinimized={() => setMinimized((prev) => !prev)}
|
||||||
|
onCloseMobile={() => setMobileOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-1 flex-col min-w-0">
|
||||||
|
<Header
|
||||||
|
pageTitle={staticData?.title ?? "Dashboard"}
|
||||||
|
pageSubtitle={
|
||||||
|
staticData?.subtitle ?? "Pharmacy overview & inventory at a glance"
|
||||||
|
}
|
||||||
|
onMobileMenuToggle={() => setMobileOpen((prev) => !prev)}
|
||||||
|
/>
|
||||||
|
<main className="flex-1 overflow-y-auto p-6 bg-slate-100">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
apps/pharmanager/src/components/Header.tsx
Normal file
54
apps/pharmanager/src/components/Header.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { Menu, Bell, Search } from "lucide-react";
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
pageTitle: string;
|
||||||
|
pageSubtitle: string;
|
||||||
|
onMobileMenuToggle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Header({
|
||||||
|
pageTitle,
|
||||||
|
pageSubtitle,
|
||||||
|
onMobileMenuToggle,
|
||||||
|
}: HeaderProps) {
|
||||||
|
return (
|
||||||
|
<header className="h-16 bg-white border-b border-slate-200 flex items-center justify-between px-6 shrink-0">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onMobileMenuToggle}
|
||||||
|
className="md:hidden w-9 h-9 flex items-center justify-center rounded-md text-slate-600 hover:bg-slate-100"
|
||||||
|
aria-label="Open menu"
|
||||||
|
>
|
||||||
|
<Menu className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-lg font-semibold text-slate-900">{pageTitle}</h1>
|
||||||
|
<p className="text-[13px] text-slate-600 mt-px">{pageSubtitle}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2 px-3 py-2 bg-slate-100 rounded-md border border-transparent transition-all duration-200 focus-within:border-blue-600 focus-within:bg-white focus-within:w-[280px] w-[220px]">
|
||||||
|
<Search className="w-3.5 h-3.5 text-slate-600 shrink-0" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search medicines..."
|
||||||
|
className="bg-transparent border-none outline-none text-sm text-slate-900 w-full placeholder:text-slate-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="relative w-9 h-9 flex items-center justify-center rounded-md text-slate-600 hover:bg-slate-100 hover:text-slate-900 transition-colors"
|
||||||
|
aria-label="Notifications"
|
||||||
|
>
|
||||||
|
<Bell className="w-[18px] h-[18px]" />
|
||||||
|
<span className="absolute top-1 right-1 w-4 h-4 rounded-full bg-red-600 text-white text-[9px] font-bold flex items-center justify-center">
|
||||||
|
3
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
159
apps/pharmanager/src/components/Sidebar.tsx
Normal file
159
apps/pharmanager/src/components/Sidebar.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
import { Link, useLocation } from "@tanstack/react-router";
|
||||||
|
import {
|
||||||
|
LayoutDashboard,
|
||||||
|
ReceiptText,
|
||||||
|
Package,
|
||||||
|
Truck,
|
||||||
|
Warehouse,
|
||||||
|
Users,
|
||||||
|
Pill,
|
||||||
|
UserRoundCog,
|
||||||
|
ChevronLeft,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { cn } from "#/lib/cn";
|
||||||
|
|
||||||
|
const NAV_ITEMS = [
|
||||||
|
{ to: "/", label: "Home", icon: LayoutDashboard },
|
||||||
|
{ to: "/billing", label: "Billing", icon: ReceiptText },
|
||||||
|
{ to: "/stock", label: "Stock", icon: Package },
|
||||||
|
{ to: "/distributors", label: "Distributors", icon: Truck },
|
||||||
|
{ to: "/storage", label: "Storage", icon: Warehouse },
|
||||||
|
{ to: "/customers", label: "Customers", icon: Users },
|
||||||
|
{ to: "/products", label: "Products", icon: Pill },
|
||||||
|
{ to: "/staff", label: "Staff", icon: UserRoundCog },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
interface SidebarProps {
|
||||||
|
minimized: boolean;
|
||||||
|
mobileOpen: boolean;
|
||||||
|
onToggleMinimized: () => void;
|
||||||
|
onCloseMobile: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Sidebar({
|
||||||
|
minimized,
|
||||||
|
mobileOpen,
|
||||||
|
onToggleMinimized,
|
||||||
|
onCloseMobile,
|
||||||
|
}: SidebarProps) {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const showLabels = !minimized || mobileOpen;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-40 bg-black/40 md:hidden transition-opacity border-0 p-0 w-full h-full cursor-default",
|
||||||
|
mobileOpen ? "opacity-100" : "opacity-0 pointer-events-none",
|
||||||
|
)}
|
||||||
|
onClick={onCloseMobile}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Escape" || e.key === "Enter") onCloseMobile();
|
||||||
|
}}
|
||||||
|
aria-label="Close sidebar"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<aside
|
||||||
|
className={cn(
|
||||||
|
"z-50 flex flex-col bg-slate-900 overflow-hidden transition-[width,transform] duration-300",
|
||||||
|
"fixed inset-y-0 left-0 md:static",
|
||||||
|
"w-60 min-w-60",
|
||||||
|
mobileOpen ? "translate-x-0" : "-translate-x-full",
|
||||||
|
"md:translate-x-0",
|
||||||
|
minimized && "md:w-16 md:min-w-16",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between h-16 px-4 shrink-0">
|
||||||
|
<div className="flex items-center gap-2.5 overflow-hidden">
|
||||||
|
<div className="w-7 h-7 rounded-md bg-blue-600 flex items-center justify-center shrink-0">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 28 28"
|
||||||
|
fill="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9 14l3.5 3.5L19 11"
|
||||||
|
stroke="#fff"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{showLabels && (
|
||||||
|
<span className="text-white font-semibold text-lg whitespace-nowrap">
|
||||||
|
Pharmanager
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onToggleMinimized}
|
||||||
|
className="hidden md:flex w-8 h-8 items-center justify-center rounded-md text-slate-400 hover:bg-slate-800 hover:text-white transition-colors shrink-0"
|
||||||
|
aria-label="Toggle sidebar"
|
||||||
|
>
|
||||||
|
<ChevronLeft
|
||||||
|
className={cn(
|
||||||
|
"w-[18px] h-[18px] transition-transform",
|
||||||
|
minimized && "rotate-180",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav className="flex-1 overflow-y-auto px-3 py-2 flex flex-col gap-0.5">
|
||||||
|
{NAV_ITEMS.map((item) => {
|
||||||
|
const isActive = location.pathname === item.to;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
onClick={onCloseMobile}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium whitespace-nowrap overflow-hidden transition-colors",
|
||||||
|
minimized &&
|
||||||
|
!(item.to === "/") &&
|
||||||
|
"md:justify-center md:px-0",
|
||||||
|
isActive
|
||||||
|
? "bg-blue-600 text-white"
|
||||||
|
: "text-slate-400 hover:bg-slate-800 hover:text-white",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<item.icon className="w-5 h-5 shrink-0" />
|
||||||
|
{showLabels && <span>{item.label}</span>}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 border-t border-white/[0.06]",
|
||||||
|
minimized && !mobileOpen ? "p-3" : "p-4",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to="/profile"
|
||||||
|
className="flex items-center gap-2.5 overflow-hidden text-slate-400 text-sm"
|
||||||
|
>
|
||||||
|
<div className="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white font-semibold text-[13px] shrink-0">
|
||||||
|
MS
|
||||||
|
</div>
|
||||||
|
{showLabels && (
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<div className="text-white font-medium whitespace-nowrap text-[13px]">
|
||||||
|
Dr. Mohammed
|
||||||
|
</div>
|
||||||
|
<div className="whitespace-nowrap text-[11px]">Pharmacist</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
apps/pharmanager/src/lib/cn.ts
Normal file
3
apps/pharmanager/src/lib/cn.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function cn(...classes: (string | false | null | undefined)[]): string {
|
||||||
|
return classes.filter(Boolean).join(" ");
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from "react-dom/client";
|
||||||
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||||
import { routeTree } from './routeTree.gen'
|
import { routeTree } from "./routeTree.gen";
|
||||||
import { TrpcProvider } from 'shared-react'
|
import { TrpcProvider } from "shared-react";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
defaultPreload: 'intent',
|
defaultPreload: "intent",
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module "@tanstack/react-router" {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: typeof router
|
router: typeof router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootElement = document.getElementById('app')!
|
const rootElement = document.getElementById("app");
|
||||||
|
if (!rootElement) throw new Error("Root element not found");
|
||||||
|
|
||||||
if (!rootElement.innerHTML) {
|
if (!rootElement.innerHTML) {
|
||||||
const root = ReactDOM.createRoot(rootElement)
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
root.render(
|
root.render(
|
||||||
<TrpcProvider baseUrl="http://localhost:3001">
|
<TrpcProvider baseUrl="http://localhost:3001">
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</TrpcProvider>,
|
</TrpcProvider>,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,56 @@
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as StorageRouteImport } from './routes/storage'
|
||||||
|
import { Route as StockRouteImport } from './routes/stock'
|
||||||
|
import { Route as StaffRouteImport } from './routes/staff'
|
||||||
|
import { Route as ProfileRouteImport } from './routes/profile'
|
||||||
|
import { Route as ProductsRouteImport } from './routes/products'
|
||||||
|
import { Route as DistributorsRouteImport } from './routes/distributors'
|
||||||
|
import { Route as CustomersRouteImport } from './routes/customers'
|
||||||
|
import { Route as BillingRouteImport } from './routes/billing'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
|
||||||
|
const StorageRoute = StorageRouteImport.update({
|
||||||
|
id: '/storage',
|
||||||
|
path: '/storage',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const StockRoute = StockRouteImport.update({
|
||||||
|
id: '/stock',
|
||||||
|
path: '/stock',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const StaffRoute = StaffRouteImport.update({
|
||||||
|
id: '/staff',
|
||||||
|
path: '/staff',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const ProfileRoute = ProfileRouteImport.update({
|
||||||
|
id: '/profile',
|
||||||
|
path: '/profile',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const ProductsRoute = ProductsRouteImport.update({
|
||||||
|
id: '/products',
|
||||||
|
path: '/products',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const DistributorsRoute = DistributorsRouteImport.update({
|
||||||
|
id: '/distributors',
|
||||||
|
path: '/distributors',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const CustomersRoute = CustomersRouteImport.update({
|
||||||
|
id: '/customers',
|
||||||
|
path: '/customers',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const BillingRoute = BillingRouteImport.update({
|
||||||
|
id: '/billing',
|
||||||
|
path: '/billing',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const IndexRoute = IndexRouteImport.update({
|
const IndexRoute = IndexRouteImport.update({
|
||||||
id: '/',
|
id: '/',
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|
@ -19,28 +67,144 @@ const IndexRoute = IndexRouteImport.update({
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/billing': typeof BillingRoute
|
||||||
|
'/customers': typeof CustomersRoute
|
||||||
|
'/distributors': typeof DistributorsRoute
|
||||||
|
'/products': typeof ProductsRoute
|
||||||
|
'/profile': typeof ProfileRoute
|
||||||
|
'/staff': typeof StaffRoute
|
||||||
|
'/stock': typeof StockRoute
|
||||||
|
'/storage': typeof StorageRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/billing': typeof BillingRoute
|
||||||
|
'/customers': typeof CustomersRoute
|
||||||
|
'/distributors': typeof DistributorsRoute
|
||||||
|
'/products': typeof ProductsRoute
|
||||||
|
'/profile': typeof ProfileRoute
|
||||||
|
'/staff': typeof StaffRoute
|
||||||
|
'/stock': typeof StockRoute
|
||||||
|
'/storage': typeof StorageRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/billing': typeof BillingRoute
|
||||||
|
'/customers': typeof CustomersRoute
|
||||||
|
'/distributors': typeof DistributorsRoute
|
||||||
|
'/products': typeof ProductsRoute
|
||||||
|
'/profile': typeof ProfileRoute
|
||||||
|
'/staff': typeof StaffRoute
|
||||||
|
'/stock': typeof StockRoute
|
||||||
|
'/storage': typeof StorageRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: '/'
|
fullPaths:
|
||||||
|
| '/'
|
||||||
|
| '/billing'
|
||||||
|
| '/customers'
|
||||||
|
| '/distributors'
|
||||||
|
| '/products'
|
||||||
|
| '/profile'
|
||||||
|
| '/staff'
|
||||||
|
| '/stock'
|
||||||
|
| '/storage'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/'
|
to:
|
||||||
id: '__root__' | '/'
|
| '/'
|
||||||
|
| '/billing'
|
||||||
|
| '/customers'
|
||||||
|
| '/distributors'
|
||||||
|
| '/products'
|
||||||
|
| '/profile'
|
||||||
|
| '/staff'
|
||||||
|
| '/stock'
|
||||||
|
| '/storage'
|
||||||
|
id:
|
||||||
|
| '__root__'
|
||||||
|
| '/'
|
||||||
|
| '/billing'
|
||||||
|
| '/customers'
|
||||||
|
| '/distributors'
|
||||||
|
| '/products'
|
||||||
|
| '/profile'
|
||||||
|
| '/staff'
|
||||||
|
| '/stock'
|
||||||
|
| '/storage'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
|
BillingRoute: typeof BillingRoute
|
||||||
|
CustomersRoute: typeof CustomersRoute
|
||||||
|
DistributorsRoute: typeof DistributorsRoute
|
||||||
|
ProductsRoute: typeof ProductsRoute
|
||||||
|
ProfileRoute: typeof ProfileRoute
|
||||||
|
StaffRoute: typeof StaffRoute
|
||||||
|
StockRoute: typeof StockRoute
|
||||||
|
StorageRoute: typeof StorageRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
|
'/storage': {
|
||||||
|
id: '/storage'
|
||||||
|
path: '/storage'
|
||||||
|
fullPath: '/storage'
|
||||||
|
preLoaderRoute: typeof StorageRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/stock': {
|
||||||
|
id: '/stock'
|
||||||
|
path: '/stock'
|
||||||
|
fullPath: '/stock'
|
||||||
|
preLoaderRoute: typeof StockRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/staff': {
|
||||||
|
id: '/staff'
|
||||||
|
path: '/staff'
|
||||||
|
fullPath: '/staff'
|
||||||
|
preLoaderRoute: typeof StaffRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/profile': {
|
||||||
|
id: '/profile'
|
||||||
|
path: '/profile'
|
||||||
|
fullPath: '/profile'
|
||||||
|
preLoaderRoute: typeof ProfileRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/products': {
|
||||||
|
id: '/products'
|
||||||
|
path: '/products'
|
||||||
|
fullPath: '/products'
|
||||||
|
preLoaderRoute: typeof ProductsRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/distributors': {
|
||||||
|
id: '/distributors'
|
||||||
|
path: '/distributors'
|
||||||
|
fullPath: '/distributors'
|
||||||
|
preLoaderRoute: typeof DistributorsRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/customers': {
|
||||||
|
id: '/customers'
|
||||||
|
path: '/customers'
|
||||||
|
fullPath: '/customers'
|
||||||
|
preLoaderRoute: typeof CustomersRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/billing': {
|
||||||
|
id: '/billing'
|
||||||
|
path: '/billing'
|
||||||
|
fullPath: '/billing'
|
||||||
|
preLoaderRoute: typeof BillingRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/': {
|
'/': {
|
||||||
id: '/'
|
id: '/'
|
||||||
path: '/'
|
path: '/'
|
||||||
|
|
@ -53,6 +217,14 @@ declare module '@tanstack/react-router' {
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
|
BillingRoute: BillingRoute,
|
||||||
|
CustomersRoute: CustomersRoute,
|
||||||
|
DistributorsRoute: DistributorsRoute,
|
||||||
|
ProductsRoute: ProductsRoute,
|
||||||
|
ProfileRoute: ProfileRoute,
|
||||||
|
StaffRoute: StaffRoute,
|
||||||
|
StockRoute: StockRoute,
|
||||||
|
StorageRoute: StorageRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
|
|
||||||
import { routeTree } from './routeTree.gen'
|
|
||||||
|
|
||||||
export function getRouter() {
|
|
||||||
const router = createTanStackRouter({
|
|
||||||
routeTree,
|
|
||||||
scrollRestoration: true,
|
|
||||||
defaultPreload: 'intent',
|
|
||||||
defaultPreloadStaleTime: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface Register {
|
|
||||||
router: ReturnType<typeof getRouter>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +1,31 @@
|
||||||
import { Outlet, createRootRoute } from '@tanstack/react-router'
|
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
||||||
import { TanStackDevtools } from '@tanstack/react-devtools'
|
import { TanStackDevtools } from "@tanstack/react-devtools";
|
||||||
|
import { AppLayout } from "#/components/AppLayout";
|
||||||
|
|
||||||
import '../styles.css'
|
import "../styles.css";
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootComponent,
|
component: RootComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RootComponent() {
|
function RootComponent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Outlet />
|
<AppLayout>
|
||||||
<TanStackDevtools
|
<Outlet />
|
||||||
config={{
|
</AppLayout>
|
||||||
position: 'bottom-right',
|
<TanStackDevtools
|
||||||
}}
|
config={{
|
||||||
plugins={[
|
position: "bottom-right",
|
||||||
{
|
}}
|
||||||
name: 'TanStack Router',
|
plugins={[
|
||||||
render: <TanStackRouterDevtoolsPanel />,
|
{
|
||||||
},
|
name: "TanStack Router",
|
||||||
]}
|
render: <TanStackRouterDevtoolsPanel />,
|
||||||
/>
|
},
|
||||||
</>
|
]}
|
||||||
)
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
apps/pharmanager/src/routes/billing.tsx
Normal file
13
apps/pharmanager/src/routes/billing.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/billing")({
|
||||||
|
component: BillingPage,
|
||||||
|
staticData: {
|
||||||
|
title: "Billing",
|
||||||
|
subtitle: "Invoices, payments, revenue tracking",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function BillingPage() {
|
||||||
|
return <div>Billing</div>;
|
||||||
|
}
|
||||||
13
apps/pharmanager/src/routes/customers.tsx
Normal file
13
apps/pharmanager/src/routes/customers.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/customers")({
|
||||||
|
component: CustomersPage,
|
||||||
|
staticData: {
|
||||||
|
title: "Customers",
|
||||||
|
subtitle: "Customer management with purchase history",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function CustomersPage() {
|
||||||
|
return <div>Customers</div>;
|
||||||
|
}
|
||||||
13
apps/pharmanager/src/routes/distributors.tsx
Normal file
13
apps/pharmanager/src/routes/distributors.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/distributors")({
|
||||||
|
component: DistributorsPage,
|
||||||
|
staticData: {
|
||||||
|
title: "Distributors",
|
||||||
|
subtitle: "Agency management with contact details",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function DistributorsPage() {
|
||||||
|
return <div>Distributors</div>;
|
||||||
|
}
|
||||||
|
|
@ -1,45 +1,13 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import type { Person } from '@repo/shared'
|
|
||||||
import { useCounterStore, useGetStorageSpaces } from 'shared-react'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({ component: Home })
|
export const Route = createFileRoute("/")({
|
||||||
|
component: HomePage,
|
||||||
|
staticData: {
|
||||||
|
title: "Dashboard",
|
||||||
|
subtitle: "Pharmacy overview & inventory at a glance",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function Home() {
|
function HomePage() {
|
||||||
const shafi:Person = {age: 32, name: 'Shafi'}
|
return <div>Home</div>;
|
||||||
const count = useCounterStore((s) => s.count)
|
|
||||||
const inc = useCounterStore((s) => s.inc)
|
|
||||||
const spaces = useGetStorageSpaces()
|
|
||||||
return (
|
|
||||||
<div className="p-8">
|
|
||||||
<h1 className="text-4xl font-bold">Welcome to TanStack Start</h1>
|
|
||||||
<p className="mt-4 text-lg">
|
|
||||||
{shafi.name} is {shafi.age} years old.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="mt-6 rounded bg-black px-4 py-2 text-white"
|
|
||||||
onClick={inc}
|
|
||||||
>
|
|
||||||
Count: {count}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="mt-8">
|
|
||||||
<h2 className="text-2xl font-semibold">Storage Spaces</h2>
|
|
||||||
{spaces.isLoading ? (
|
|
||||||
<p className="mt-2">Loading…</p>
|
|
||||||
) : spaces.error ? (
|
|
||||||
<p className="mt-2 text-red-600">Failed to load storage spaces</p>
|
|
||||||
) : (
|
|
||||||
<ul className="mt-2 list-disc pl-5">
|
|
||||||
{spaces.data?.map((s) => (
|
|
||||||
<li key={s.id}>
|
|
||||||
{s.name}
|
|
||||||
{s.description ? `: ${s.description}` : ''}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
apps/pharmanager/src/routes/products.tsx
Normal file
13
apps/pharmanager/src/routes/products.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/products")({
|
||||||
|
component: ProductsPage,
|
||||||
|
staticData: {
|
||||||
|
title: "Products",
|
||||||
|
subtitle: "Medicine catalog with search, add, edit, delete",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function ProductsPage() {
|
||||||
|
return <div>Products</div>;
|
||||||
|
}
|
||||||
13
apps/pharmanager/src/routes/profile.tsx
Normal file
13
apps/pharmanager/src/routes/profile.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/profile")({
|
||||||
|
component: ProfilePage,
|
||||||
|
staticData: {
|
||||||
|
title: "Profile",
|
||||||
|
subtitle: "Your profile information",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function ProfilePage() {
|
||||||
|
return <div>Profile</div>;
|
||||||
|
}
|
||||||
13
apps/pharmanager/src/routes/staff.tsx
Normal file
13
apps/pharmanager/src/routes/staff.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/staff")({
|
||||||
|
component: StaffPage,
|
||||||
|
staticData: {
|
||||||
|
title: "Staff",
|
||||||
|
subtitle: "Staff management with roles & permissions",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function StaffPage() {
|
||||||
|
return <div>Staff</div>;
|
||||||
|
}
|
||||||
13
apps/pharmanager/src/routes/stock.tsx
Normal file
13
apps/pharmanager/src/routes/stock.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/stock")({
|
||||||
|
component: StockPage,
|
||||||
|
staticData: {
|
||||||
|
title: "Stock",
|
||||||
|
subtitle: "Inventory levels, low-stock alerts, purchase orders",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function StockPage() {
|
||||||
|
return <div>Stock</div>;
|
||||||
|
}
|
||||||
13
apps/pharmanager/src/routes/storage.tsx
Normal file
13
apps/pharmanager/src/routes/storage.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/storage")({
|
||||||
|
component: StoragePage,
|
||||||
|
staticData: {
|
||||||
|
title: "Storage",
|
||||||
|
subtitle: "Rack & shelf management with image records",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function StoragePage() {
|
||||||
|
return <div>Storage</div>;
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import { devtools } from '@tanstack/devtools-vite'
|
import { devtools } from "@tanstack/devtools-vite";
|
||||||
|
|
||||||
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
||||||
|
|
||||||
import viteReact from '@vitejs/plugin-react'
|
import viteReact from "@vitejs/plugin-react";
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
const config = defineConfig({
|
const config = defineConfig({
|
||||||
resolve: { tsconfigPaths: true },
|
resolve: { tsconfigPaths: true },
|
||||||
plugins: [
|
plugins: [
|
||||||
devtools(),
|
devtools(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
|
tanstackRouter({ target: "react", autoCodeSplitting: true }),
|
||||||
viteReact(),
|
viteReact(),
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
export default config
|
export default config;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue