From 11eca48354f6fca9f95b438a5910c301cb1a3c80 Mon Sep 17 00:00:00 2001 From: shafi54 <108669266+shafi-aviz@users.noreply.github.com> Date: Sat, 23 May 2026 14:37:24 +0530 Subject: [PATCH] distributor screen functional --- apps/backend/dev.db | Bin 20480 -> 24576 bytes apps/backend/src/lib/data-manager.ts | 10 +- .../src/trpc/pharmanager/v1/distributor.ts | 63 ++++++ apps/backend/src/trpc/router.ts | 2 + apps/pharmanager/src/routeTree.gen.ts | 83 +++++++- apps/pharmanager/src/routes/distributors.tsx | 12 +- .../src/routes/distributors/$id.tsx | 146 ++++++++++++++ .../src/routes/distributors/add.tsx | 181 +++++++++++++++++ .../src/routes/distributors/index.tsx | 190 ++++++++++++++++++ .../drizzle/0001_overjoyed_dakota_north.sql | 7 + .../drizzle/meta/0001_snapshot.json | 110 ++++++++++ .../drizzle/meta/_journal.json | 7 + .../data-manager-sqlite/src/distributors.ts | 105 ++++++++++ packages/data-manager-sqlite/src/index.ts | 6 + .../src/schema/distributors.ts | 9 + .../data-manager-sqlite/src/schema/index.ts | 1 + .../shared-react/src/hooks/distributors.ts | 21 ++ packages/shared-react/src/index.ts | 1 + 18 files changed, 936 insertions(+), 18 deletions(-) create mode 100644 apps/backend/src/trpc/pharmanager/v1/distributor.ts create mode 100644 apps/pharmanager/src/routes/distributors/$id.tsx create mode 100644 apps/pharmanager/src/routes/distributors/add.tsx create mode 100644 apps/pharmanager/src/routes/distributors/index.tsx create mode 100644 packages/data-manager-sqlite/drizzle/0001_overjoyed_dakota_north.sql create mode 100644 packages/data-manager-sqlite/drizzle/meta/0001_snapshot.json create mode 100644 packages/data-manager-sqlite/src/distributors.ts create mode 100644 packages/data-manager-sqlite/src/schema/distributors.ts create mode 100644 packages/shared-react/src/hooks/distributors.ts diff --git a/apps/backend/dev.db b/apps/backend/dev.db index 6326ac6d1c64292264f9eef3387fbd23daccb8f8..e3838dc4d675d75a8087f0d063107ca8e8fb15eb 100644 GIT binary patch delta 466 zcmZvYF-yZx5P)A2Q(I|zb`S@Lrb7ooY}zDEGgj%MZAGaFI@tGKUTRQNNm9_w`U@0C zLHq|Ifeud2o%{h^oW;2g4n7p2gLfPpeB5#OT|NkxFT(XwRz?UVc>RTRv!XA`Xw`pK z;1kK|Hv|$qW|GP<{q{6CPS*xI^6X-vAY64LOiq~Ng;C&eRbi0+Y4{(?H^rE9i>^xFiTWz(q+i0xJr8>0dN0>(ckkcOVPS`&HJ5IpD@R!Uj z$3vwhCPi=&#Nfu+`F&_^iZU7~vf=2ONi9+#72Pd4mP4qen?kLz+rfXDGL?uUVssT5ubU1iXkLWK9?^xJeSpeJZWae}lUGXnzyD-go~(?lI(QDz3cyj{HfKNwhew=wWD^RMUQ;N7-aP{4_I z^EO^TCN@U?%?$jTHw!AP; + +export const distributorRouter = t.router({ + list: t.procedure + .output(z.array(DistributorSchema)) + .query(() => dataManager.distributors.getDistributors()), + + byId: t.procedure + .input(z.object({ id: z.number().int() })) + .output(DistributorSchema.nullable()) + .query(({ input }) => + dataManager.distributors.getDistributorById(input.id), + ), + + create: t.procedure + .input(CreateDistributorInput) + .output(DistributorSchema) + .mutation(({ input }) => + dataManager.distributors.createDistributor(input), + ), + + update: t.procedure + .input(UpdateDistributorInput) + .output(DistributorSchema.nullable()) + .mutation(({ input }) => { + const { id, ...patch } = input; + return dataManager.distributors.updateDistributor(id, patch); + }), + + remove: t.procedure + .input(z.object({ id: z.number().int() })) + .output(z.object({ ok: z.boolean() })) + .mutation(async ({ input }) => { + const ok = + await dataManager.distributors.deleteDistributor(input.id); + return { ok }; + }), +}); diff --git a/apps/backend/src/trpc/router.ts b/apps/backend/src/trpc/router.ts index 9938c18..5d35f1d 100644 --- a/apps/backend/src/trpc/router.ts +++ b/apps/backend/src/trpc/router.ts @@ -1,8 +1,10 @@ import { t } from "./init"; import { storageRouter } from "./pharmanager/v1/storage"; +import { distributorRouter } from "./pharmanager/v1/distributor"; export const appRouter = t.router({ storage: storageRouter, + distributor: distributorRouter, }); export type AppRouter = typeof appRouter; diff --git a/apps/pharmanager/src/routeTree.gen.ts b/apps/pharmanager/src/routeTree.gen.ts index e3ff4c9..aab3631 100644 --- a/apps/pharmanager/src/routeTree.gen.ts +++ b/apps/pharmanager/src/routeTree.gen.ts @@ -19,8 +19,11 @@ import { Route as CustomersRouteImport } from './routes/customers' import { Route as BillingRouteImport } from './routes/billing' import { Route as IndexRouteImport } from './routes/index' import { Route as StorageIndexRouteImport } from './routes/storage/index' +import { Route as DistributorsIndexRouteImport } from './routes/distributors/index' import { Route as StorageAddRouteImport } from './routes/storage/add' import { Route as StorageIdRouteImport } from './routes/storage/$id' +import { Route as DistributorsAddRouteImport } from './routes/distributors/add' +import { Route as DistributorsIdRouteImport } from './routes/distributors/$id' const StorageRoute = StorageRouteImport.update({ id: '/storage', @@ -72,6 +75,11 @@ const StorageIndexRoute = StorageIndexRouteImport.update({ path: '/', getParentRoute: () => StorageRoute, } as any) +const DistributorsIndexRoute = DistributorsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DistributorsRoute, +} as any) const StorageAddRoute = StorageAddRouteImport.update({ id: '/add', path: '/add', @@ -82,32 +90,47 @@ const StorageIdRoute = StorageIdRouteImport.update({ path: '/$id', getParentRoute: () => StorageRoute, } as any) +const DistributorsAddRoute = DistributorsAddRouteImport.update({ + id: '/add', + path: '/add', + getParentRoute: () => DistributorsRoute, +} as any) +const DistributorsIdRoute = DistributorsIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => DistributorsRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/billing': typeof BillingRoute '/customers': typeof CustomersRoute - '/distributors': typeof DistributorsRoute + '/distributors': typeof DistributorsRouteWithChildren '/products': typeof ProductsRoute '/profile': typeof ProfileRoute '/staff': typeof StaffRoute '/stock': typeof StockRoute '/storage': typeof StorageRouteWithChildren + '/distributors/$id': typeof DistributorsIdRoute + '/distributors/add': typeof DistributorsAddRoute '/storage/$id': typeof StorageIdRoute '/storage/add': typeof StorageAddRoute + '/distributors/': typeof DistributorsIndexRoute '/storage/': typeof StorageIndexRoute } 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 + '/distributors/$id': typeof DistributorsIdRoute + '/distributors/add': typeof DistributorsAddRoute '/storage/$id': typeof StorageIdRoute '/storage/add': typeof StorageAddRoute + '/distributors': typeof DistributorsIndexRoute '/storage': typeof StorageIndexRoute } export interface FileRoutesById { @@ -115,14 +138,17 @@ export interface FileRoutesById { '/': typeof IndexRoute '/billing': typeof BillingRoute '/customers': typeof CustomersRoute - '/distributors': typeof DistributorsRoute + '/distributors': typeof DistributorsRouteWithChildren '/products': typeof ProductsRoute '/profile': typeof ProfileRoute '/staff': typeof StaffRoute '/stock': typeof StockRoute '/storage': typeof StorageRouteWithChildren + '/distributors/$id': typeof DistributorsIdRoute + '/distributors/add': typeof DistributorsAddRoute '/storage/$id': typeof StorageIdRoute '/storage/add': typeof StorageAddRoute + '/distributors/': typeof DistributorsIndexRoute '/storage/': typeof StorageIndexRoute } export interface FileRouteTypes { @@ -137,21 +163,26 @@ export interface FileRouteTypes { | '/staff' | '/stock' | '/storage' + | '/distributors/$id' + | '/distributors/add' | '/storage/$id' | '/storage/add' + | '/distributors/' | '/storage/' fileRoutesByTo: FileRoutesByTo to: | '/' | '/billing' | '/customers' - | '/distributors' | '/products' | '/profile' | '/staff' | '/stock' + | '/distributors/$id' + | '/distributors/add' | '/storage/$id' | '/storage/add' + | '/distributors' | '/storage' id: | '__root__' @@ -164,8 +195,11 @@ export interface FileRouteTypes { | '/staff' | '/stock' | '/storage' + | '/distributors/$id' + | '/distributors/add' | '/storage/$id' | '/storage/add' + | '/distributors/' | '/storage/' fileRoutesById: FileRoutesById } @@ -173,7 +207,7 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute BillingRoute: typeof BillingRoute CustomersRoute: typeof CustomersRoute - DistributorsRoute: typeof DistributorsRoute + DistributorsRoute: typeof DistributorsRouteWithChildren ProductsRoute: typeof ProductsRoute ProfileRoute: typeof ProfileRoute StaffRoute: typeof StaffRoute @@ -253,6 +287,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof StorageIndexRouteImport parentRoute: typeof StorageRoute } + '/distributors/': { + id: '/distributors/' + path: '/' + fullPath: '/distributors/' + preLoaderRoute: typeof DistributorsIndexRouteImport + parentRoute: typeof DistributorsRoute + } '/storage/add': { id: '/storage/add' path: '/add' @@ -267,9 +308,39 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof StorageIdRouteImport parentRoute: typeof StorageRoute } + '/distributors/add': { + id: '/distributors/add' + path: '/add' + fullPath: '/distributors/add' + preLoaderRoute: typeof DistributorsAddRouteImport + parentRoute: typeof DistributorsRoute + } + '/distributors/$id': { + id: '/distributors/$id' + path: '/$id' + fullPath: '/distributors/$id' + preLoaderRoute: typeof DistributorsIdRouteImport + parentRoute: typeof DistributorsRoute + } } } +interface DistributorsRouteChildren { + DistributorsIdRoute: typeof DistributorsIdRoute + DistributorsAddRoute: typeof DistributorsAddRoute + DistributorsIndexRoute: typeof DistributorsIndexRoute +} + +const DistributorsRouteChildren: DistributorsRouteChildren = { + DistributorsIdRoute: DistributorsIdRoute, + DistributorsAddRoute: DistributorsAddRoute, + DistributorsIndexRoute: DistributorsIndexRoute, +} + +const DistributorsRouteWithChildren = DistributorsRoute._addFileChildren( + DistributorsRouteChildren, +) + interface StorageRouteChildren { StorageIdRoute: typeof StorageIdRoute StorageAddRoute: typeof StorageAddRoute @@ -289,7 +360,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, BillingRoute: BillingRoute, CustomersRoute: CustomersRoute, - DistributorsRoute: DistributorsRoute, + DistributorsRoute: DistributorsRouteWithChildren, ProductsRoute: ProductsRoute, ProfileRoute: ProfileRoute, StaffRoute: StaffRoute, diff --git a/apps/pharmanager/src/routes/distributors.tsx b/apps/pharmanager/src/routes/distributors.tsx index 8b054a7..1500af2 100644 --- a/apps/pharmanager/src/routes/distributors.tsx +++ b/apps/pharmanager/src/routes/distributors.tsx @@ -1,13 +1,5 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { Outlet, createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/distributors")({ - component: DistributorsPage, - staticData: { - title: "Distributors", - subtitle: "Agency management with contact details", - }, + component: () => , }); - -function DistributorsPage() { - return
Distributors
; -} diff --git a/apps/pharmanager/src/routes/distributors/$id.tsx b/apps/pharmanager/src/routes/distributors/$id.tsx new file mode 100644 index 0000000..7f3e0bb --- /dev/null +++ b/apps/pharmanager/src/routes/distributors/$id.tsx @@ -0,0 +1,146 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { ArrowLeft, Pencil, Trash2, Truck } from "lucide-react"; +import { + useGetDistributorById, + useRemoveDistributor, + trpc, +} from "shared-react"; + +export const Route = createFileRoute("/distributors/$id")({ + component: DistributorDetailsPage, + staticData: { + title: "Distributor Details", + subtitle: "Distributor information", + }, +}); + +function DistributorDetailsPage() { + const { id } = Route.useParams(); + const distributorId = Number(id); + const { data: distributor, isLoading, error } = + useGetDistributorById(distributorId); + const removeMutation = useRemoveDistributor(); + const utils = trpc.useUtils(); + + function handleDelete() { + if (!distributor) return; + if (!confirm(`Delete ${distributor.agency}?`)) return; + removeMutation.mutate( + { id: distributor.id }, + { + onSuccess: () => { + utils.distributor.list.invalidate(); + window.location.href = "/distributors"; + }, + }, + ); + } + + if (isLoading) { + return ( +
+ Loading distributor details... +
+ ); + } + + if (error || !distributor) { + return ( +
+ +

+ Distributor not found +

+

+ The distributor you're looking for doesn't exist. +

+ + Back to Distributors + +
+ ); + } + + return ( +
+ + + Back to Distributors + + +
+

+ + Distributor Info +

+ +
+
+ Agency Name + + {distributor.agency} + +
+ +
+ + Distributor ID + + + DIST-{String(distributor.id).padStart(3, "0")} + +
+ +
+ + Contact Person + + + {distributor.contact} + +
+ +
+ + Contact Mobile + + + {distributor.mobile} + +
+ +
+ Address + + {distributor.address || "No address provided"} + +
+
+ +
+ + +
+
+
+ ); +} diff --git a/apps/pharmanager/src/routes/distributors/add.tsx b/apps/pharmanager/src/routes/distributors/add.tsx new file mode 100644 index 0000000..a6a6a6d --- /dev/null +++ b/apps/pharmanager/src/routes/distributors/add.tsx @@ -0,0 +1,181 @@ +import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; +import { ArrowLeft, Plus } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useCreateDistributor, trpc } from "shared-react"; + +const formSchema = z.object({ + agency: z.string().min(1, "Agency name is required"), + contact: z.string().min(1, "Contact person is required"), + mobile: z + .string() + .length(10, "Valid 10-digit mobile number is required") + .regex(/^\d{10}$/, "Must be 10 digits"), + address: z.string().nullable().optional(), +}); + +type FormValues = z.infer; + +export const Route = createFileRoute("/distributors/add")({ + component: AddDistributorPage, + staticData: { + title: "Add Distributor", + subtitle: "Register a new distributor agency", + }, +}); + +function AddDistributorPage() { + const navigate = useNavigate(); + const createMutation = useCreateDistributor(); + const utils = trpc.useUtils(); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + agency: "", + contact: "", + mobile: "", + address: null, + }, + }); + + function onSubmit(values: FormValues) { + createMutation.mutate( + { + agency: values.agency, + contact: values.contact, + mobile: values.mobile, + address: values.address ?? null, + }, + { + onSuccess: () => { + utils.distributor.list.invalidate(); + navigate({ to: "/distributors" }); + }, + }, + ); + } + + return ( +
+ + + Back to Distributors + + +
+
+
+ + + {errors.agency && ( +

+ {errors.agency.message} +

+ )} +
+ +
+ + + {errors.contact && ( +

+ {errors.contact.message} +

+ )} +
+ +
+ + { + e.target.value = e.target.value + .replace(/\D/g, "") + .slice(0, 10); + }, + })} + placeholder="e.g. 9876500001" + className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.mobile ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`} + /> + {errors.mobile && ( +

+ {errors.mobile.message} +

+ )} +
+ +
+ +