From 85a327526855396d6c55221908cbee02f1da84ac Mon Sep 17 00:00:00 2001 From: KavyaCsh Date: Tue, 15 Sep 2026 11:25:56 +0530 Subject: [PATCH] feat: update frontend components and page layouts --- .env | 2 +- package-lock.json | 22 +- src/components/Header.tsx | 8 +- src/components/StockDropdown.tsx | 444 +++++++++++++++++++++++++++++++ src/components/Table.tsx | 10 +- src/pages/Inventory.tsx | 35 +-- src/pages/Pharmacy.tsx | 49 ++-- src/pages/Prescriptions.tsx | 38 +-- 8 files changed, 530 insertions(+), 78 deletions(-) create mode 100644 src/components/StockDropdown.tsx diff --git a/.env b/.env index 8f4d047..83f51a9 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ VITE_API_BASE_URL=http://localhost:8080/v1 -VITE_API_KEY=915e46784e798f21759e52bd2a9d4cc2 +VITE_API_KEY=DUMUqJrNeyuaPDSZSsMf91qjgwc5K3N7 diff --git a/package-lock.json b/package-lock.json index 12cd965..7c8c5cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -1409,7 +1419,6 @@ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -1420,7 +1429,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1567,7 +1575,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", @@ -2541,7 +2548,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2568,7 +2574,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", @@ -2599,7 +2604,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2609,7 +2613,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2629,7 +2632,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -2720,8 +2722,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -2928,7 +2929,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", diff --git a/src/components/Header.tsx b/src/components/Header.tsx index bc0f7cd..9e6f540 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -79,18 +79,18 @@ export const Header = () => { {isDropdownOpen && ( -
+
-
+
+ + {/* Dropdown Popover Card */} + {isOpen && ( +
+ + {/* Search Header Bar */} +
+
+
+ +
+ + setSearchQuery(e.target.value)} + placeholder="Search brand, generic name, or batch..." + className="w-full bg-white border border-slate-200 hover:border-slate-300 rounded-xl text-sm font-medium text-slate-800 focus:outline-none focus:ring-4 focus:ring-blue-500/10 focus:border-blue-500 shadow-sm placeholder:text-slate-400 transition-all" + style={{ + paddingLeft: '44px', + paddingRight: '40px', + paddingTop: '10px', + paddingBottom: '10px', + fontSize: '14px' + }} + /> + + {searchQuery && ( +
+ +
+ )} +
+
+ + {/* Scrollable Item Options List */} +
+ {isLoading ? ( +
+
+ Loading stock items... +
+ ) : filteredItems.length === 0 ? ( +
+ No stock items match "{searchQuery}" +
+ ) : ( + filteredItems.map(item => { + const key = getItemKey(item); + const isSelected = key === value; + const mrp = item.mrp || item.price || item.purchase_price; + + return ( +
handleSelect(item)} + className={` + px-4 py-3 cursor-pointer rounded-xl transition-all duration-150 flex items-center justify-between gap-4 border + ${isSelected + ? 'bg-blue-50/90 border-blue-200/90 text-blue-950 shadow-sm' + : 'border-transparent hover:bg-slate-100/70 hover:border-slate-200/60' + } + `} + > +
+
+ + {item.brand_name || item.generic_name || 'Unnamed Item'} + + {mode === 'batch' && item.batch_no && ( + + {item.batch_no} + + )} +
+ + {item.generic_name && ( + + {item.generic_name} + + )} + +
+ {showStockBadge && getStockBadge(item)} + + {item.expiry && ( + + Exp: {new Date(item.expiry).toLocaleDateString()} + + )} + + {item.form && ( + + {item.form} + + )} +
+
+ +
+ {showPrice && mrp !== undefined && mrp !== null && ( +
+ MRP + + ₹{Number(mrp).toFixed(2)} + +
+ )} + + {isSelected && ( +
+ +
+ )} +
+
+ ); + }) + )} +
+ + {/* Footer Status Bar */} +
+ {filteredItems.length} available items + Mode: {mode} +
+
+ )} +
+ + {error && ( +

+ {error} +

+ )} +
+ ); +} + +export default StockDropdown; diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 2c174df..c80ddee 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -8,7 +8,7 @@ import { MoreHorizontal } from 'lucide-react'; export interface TableProps { data: T[]; columns: any[]; - getRowId?: (row: T) => string; + getRowId?: (row: T, index?: number) => string; emptyMessage?: string; } @@ -17,7 +17,13 @@ export const Table = ({ data, columns, getRowId, emptyMessage = 'No data ava data, columns, getCoreRowModel: getCoreRowModel(), - getRowId, + getRowId: (row: any, index: number) => { + let customId = getRowId ? getRowId(row) : null; + if (customId !== undefined && customId !== null && customId !== '') { + return `${customId}_${index}`; + } + return row.id || row.batch_no || row.product_id || row.dispensing_id || `row-${index}`; + }, }); return ( diff --git a/src/pages/Inventory.tsx b/src/pages/Inventory.tsx index c2c8bf3..cb5ff59 100644 --- a/src/pages/Inventory.tsx +++ b/src/pages/Inventory.tsx @@ -4,6 +4,7 @@ import { createColumnHelper } from '@tanstack/react-table'; import { Table } from '../components/Table'; import Pagination from '../components/Pagination'; import { CustomSelect } from '../components/CustomSelect'; +import { StockDropdown } from '../components/StockDropdown'; import { getExpiringInventory, getLowStockInventory, authenticate, addStock, getDrugs, addMedicine, adjustStock } from '../services/api'; interface InventoryBatch { @@ -450,7 +451,7 @@ const Inventory = () => { item.batch_no} + getRowId={(item, index) => item.batch_no ? `${item.batch_no}_${item.product_id || index}` : `inv-${index}`} /> )} @@ -503,28 +504,16 @@ const Inventory = () => {
{stockMode === 'restock' ? (
- -
-
- -
- setFormData({ ...formData, product_id: e.target.value })} - placeholder="Select or enter Product ID..." - className="w-full bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/10 focus:border-blue-500 text-[15px] font-medium text-slate-800 transition-all hover:bg-slate-100 hover:border-slate-300 placeholder:text-slate-400" - style={{ padding: '12px 16px', paddingLeft: '44px' }} - /> - - {drugOptions.map(d => ( - - ))} - -
+ { + setFormData({ ...formData, product_id: val }); + }} + />
) : ( <> diff --git a/src/pages/Pharmacy.tsx b/src/pages/Pharmacy.tsx index 8dca7fe..12a54d1 100644 --- a/src/pages/Pharmacy.tsx +++ b/src/pages/Pharmacy.tsx @@ -4,6 +4,7 @@ import { createColumnHelper } from '@tanstack/react-table'; import { Table } from '../components/Table'; import Pagination from '../components/Pagination'; import { CustomSelect } from '../components/CustomSelect'; +import { StockDropdown } from '../components/StockDropdown'; import { getDrugs, authenticate, addMedicine, createOrderDraft, updateOrderItems, precheckOrder, dispenseOrder, downloadInvoice, fetchSyncedPatients } from '../services/api'; interface PharmacyItem { @@ -290,8 +291,8 @@ const Pharmacy = () => { {/* Metrics Row */}
-
-
+
+
@@ -304,7 +305,7 @@ const Pharmacy = () => { {lowStockCount > 0 && (
)} -
+
@@ -313,8 +314,8 @@ const Pharmacy = () => {
-
-
+
+
@@ -521,22 +522,32 @@ const Pharmacy = () => {
- { - const item = inventory.find(i => i.id === e.target.value); - if (item && !selectedItems.find(i => i.product_id === item.id)) { - setSelectedItems([...selectedItems, { product_id: item.id, name: item.medicine, qty: 1, price: item.rawPrice }]); + ({ + id: i.id, + product_id: i.id, + brand_name: i.medicine, + generic_name: i.subtitle, + qty_on_hand: i.stock, + mrp: i.rawPrice, + manufacturer: i.manufacture + }))} + onChange={(val, item) => { + if (item && val && !selectedItems.find(i => i.product_id === val)) { + setSelectedItems([ + ...selectedItems, + { + product_id: item.product_id || item.id || val, + name: item.brand_name || '', + qty: 1, + price: Number(item.mrp || 0) + } + ]); } }} - > - - {inventory.filter(i => i.stock > 0).map(item => ( - - ))} - + />
{selectedItems.length > 0 && ( diff --git a/src/pages/Prescriptions.tsx b/src/pages/Prescriptions.tsx index 8d9054d..1ca61b7 100644 --- a/src/pages/Prescriptions.tsx +++ b/src/pages/Prescriptions.tsx @@ -349,8 +349,8 @@ const Prescriptions = () => {
{/* Upload Column */} -
-

Upload Scan

+
+

Upload Scan

@@ -403,7 +403,7 @@ const Prescriptions = () => {
{/* Results Column */} -
+

Extraction Results

{(isUploading || isPolling) && !prescriptionData && ( @@ -423,9 +423,10 @@ const Prescriptions = () => { )} {prescriptionData && ( -
+ <> +
-
+

Extraction Complete

@@ -433,8 +434,8 @@ const Prescriptions = () => {
-
-
+
+
@@ -469,13 +470,13 @@ const Prescriptions = () => { doctor_name: e.target.value } })} - className="w-full px-4 py-2 bg-white border border-slate-300 rounded-lg text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-shadow" + className="w-full px-4 py-2.5 bg-white border border-slate-300 rounded-lg text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-shadow" />
-
-
+
+

Prescribed Medications

@@ -509,7 +510,7 @@ const Prescriptions = () => { setPaymentMethod(e.target.value)} - className="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none" + className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none" > @@ -631,7 +633,7 @@ const Prescriptions = () => {

Inventory has been deducted and audit logs updated.

)} -
+ )}
@@ -651,7 +653,7 @@ const Prescriptions = () => {
-
+
{prescriptionsList.length === 0 ? (