diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..cc18bb5 --- /dev/null +++ b/agents.md @@ -0,0 +1 @@ +don't try to compile or run or build any project until explicitly asked for \ No newline at end of file diff --git a/apps/pharmanager/.vscode/settings.json b/apps/pharmanager/.vscode/settings.json index 70dd163..b001961 100644 --- a/apps/pharmanager/.vscode/settings.json +++ b/apps/pharmanager/.vscode/settings.json @@ -1,35 +1,35 @@ { - "files.watcherExclude": { - "**/routeTree.gen.ts": true - }, - "search.exclude": { - "**/routeTree.gen.ts": true - }, - "files.readonlyInclude": { - "**/routeTree.gen.ts": true - }, - "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[json]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[jsonc]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[css]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "editor.codeActionsOnSave": { - "source.organizeImports.biome": "explicit" - } + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[css]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "explicit" + } } diff --git a/apps/pharmanager/biome.json b/apps/pharmanager/biome.json index 64b4d31..10a58e3 100644 --- a/apps/pharmanager/biome.json +++ b/apps/pharmanager/biome.json @@ -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 @@ } } } - diff --git a/apps/pharmanager/src/components/AppLayout.tsx b/apps/pharmanager/src/components/AppLayout.tsx new file mode 100644 index 0000000..4df27ba --- /dev/null +++ b/apps/pharmanager/src/components/AppLayout.tsx @@ -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 ( +
+ setMinimized((prev) => !prev)} + onCloseMobile={() => setMobileOpen(false)} + /> + +
+
setMobileOpen((prev) => !prev)} + /> +
+ {children} +
+
+
+ ); +} diff --git a/apps/pharmanager/src/components/Header.tsx b/apps/pharmanager/src/components/Header.tsx new file mode 100644 index 0000000..73b5104 --- /dev/null +++ b/apps/pharmanager/src/components/Header.tsx @@ -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 ( +
+
+ +
+

{pageTitle}

+

{pageSubtitle}

+
+
+ +
+
+ + +
+ + +
+
+ ); +} diff --git a/apps/pharmanager/src/components/Sidebar.tsx b/apps/pharmanager/src/components/Sidebar.tsx new file mode 100644 index 0000000..ff10996 --- /dev/null +++ b/apps/pharmanager/src/components/Sidebar.tsx @@ -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 ( + <> + + + + + +
+ +
+ MS +
+ {showLabels && ( +
+
+ Dr. Mohammed +
+
Pharmacist
+
+ )} + +
+ + + ); +} diff --git a/apps/pharmanager/src/lib/cn.ts b/apps/pharmanager/src/lib/cn.ts new file mode 100644 index 0000000..11b0739 --- /dev/null +++ b/apps/pharmanager/src/lib/cn.ts @@ -0,0 +1,3 @@ +export function cn(...classes: (string | false | null | undefined)[]): string { + return classes.filter(Boolean).join(" "); +} diff --git a/apps/pharmanager/src/main.tsx b/apps/pharmanager/src/main.tsx index 05e4b5d..22265f0 100644 --- a/apps/pharmanager/src/main.tsx +++ b/apps/pharmanager/src/main.tsx @@ -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', - scrollRestoration: true, -}) + routeTree, + defaultPreload: "intent", + scrollRestoration: true, +}); -declare module '@tanstack/react-router' { - interface Register { - router: typeof router - } +declare module "@tanstack/react-router" { + interface Register { + 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) - root.render( - - - , - ) + const root = ReactDOM.createRoot(rootElement); + root.render( + + + , + ); } diff --git a/apps/pharmanager/src/routeTree.gen.ts b/apps/pharmanager/src/routeTree.gen.ts index d204c26..1f78345 100644 --- a/apps/pharmanager/src/routeTree.gen.ts +++ b/apps/pharmanager/src/routeTree.gen.ts @@ -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) diff --git a/apps/pharmanager/src/router.tsx b/apps/pharmanager/src/router.tsx deleted file mode 100644 index e7b1c4d..0000000 --- a/apps/pharmanager/src/router.tsx +++ /dev/null @@ -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 - } -} diff --git a/apps/pharmanager/src/routes/__root.tsx b/apps/pharmanager/src/routes/__root.tsx index 0a621f9..6d2abf6 100644 --- a/apps/pharmanager/src/routes/__root.tsx +++ b/apps/pharmanager/src/routes/__root.tsx @@ -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, -}) + component: RootComponent, +}); function RootComponent() { - return ( - <> - - , - }, - ]} - /> - - ) + return ( + <> + + + + , + }, + ]} + /> + + ); } diff --git a/apps/pharmanager/src/routes/billing.tsx b/apps/pharmanager/src/routes/billing.tsx new file mode 100644 index 0000000..27bb4c6 --- /dev/null +++ b/apps/pharmanager/src/routes/billing.tsx @@ -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
Billing
; +} diff --git a/apps/pharmanager/src/routes/customers.tsx b/apps/pharmanager/src/routes/customers.tsx new file mode 100644 index 0000000..c689d72 --- /dev/null +++ b/apps/pharmanager/src/routes/customers.tsx @@ -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
Customers
; +} diff --git a/apps/pharmanager/src/routes/distributors.tsx b/apps/pharmanager/src/routes/distributors.tsx new file mode 100644 index 0000000..8b054a7 --- /dev/null +++ b/apps/pharmanager/src/routes/distributors.tsx @@ -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
Distributors
; +} diff --git a/apps/pharmanager/src/routes/index.tsx b/apps/pharmanager/src/routes/index.tsx index ee6dab1..a96e587 100644 --- a/apps/pharmanager/src/routes/index.tsx +++ b/apps/pharmanager/src/routes/index.tsx @@ -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 ( -
-

Welcome to TanStack Start

-

- {shafi.name} is {shafi.age} years old. -

- - -
-

Storage Spaces

- {spaces.isLoading ? ( -

Loading…

- ) : spaces.error ? ( -

Failed to load storage spaces

- ) : ( -
    - {spaces.data?.map((s) => ( -
  • - {s.name} - {s.description ? `: ${s.description}` : ''} -
  • - ))} -
- )} -
-
- ) +function HomePage() { + return
Home
; } diff --git a/apps/pharmanager/src/routes/products.tsx b/apps/pharmanager/src/routes/products.tsx new file mode 100644 index 0000000..1c76f61 --- /dev/null +++ b/apps/pharmanager/src/routes/products.tsx @@ -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
Products
; +} diff --git a/apps/pharmanager/src/routes/profile.tsx b/apps/pharmanager/src/routes/profile.tsx new file mode 100644 index 0000000..c34b5f0 --- /dev/null +++ b/apps/pharmanager/src/routes/profile.tsx @@ -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
Profile
; +} diff --git a/apps/pharmanager/src/routes/staff.tsx b/apps/pharmanager/src/routes/staff.tsx new file mode 100644 index 0000000..2be9927 --- /dev/null +++ b/apps/pharmanager/src/routes/staff.tsx @@ -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
Staff
; +} diff --git a/apps/pharmanager/src/routes/stock.tsx b/apps/pharmanager/src/routes/stock.tsx new file mode 100644 index 0000000..00973d6 --- /dev/null +++ b/apps/pharmanager/src/routes/stock.tsx @@ -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
Stock
; +} diff --git a/apps/pharmanager/src/routes/storage.tsx b/apps/pharmanager/src/routes/storage.tsx new file mode 100644 index 0000000..1972d79 --- /dev/null +++ b/apps/pharmanager/src/routes/storage.tsx @@ -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
Storage
; +} diff --git a/apps/pharmanager/vite.config.ts b/apps/pharmanager/vite.config.ts index 8468697..1afd9c5 100644 --- a/apps/pharmanager/vite.config.ts +++ b/apps/pharmanager/vite.config.ts @@ -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 }), - viteReact(), - ], -}) + resolve: { tsconfigPaths: true }, + plugins: [ + devtools(), + tailwindcss(), + tanstackRouter({ target: "react", autoCodeSplitting: true }), + viteReact(), + ], +}); -export default config +export default config;