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
|
||||
|
|
@ -13,7 +13,8 @@
|
|||
"**/index.html",
|
||||
"**/vite.config.ts",
|
||||
"!**/src/routeTree.gen.ts",
|
||||
"!**/src/styles.css"
|
||||
"!**/src/styles.css",
|
||||
"!**/inspiration"
|
||||
]
|
||||
},
|
||||
"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 { RouterProvider, createRouter } from '@tanstack/react-router'
|
||||
import { routeTree } from './routeTree.gen'
|
||||
import { TrpcProvider } from 'shared-react'
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import { TrpcProvider } from "shared-react";
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
defaultPreload: 'intent',
|
||||
defaultPreload: "intent",
|
||||
scrollRestoration: true,
|
||||
})
|
||||
});
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
declare module "@tanstack/react-router" {
|
||||
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) {
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<TrpcProvider baseUrl="http://localhost:3001">
|
||||
<RouterProvider router={router} />
|
||||
</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.
|
||||
|
||||
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'
|
||||
|
||||
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({
|
||||
id: '/',
|
||||
path: '/',
|
||||
|
|
@ -19,28 +67,144 @@ const IndexRoute = IndexRouteImport.update({
|
|||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': 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 {
|
||||
'/': 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 {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': 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 {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/'
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/billing'
|
||||
| '/customers'
|
||||
| '/distributors'
|
||||
| '/products'
|
||||
| '/profile'
|
||||
| '/staff'
|
||||
| '/stock'
|
||||
| '/storage'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/'
|
||||
id: '__root__' | '/'
|
||||
to:
|
||||
| '/'
|
||||
| '/billing'
|
||||
| '/customers'
|
||||
| '/distributors'
|
||||
| '/products'
|
||||
| '/profile'
|
||||
| '/staff'
|
||||
| '/stock'
|
||||
| '/storage'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/billing'
|
||||
| '/customers'
|
||||
| '/distributors'
|
||||
| '/products'
|
||||
| '/profile'
|
||||
| '/staff'
|
||||
| '/stock'
|
||||
| '/storage'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
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' {
|
||||
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: '/'
|
||||
path: '/'
|
||||
|
|
@ -53,6 +217,14 @@ declare module '@tanstack/react-router' {
|
|||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
BillingRoute: BillingRoute,
|
||||
CustomersRoute: CustomersRoute,
|
||||
DistributorsRoute: DistributorsRoute,
|
||||
ProductsRoute: ProductsRoute,
|
||||
ProfileRoute: ProfileRoute,
|
||||
StaffRoute: StaffRoute,
|
||||
StockRoute: StockRoute,
|
||||
StorageRoute: StorageRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._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 { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
||||
import { TanStackDevtools } from '@tanstack/react-devtools'
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
||||
import { TanStackDevtools } from "@tanstack/react-devtools";
|
||||
import { AppLayout } from "#/components/AppLayout";
|
||||
|
||||
import '../styles.css'
|
||||
import "../styles.css";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootComponent,
|
||||
})
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
<AppLayout>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
<TanStackDevtools
|
||||
config={{
|
||||
position: 'bottom-right',
|
||||
position: "bottom-right",
|
||||
}}
|
||||
plugins={[
|
||||
{
|
||||
name: 'TanStack Router',
|
||||
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 type { Person } from '@repo/shared'
|
||||
import { useCounterStore, useGetStorageSpaces } from 'shared-react'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute('/')({ component: Home })
|
||||
export const Route = createFileRoute("/")({
|
||||
component: HomePage,
|
||||
staticData: {
|
||||
title: "Dashboard",
|
||||
subtitle: "Pharmacy overview & inventory at a glance",
|
||||
},
|
||||
});
|
||||
|
||||
function Home() {
|
||||
const shafi:Person = {age: 32, name: 'Shafi'}
|
||||
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>
|
||||
)
|
||||
function HomePage() {
|
||||
return <div>Home</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 { devtools } from '@tanstack/devtools-vite'
|
||||
import { defineConfig } from "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 tailwindcss from '@tailwindcss/vite'
|
||||
import viteReact from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
const config = defineConfig({
|
||||
resolve: { tsconfigPaths: true },
|
||||
plugins: [
|
||||
devtools(),
|
||||
tailwindcss(),
|
||||
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
|
||||
tanstackRouter({ target: "react", autoCodeSplitting: true }),
|
||||
viteReact(),
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
export default config
|
||||
export default config;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue