From 22994f9f6d87caf8186fdafe674a1c12579e7082 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Tue, 26 Nov 2024 20:55:59 +0530 Subject: [PATCH 001/137] Add lab tests translation and sidebar navigation item --- public/locale/en.json | 1 + src/components/Common/Sidebar/Sidebar.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/public/locale/en.json b/public/locale/en.json index 8a4a36a1a5c..0178d809a72 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -802,6 +802,7 @@ "is_up_shift": "Is up shift", "is_upshift_case": "Is upshift case", "is_vaccinated": "Whether vaccinated", + "lab_tests": "Lab Tests", "label": "Label", "landline": "Indian landline", "language_selection": "Language Selection", diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index 6612555a908..a1b61e5c8a1 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -58,6 +58,7 @@ const StatelessSidebar = ({ const BaseNavItems: INavItem[] = [ { text: t("facilities"), to: "/facility", icon: "l-hospital" }, { text: t("patients"), to: "/patients", icon: "l-user-injured" }, + { text: t("lab_tests"), to: "/lab_tests", icon: "l-heart-rate" }, { text: t("assets"), to: "/assets", icon: "l-shopping-cart-alt" }, { text: t("sample_test"), to: "/sample", icon: "l-medkit" }, { text: t("shifting"), to: "/shifting", icon: "l-ambulance" }, From e5e36d45124a1b1dd08e7b864bb02b35df822c02 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Wed, 27 Nov 2024 02:41:51 +0530 Subject: [PATCH 002/137] Add Lab Test routes and component for lab orders --- src/Routers/AppRouter.tsx | 2 ++ src/Routers/routes/LabTestRoutes.tsx | 9 +++++++++ src/components/LabTest/Index.tsx | 9 +++++++++ src/hooks/useActiveLink.ts | 1 + 4 files changed, 21 insertions(+) create mode 100644 src/Routers/routes/LabTestRoutes.tsx create mode 100644 src/components/LabTest/Index.tsx diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 125ee8c9055..5b82cb695cf 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -22,6 +22,7 @@ import { BLACKLISTED_PATHS } from "@/common/constants"; import AssetRoutes from "@/Routers/routes/AssetRoutes"; import ConsultationRoutes from "@/Routers/routes/ConsultationRoutes"; import FacilityRoutes from "@/Routers/routes/FacilityRoutes"; +import LabTestRoutes from "@/Routers/routes/LabTestRoutes"; import PatientRoutes from "@/Routers/routes/PatientRoutes"; import ResourceRoutes from "@/Routers/routes/ResourceRoutes"; import SampleRoutes from "@/Routers/routes/SampleRoutes"; @@ -48,6 +49,7 @@ const Routes: AppRoutes = { ...AssetRoutes, ...ConsultationRoutes, + ...LabTestRoutes, ...FacilityRoutes, ...PatientRoutes, ...ResourceRoutes, diff --git a/src/Routers/routes/LabTestRoutes.tsx b/src/Routers/routes/LabTestRoutes.tsx new file mode 100644 index 00000000000..7bfbb4b958f --- /dev/null +++ b/src/Routers/routes/LabTestRoutes.tsx @@ -0,0 +1,9 @@ +import { LabTest } from "@/components/LabTest/Index"; + +import { AppRoutes } from "@/Routers/AppRouter"; + +const LabTestRoutes: AppRoutes = { + "/lab_tests": () => , +}; + +export default LabTestRoutes; diff --git a/src/components/LabTest/Index.tsx b/src/components/LabTest/Index.tsx new file mode 100644 index 00000000000..f863483ff4a --- /dev/null +++ b/src/components/LabTest/Index.tsx @@ -0,0 +1,9 @@ +import Page from "@/components/Common/Page"; + +export const LabTest = () => { + return ( + +
Lab Test
+
+ ); +}; diff --git a/src/hooks/useActiveLink.ts b/src/hooks/useActiveLink.ts index b52b15600dd..b804d3c2a46 100644 --- a/src/hooks/useActiveLink.ts +++ b/src/hooks/useActiveLink.ts @@ -13,6 +13,7 @@ const activeLinkPriority = { "/death_report": "/patients", "/assets": "/assets", "/sample": "/sample", + "/lab_tests": "/lab_tests", "/shifting": "/shifting", "/resource": "/resource", "/users": "/users", From 2f5f4d6c7948e97f1912ec25fedd880c6db255ae Mon Sep 17 00:00:00 2001 From: yash-learner Date: Wed, 27 Nov 2024 04:17:38 +0530 Subject: [PATCH 003/137] Get tabs working --- src/Routers/routes/LabTestRoutes.tsx | 4 +- src/components/Common/Sidebar/Sidebar.tsx | 6 ++- src/components/LabTest/Index.tsx | 53 +++++++++++++++++++- src/components/LabTest/OrderPlaced.tsx | 11 ++++ src/components/LabTest/SpecimenCollected.tsx | 11 ++++ src/hooks/useActiveLink.ts | 2 +- 6 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 src/components/LabTest/OrderPlaced.tsx create mode 100644 src/components/LabTest/SpecimenCollected.tsx diff --git a/src/Routers/routes/LabTestRoutes.tsx b/src/Routers/routes/LabTestRoutes.tsx index 7bfbb4b958f..95be026094d 100644 --- a/src/Routers/routes/LabTestRoutes.tsx +++ b/src/Routers/routes/LabTestRoutes.tsx @@ -3,7 +3,9 @@ import { LabTest } from "@/components/LabTest/Index"; import { AppRoutes } from "@/Routers/AppRouter"; const LabTestRoutes: AppRoutes = { - "/lab_tests": () => , + "/lab_tests/order_placed": () => , + "/lab_tests/specimen_collected": () => , + "/lab_tests/sent_to_lab": () => , }; export default LabTestRoutes; diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index a1b61e5c8a1..a8f228c43c3 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -58,7 +58,11 @@ const StatelessSidebar = ({ const BaseNavItems: INavItem[] = [ { text: t("facilities"), to: "/facility", icon: "l-hospital" }, { text: t("patients"), to: "/patients", icon: "l-user-injured" }, - { text: t("lab_tests"), to: "/lab_tests", icon: "l-heart-rate" }, + { + text: t("lab_tests"), + to: "/lab_tests/order_placed", + icon: "l-heart-rate", + }, { text: t("assets"), to: "/assets", icon: "l-shopping-cart-alt" }, { text: t("sample_test"), to: "/sample", icon: "l-medkit" }, { text: t("shifting"), to: "/shifting", icon: "l-ambulance" }, diff --git a/src/components/LabTest/Index.tsx b/src/components/LabTest/Index.tsx index f863483ff4a..f8e6f29def4 100644 --- a/src/components/LabTest/Index.tsx +++ b/src/components/LabTest/Index.tsx @@ -1,9 +1,58 @@ +import { Link, usePath } from "raviger"; + import Page from "@/components/Common/Page"; +import { OrderPlaced } from "@/components/LabTest/OrderPlaced"; +import { SpecimenCollected } from "@/components/LabTest/SpecimenCollected"; export const LabTest = () => { + const currentPath = usePath(); + + const tabButtonClasses = (selected: boolean) => + `min-w-max-content cursor-pointer whitespace-nowrap ${ + selected + ? "border-primary-500 hover:border-secondary-300 text-primary-600 border-b-2" + : "text-secondary-700 hover:text-secondary-700" + } pb-2 border-b-2 text-sm`; + + const renderTabContent = () => { + switch (currentPath) { + case "/lab_tests/order_placed": + return ; + case "/lab_tests/specimen_collected": + return ; + default: + return
Tab not found
; + } + }; + return ( - -
Lab Test
+ + + +
{renderTabContent()}
); }; diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx new file mode 100644 index 00000000000..9f46c8ec7a0 --- /dev/null +++ b/src/components/LabTest/OrderPlaced.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +export const OrderPlaced: React.FC = () => { + return ( +
+

Order Placed

+

Display data for orders that are placed.

+ {/* Add table or other UI elements specific to this tab */} +
+ ); +}; diff --git a/src/components/LabTest/SpecimenCollected.tsx b/src/components/LabTest/SpecimenCollected.tsx new file mode 100644 index 00000000000..fc7bfea684e --- /dev/null +++ b/src/components/LabTest/SpecimenCollected.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +export const SpecimenCollected: React.FC = () => { + return ( +
+

Specimen Collected

+

Display data for specimens that are collected.

+ {/* Add table or other UI elements specific to this tab */} +
+ ); +}; diff --git a/src/hooks/useActiveLink.ts b/src/hooks/useActiveLink.ts index b804d3c2a46..80e2cd53357 100644 --- a/src/hooks/useActiveLink.ts +++ b/src/hooks/useActiveLink.ts @@ -13,7 +13,7 @@ const activeLinkPriority = { "/death_report": "/patients", "/assets": "/assets", "/sample": "/sample", - "/lab_tests": "/lab_tests", + "/lab_tests": "/lab_tests/order_placed", "/shifting": "/shifting", "/resource": "/resource", "/users": "/users", From 77f3368692d51c400b36693f833b772d149d854a Mon Sep 17 00:00:00 2001 From: yash-learner Date: Thu, 28 Nov 2024 02:41:57 +0530 Subject: [PATCH 004/137] Add @tanstack/react-table package --- package-lock.json | 34 +++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 63af1164ef0..d4d7c7757d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", "@sentry/browser": "^8.37.1", + "@tanstack/react-table": "^8.20.5", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -5247,6 +5248,25 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20" } }, + "node_modules/@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.10.8", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", @@ -5264,6 +5284,18 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.10.8", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz", @@ -20862,4 +20894,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 020d73f5e65..766cf6ea2aa 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", "@sentry/browser": "^8.37.1", + "@tanstack/react-table": "^8.20.5", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", From a6868ba7312b90a682e7db9c659efec84f98205e Mon Sep 17 00:00:00 2001 From: yash-learner Date: Thu, 28 Nov 2024 02:42:28 +0530 Subject: [PATCH 005/137] Add shadcn Input and Table components --- src/components/ui/input.tsx | 22 +++++++ src/components/ui/table.tsx | 120 ++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/table.tsx diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 00000000000..1578e860a3d --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 00000000000..c418a664086 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0 dark:bg-gray-800/50", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px] dark:text-gray-400", + className, + )} + {...props} + /> +)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; From 8a8c6f30fb90d2f5504ca633d37673fd43e3cc2f Mon Sep 17 00:00:00 2001 From: yash-learner Date: Thu, 28 Nov 2024 03:41:39 +0530 Subject: [PATCH 006/137] WIP get table structure --- src/components/LabTest/DataTable.tsx | 45 ++++++++++++++++ src/components/LabTest/OrderPlaced.tsx | 54 ++++++++++++++++++-- src/components/LabTest/SpecimenCollected.tsx | 40 ++++++++++++--- 3 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 src/components/LabTest/DataTable.tsx diff --git a/src/components/LabTest/DataTable.tsx b/src/components/LabTest/DataTable.tsx new file mode 100644 index 00000000000..2007f74f031 --- /dev/null +++ b/src/components/LabTest/DataTable.tsx @@ -0,0 +1,45 @@ +import React from "react"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +interface DataTableProps { + columns: Array<{ label: string; key: string }>; + data: Array>; + actions?: (row: Record) => React.ReactNode; +} + +export const DataTable: React.FC = ({ + columns, + data, + actions, +}) => { + return ( + + + + {columns.map((col) => ( + {col.label} + ))} + {actions && Actions} + + + + {data.map((row, index) => ( + + {columns.map((col) => ( + {row[col.key]} + ))} + {actions && {actions(row)}} + + ))} + +
+ ); +}; diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 9f46c8ec7a0..378981aa337 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -1,11 +1,57 @@ import React from "react"; +import { DataTable } from "@/components/LabTest/DataTable"; + export const OrderPlaced: React.FC = () => { + const columns = [ + { label: "Specimen ID", key: "specimenId" }, + { label: "Order ID", key: "orderId" }, + { label: "Patient Name", key: "patientName" }, + { label: "Specimen", key: "specimen" }, + { label: "Tests", key: "tests" }, + { label: "Collector", key: "collector" }, + { label: "Status", key: "status" }, + ]; + + const data = [ + { + specimenId: "SPEC009213", + orderId: "CARE_LAB-001", + patientName: "John Honai", + specimen: "Blood", + tests: "Complete Blood Count (CBC)", + collector: "John Doe", + status: "Pending", + }, + { + specimenId: "SPEC009412", + orderId: "CARE_LAB-002", + patientName: "Jane Doe", + specimen: "Swab", + tests: "COVID-19 PCR, Influenza Test", + collector: "Jane Doe", + status: "Pending", + }, + ]; + + const handleAction = (row: Record) => { + alert(`Collect Specimen for ${row.orderId}`); + }; + return ( -
-

Order Placed

-

Display data for orders that are placed.

- {/* Add table or other UI elements specific to this tab */} +
+ ( + + )} + />
); }; diff --git a/src/components/LabTest/SpecimenCollected.tsx b/src/components/LabTest/SpecimenCollected.tsx index fc7bfea684e..4012f5be0cd 100644 --- a/src/components/LabTest/SpecimenCollected.tsx +++ b/src/components/LabTest/SpecimenCollected.tsx @@ -1,11 +1,39 @@ -import React from "react"; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; export const SpecimenCollected: React.FC = () => { + const data = [ + { id: 1, name: "John Doe", age: 28 }, + { id: 2, name: "Jane Smith", age: 34 }, + { id: 3, name: "Emily Johnson", age: 40 }, + ]; + return ( -
-

Specimen Collected

-

Display data for specimens that are collected.

- {/* Add table or other UI elements specific to this tab */} -
+ + A simple example table + + + ID + Name + Age + + + + {data.map((row) => ( + + {row.id} + {row.name} + {row.age} + + ))} + +
); }; From 43b867bb5b7cc1da9805ba877ec23f9ae4d831a1 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Fri, 29 Nov 2024 05:15:29 +0530 Subject: [PATCH 007/137] WIP: get a basic structure for filtering --- package-lock.json | 129 +++++++++++++++ package.json | 3 + public/images/filter.svg | 5 + src/components/LabTest/OrderPlaced.tsx | 63 +++++-- src/components/LabTest/TableFilter.tsx | 219 +++++++++++++++++++++++++ src/components/ui/checkbox.tsx | 28 ++++ src/components/ui/popover.tsx | 31 ++++ src/components/ui/select.tsx | 161 ++++++++++++++++++ 8 files changed, 622 insertions(+), 17 deletions(-) create mode 100644 public/images/filter.svg create mode 100644 src/components/LabTest/TableFilter.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/select.tsx diff --git a/package-lock.json b/package-lock.json index d4d7c7757d6..4d8a106afcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,11 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -3627,6 +3630,11 @@ "validator": "^13.9.0" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", @@ -3656,6 +3664,35 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", + "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -3905,6 +3942,42 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", + "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -4069,6 +4142,48 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.2.tgz", + "integrity": "sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -4221,6 +4336,20 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", diff --git a/package.json b/package.json index 766cf6ea2aa..f65e6d7c217 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,11 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", diff --git a/public/images/filter.svg b/public/images/filter.svg new file mode 100644 index 00000000000..1650ac1d216 --- /dev/null +++ b/public/images/filter.svg @@ -0,0 +1,5 @@ + + + diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 378981aa337..5db675fb946 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -1,6 +1,7 @@ -import React from "react"; +import React, { useState } from "react"; import { DataTable } from "@/components/LabTest/DataTable"; +import TableFilter, { Filter } from "@/components/LabTest/TableFilter"; export const OrderPlaced: React.FC = () => { const columns = [ @@ -13,7 +14,33 @@ export const OrderPlaced: React.FC = () => { { label: "Status", key: "status" }, ]; - const data = [ + const keys = [ + { + label: "Specimen", + key: "specimen", + type: "checkbox", + options: ["Blood", "Swab"], + defaultOperator: "is", + operators: ["is", "is_not"], + }, + { + label: "Status", + key: "status", + type: "checkbox", + options: ["Pending", "Collected"], + defaultOperator: "is", + operators: ["is", "is_not"], + }, + { + label: "Order ID", + key: "orderId", + type: "text", + defaultOperator: "contains", + operators: ["contains", "is", "is_not"], + }, + ]; + + const initialData = [ { specimenId: "SPEC009213", orderId: "CARE_LAB-001", @@ -30,28 +57,30 @@ export const OrderPlaced: React.FC = () => { specimen: "Swab", tests: "COVID-19 PCR, Influenza Test", collector: "Jane Doe", - status: "Pending", + status: "Collected", }, ]; - const handleAction = (row: Record) => { - alert(`Collect Specimen for ${row.orderId}`); + const [data, setData] = useState(initialData); + + const handleFiltersChange = (filters: Filter[]) => { + const filteredData = initialData.filter((row) => + filters.every((filter) => { + const value = row[filter.column as keyof typeof row]; + if (filter.operator === "is") return value === filter.value; + if (filter.operator === "is_not") return value !== filter.value; + if (filter.operator === "contains") + return String(value).includes(filter.value); + return true; + }), + ); + setData(filteredData); }; return (
- ( - - )} - /> + +
); }; diff --git a/src/components/LabTest/TableFilter.tsx b/src/components/LabTest/TableFilter.tsx new file mode 100644 index 00000000000..0d914d2c4cf --- /dev/null +++ b/src/components/LabTest/TableFilter.tsx @@ -0,0 +1,219 @@ +import React, { useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +export interface Filter { + column: string; + operator: string; + value: string; +} + +interface TableFilterProps { + keys: Array<{ + key: string; + label: string; + type: string; // "text", "dropdown", or "checkbox" + options?: string[]; // Available options for dropdown/checkbox + defaultOperator?: string; // Default operator for this key + operators: string[]; // Allowed operators + }>; + onFiltersChange: (filters: Filter[]) => void; +} + +const TableFilter: React.FC = ({ keys, onFiltersChange }) => { + const [filters, setFilters] = useState([]); + const [currentFilter, setCurrentFilter] = useState>({}); + + const addFilter = (filter: Filter) => { + const updatedFilters = [...filters, filter]; + setFilters(updatedFilters); + setCurrentFilter({}); + onFiltersChange(updatedFilters); + }; + + const removeFilter = (index: number) => { + const updatedFilters = filters.filter((_, i) => i !== index); + setFilters(updatedFilters); + onFiltersChange(updatedFilters); + }; + + const updateFilter = (index: number, field: keyof Filter, value: string) => { + const updatedFilters = [...filters]; + updatedFilters[index] = { ...updatedFilters[index], [field]: value }; + setFilters(updatedFilters); + onFiltersChange(updatedFilters); + }; + + return ( +
+

Filters

+ + {/* Display Applied Filters */} +
+ {filters.map((filter, index) => ( +
+ + + + + + {keys.map((key) => ( + + ))} + + + + + + + + + {keys + .find((key) => key.key === filter.column) + ?.operators.map((op) => ( + + ))} + + + + + + + + + {keys.find((key) => key.key === filter.column)?.type === + "checkbox" ? ( + keys + .find((key) => key.key === filter.column) + ?.options?.map((option) => ( +
+ + updateFilter(index, "value", option) + } + /> + {option} +
+ )) + ) : ( + + updateFilter(index, "value", e.target.value) + } + placeholder="Enter value" + /> + )} +
+
+ + +
+ ))} +
+ + {/* Add Filter Button */} + + + + + + {!currentFilter.column && ( + <> +

Select Column

+ {keys.map((key) => ( + + ))} + + )} + + {currentFilter.column && !currentFilter.value && ( + <> +

Select Value

+ {keys.find((key) => key.key === currentFilter.column)?.type === + "checkbox" ? ( + keys + .find((key) => key.key === currentFilter.column) + ?.options?.map((option) => ( +
+ + addFilter({ + column: currentFilter.column!, + operator: currentFilter.operator || "is", + value: option, + }) + } + /> + {option} +
+ )) + ) : ( + + addFilter({ + column: currentFilter.column!, + operator: currentFilter.operator || "is", + value: e.target.value, + }) + } + /> + )} + + )} +
+
+
+ ); +}; + +export default TableFilter; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 00000000000..5c900551a40 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "@radix-ui/react-icons"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000000..466e7226e36 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 00000000000..f60fefc9205 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,161 @@ +import { + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, +} from "@radix-ui/react-icons"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1 dark:border-gray-800 dark:ring-offset-gray-950 dark:placeholder:text-gray-400 dark:focus:ring-gray-300", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; From cf8a514eecf97d570ed6edd5732d97de4df07da3 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 30 Nov 2024 01:15:10 +0530 Subject: [PATCH 008/137] Add shadcn separator --- package-lock.json | 23 +++++++++++++++++++++++ package.json | 1 + src/components/ui/separator.tsx | 29 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 src/components/ui/separator.tsx diff --git a/package-lock.json b/package-lock.json index 4d8a106afcd..23a34e4fada 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -4184,6 +4185,28 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", + "integrity": "sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", diff --git a/package.json b/package.json index f65e6d7c217..ed4af43e397 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 00000000000..e2a4ee2a76d --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref, + ) => ( + + ), +); +Separator.displayName = SeparatorPrimitive.Root.displayName; + +export { Separator }; From a72e92151575bef8618dc4005e1fe634235027f0 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 30 Nov 2024 01:15:46 +0530 Subject: [PATCH 009/137] Add primary button variant styles --- src/components/ui/button.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index f49a7052a78..4247426fb8c 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -11,6 +11,8 @@ const buttonVariants = cva( variant: { default: "bg-gray-900 text-gray-50 shadow hover:bg-gray-900/90 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90", + primary: + "bg-primary-700 text-white shadow hover:bg-primary-700/90 dark:bg-primary-100 dark:text-primary-900 dark:hover:bg-primary-100/90", destructive: "bg-red-500 text-gray-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90", outline: From a3a079512fe4f1cb4c7d5aab9cdf1a25284f795d Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 30 Nov 2024 01:17:45 +0530 Subject: [PATCH 010/137] Enhance TableFilter component with separators and clear filters functionality --- src/components/LabTest/TableFilter.tsx | 149 +++++++++++++++---------- 1 file changed, 91 insertions(+), 58 deletions(-) diff --git a/src/components/LabTest/TableFilter.tsx b/src/components/LabTest/TableFilter.tsx index 0d914d2c4cf..191f97f7aec 100644 --- a/src/components/LabTest/TableFilter.tsx +++ b/src/components/LabTest/TableFilter.tsx @@ -8,6 +8,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { Separator } from "@/components/ui/separator"; export interface Filter { column: string; @@ -51,65 +52,78 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { onFiltersChange(updatedFilters); }; - return ( -
-

Filters

+ const clearFilters = () => { + setFilters([]); + onFiltersChange([]); + }; + return ( +
{/* Display Applied Filters */} -
+
{filters.map((filter, index) => (
- - - - - - {keys.map((key) => ( - - ))} - - + filter + {/* Column Label */} + + {keys.find((key) => key.key === filter.column)?.label} + + + {/* Separator */} + + {/* Operator */} - - - {keys - .find((key) => key.key === filter.column) - ?.operators.map((op) => ( - - ))} + +
    + {keys + .find((key) => key.key === filter.column) + ?.operators.map((op) => ( +
  • + +
  • + ))} +
+ {/* Separator */} + + + {/* Value */} - - + {keys.find((key) => key.key === filter.column)?.type === "checkbox" ? ( keys @@ -132,13 +146,17 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { updateFilter(index, "value", e.target.value) } placeholder="Enter value" + className="text-sm" /> )} + + + {/* Remove Button */}
))}
- {/* Add Filter Button */} - + - + {!currentFilter.column && ( <>

Select Column

- {keys.map((key) => ( - - ))} +
+ {keys.map((key) => ( + + ))} +
)} - {currentFilter.column && !currentFilter.value && ( <>

Select Value

@@ -212,6 +238,13 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { )}
+ + {/* Clear Filters */} + {filters.length > 0 && ( + + )}
); }; From 18c768e96b54ccf309bae740332de050bb32b16d Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sat, 30 Nov 2024 01:19:05 +0530 Subject: [PATCH 011/137] Update OrderPlaced component layout with improved spacing --- src/components/LabTest/OrderPlaced.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 5db675fb946..72a9e02aff2 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -78,7 +78,7 @@ export const OrderPlaced: React.FC = () => { }; return ( -
+
From 60ae09c67c605552abb25a5359341ff4e7bdb2c6 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sun, 1 Dec 2024 01:06:45 +0530 Subject: [PATCH 012/137] Add shadcn Badge component --- src/components/ui/badge.tsx | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/components/ui/badge.tsx diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 00000000000..d3f1ec19690 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,37 @@ +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-md border border-gray-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 dark:border-gray-800 dark:focus:ring-gray-300", + { + variants: { + variant: { + default: + "border-transparent bg-gray-900 text-gray-50 shadow hover:bg-gray-900/80 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/80", + secondary: + "border-transparent bg-gray-100 text-gray-900 hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/80", + destructive: + "border-transparent bg-red-500 text-gray-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/80", + outline: "text-gray-950 dark:text-gray-50", + warning: "bg-orange-100 text-orange-900", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; From d51bbff75019dfd853abae0b96d6b5462dc543eb Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sun, 1 Dec 2024 01:07:18 +0530 Subject: [PATCH 013/137] Enhance DataTable component to support badge rendering for specific columns --- src/components/LabTest/DataTable.tsx | 76 +++++++++++++++++++++------- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/src/components/LabTest/DataTable.tsx b/src/components/LabTest/DataTable.tsx index 2007f74f031..c01ce734f61 100644 --- a/src/components/LabTest/DataTable.tsx +++ b/src/components/LabTest/DataTable.tsx @@ -1,5 +1,6 @@ import React from "react"; +import { Badge } from "@/components/ui/badge"; import { Table, TableBody, @@ -10,36 +11,75 @@ import { } from "@/components/ui/table"; interface DataTableProps { - columns: Array<{ label: string; key: string }>; + columns: Array<{ label: string; key: string; render_as?: string }>; data: Array>; actions?: (row: Record) => React.ReactNode; } +const badgeStyles: Record = { + pending: "bg-orange-100 text-orange-800", + collected: "bg-blue-100 text-blue-800", + completed: "bg-green-100 text-green-800", + cancelled: "bg-red-100 text-red-800", + default: "bg-gray-100 text-gray-800", +}; + export const DataTable: React.FC = ({ columns, data, actions, }) => { return ( - - - - {columns.map((col) => ( - {col.label} - ))} - {actions && Actions} - - - - {data.map((row, index) => ( - +
+
+ + {columns.map((col) => ( - {row[col.key]} + + {col.label} + ))} - {actions && {actions(row)}} + {actions && ( + + Action + + )} - ))} - -
+ + + {data.map((row, index) => ( + + {columns.map((col) => ( + + {col.render_as === "badge" ? ( + + {row[col.key]} + + ) : ( + row[col.key] + )} + + ))} + {actions && ( + {actions(row)} + )} + + ))} + +
+
); }; From da56b83f987f999edc67159bea491c7ab9c18520 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sun, 1 Dec 2024 01:48:45 +0530 Subject: [PATCH 014/137] Refactor OrderPlaced component to enhance filtering and add action button for specimen collection --- src/components/LabTest/OrderPlaced.tsx | 125 +++++++++++++++++++------ 1 file changed, 96 insertions(+), 29 deletions(-) diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 72a9e02aff2..3ddc1534ac4 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -1,42 +1,56 @@ import React, { useState } from "react"; +import { Button } from "@/components/ui/button"; + import { DataTable } from "@/components/LabTest/DataTable"; import TableFilter, { Filter } from "@/components/LabTest/TableFilter"; export const OrderPlaced: React.FC = () => { - const columns = [ - { label: "Specimen ID", key: "specimenId" }, - { label: "Order ID", key: "orderId" }, - { label: "Patient Name", key: "patientName" }, - { label: "Specimen", key: "specimen" }, - { label: "Tests", key: "tests" }, - { label: "Collector", key: "collector" }, - { label: "Status", key: "status" }, - ]; - const keys = [ { - label: "Specimen", + key: "specimenId", + label: "Specimen ID", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "orderId", + label: "Order ID", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "patientName", + label: "Patient Name", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { key: "specimen", + label: "Specimen", type: "checkbox", - options: ["Blood", "Swab"], - defaultOperator: "is", + options: ["Blood", "Swab", "Tissue"], operators: ["is", "is_not"], }, { - label: "Status", - key: "status", - type: "checkbox", - options: ["Pending", "Collected"], - defaultOperator: "is", - operators: ["is", "is_not"], + key: "tests", + label: "Tests", + type: "text", + operators: ["is", "contains", "does_not_contain"], }, { - label: "Order ID", - key: "orderId", + key: "collector", + label: "Collector", type: "text", - defaultOperator: "contains", - operators: ["contains", "is", "is_not"], + operators: ["is", "is_not", "contains"], + }, + { + key: "status", + label: "Status", + type: "checkbox", + options: ["Pending", "Collected", "Completed", "Cancelled"], + operators: ["is", "is_not"], + render_as: "badge", }, ]; @@ -59,28 +73,81 @@ export const OrderPlaced: React.FC = () => { collector: "Jane Doe", status: "Collected", }, + { + specimenId: "SPEC009425", + orderId: "CARE_LAB-003", + patientName: "Narayani", + specimen: "Tissue", + tests: "Biopsy of tissue", + collector: "Dr. Rajmohan", + status: "Completed", + }, + { + specimenId: "SPEC009876", + orderId: "CARE_LAB-004", + patientName: "Tintu Ukken", + specimen: "Swab", + tests: "COVID-19 PCR", + collector: "Jeena Mathew", + status: "Cancelled", + }, + { + specimenId: "SPEC001234", + orderId: "CARE_LAB-005", + patientName: "Paul Varghese", + specimen: "Blood", + tests: "Lipid Profile", + collector: "John Doe", + status: "Pending", + }, ]; const [data, setData] = useState(initialData); - const handleFiltersChange = (filters: Filter[]) => { + const handleFiltersChange = (appliedFilters: Filter[]) => { const filteredData = initialData.filter((row) => - filters.every((filter) => { - const value = row[filter.column as keyof typeof row]; + appliedFilters.every((filter) => { + const columnKey = filter.column as keyof typeof row; // Ensure column matches row keys + const value = row[columnKey]; // Access the row value + if (filter.operator === "is") return value === filter.value; if (filter.operator === "is_not") return value !== filter.value; if (filter.operator === "contains") - return String(value).includes(filter.value); + return typeof value === "string" && value.includes(filter.value); + if (filter.operator === "does_not_contain") + return typeof value === "string" && !value.includes(filter.value); + if (filter.operator === "is_empty") return !value; + if (filter.operator === "is_not_empty") return !!value; return true; }), ); + setData(filteredData); }; + const handleAction = (row: Record) => { + alert(`Collect Specimen for ${row.orderId}`); + }; + return ( -
+
+ {/* Table Filter */} - + + {/* Data Table */} + ({ + label: key.label, + key: key.key, + render_as: key.render_as, + }))} + data={data} + actions={(row) => ( + + )} + />
); }; From f6d6aab33ee23b05124ecc5ac1a3939c9f016fef Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sun, 1 Dec 2024 02:40:13 +0530 Subject: [PATCH 015/137] Add shadcn Label and Radio Group components --- package-lock.json | 55 +++++++++++++++++++++++++++++++ package.json | 2 ++ src/components/ui/label.tsx | 24 ++++++++++++++ src/components/ui/radio-group.tsx | 42 +++++++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/radio-group.tsx diff --git a/package-lock.json b/package-lock.json index 23a34e4fada..5c332e7a0fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,9 @@ "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -3903,6 +3905,28 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", @@ -4097,6 +4121,37 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.1.tgz", + "integrity": "sha512-kdbv54g4vfRjja9DNWPMxKvXblzqbpEC8kspEkZ6dVP7kQksGCn+iZHkcCz2nb00+lPdRvxrqy4WrvvV1cNqrQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", diff --git a/package.json b/package.json index ed4af43e397..2c9ce0cfa82 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,9 @@ "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 00000000000..a115d28af1e --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as LabelPrimitive from "@radix-ui/react-label"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 00000000000..9a96598c05e --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import { DotFilledIcon } from "@radix-ui/react-icons"; +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ); +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; + +export { RadioGroup, RadioGroupItem }; From 4c3530ab3b52b5284ff3def73ec6d1f25240302d Mon Sep 17 00:00:00 2001 From: yash-learner Date: Sun, 1 Dec 2024 02:51:22 +0530 Subject: [PATCH 016/137] Add radio button support to TableFilter component --- src/components/LabTest/TableFilter.tsx | 57 +++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/components/LabTest/TableFilter.tsx b/src/components/LabTest/TableFilter.tsx index 191f97f7aec..1b3c0f69c74 100644 --- a/src/components/LabTest/TableFilter.tsx +++ b/src/components/LabTest/TableFilter.tsx @@ -3,11 +3,13 @@ import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Separator } from "@/components/ui/separator"; export interface Filter { @@ -20,8 +22,8 @@ interface TableFilterProps { keys: Array<{ key: string; label: string; - type: string; // "text", "dropdown", or "checkbox" - options?: string[]; // Available options for dropdown/checkbox + type: string; // "text", "checkbox", or "radio" + options?: string[]; // Available options for dropdown/checkbox/radio defaultOperator?: string; // Default operator for this key operators: string[]; // Allowed operators }>; @@ -139,6 +141,29 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { {option}
)) + ) : keys.find((key) => key.key === filter.column)?.type === + "radio" ? ( + updateFilter(index, "value", val)} + > +
+ {keys + .find((key) => key.key === filter.column) + ?.options?.map((option, idx) => { + const optionId = `${filter.column}-${idx}`; + return ( +
+ + +
+ ); + })} +
+
) : ( = ({ keys, onFiltersChange }) => { {option} )) + ) : keys.find((key) => key.key === currentFilter.column)?.type === + "radio" ? ( + + addFilter({ + column: currentFilter.column!, + operator: currentFilter.operator || "is", + value: val, + }) + } + > +
+ {keys + .find((key) => key.key === currentFilter.column) + ?.options?.map((option, idx) => { + const optionId = `${currentFilter.column}-${idx}`; + return ( +
+ + +
+ ); + })} +
+
) : ( Date: Sun, 1 Dec 2024 02:54:35 +0530 Subject: [PATCH 017/137] Add priority radio filter to OrderPlaced component and update initial data --- src/components/LabTest/OrderPlaced.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 3ddc1534ac4..b7a96a7fa21 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -52,8 +52,17 @@ export const OrderPlaced: React.FC = () => { operators: ["is", "is_not"], render_as: "badge", }, + { + key: "priority", + label: "Priority", + type: "radio", // New filter type + options: ["High", "Medium", "Low"], + operators: ["is"], // Typically, only "is" makes sense for radio + defaultOperator: "is", + }, ]; + // dummy data until we have API wired up const initialData = [ { specimenId: "SPEC009213", @@ -63,6 +72,7 @@ export const OrderPlaced: React.FC = () => { tests: "Complete Blood Count (CBC)", collector: "John Doe", status: "Pending", + priority: "High", }, { specimenId: "SPEC009412", @@ -72,6 +82,7 @@ export const OrderPlaced: React.FC = () => { tests: "COVID-19 PCR, Influenza Test", collector: "Jane Doe", status: "Collected", + priority: "Medium", }, { specimenId: "SPEC009425", @@ -81,6 +92,7 @@ export const OrderPlaced: React.FC = () => { tests: "Biopsy of tissue", collector: "Dr. Rajmohan", status: "Completed", + priority: "Low", }, { specimenId: "SPEC009876", @@ -90,6 +102,7 @@ export const OrderPlaced: React.FC = () => { tests: "COVID-19 PCR", collector: "Jeena Mathew", status: "Cancelled", + priority: "High", }, { specimenId: "SPEC001234", @@ -99,16 +112,18 @@ export const OrderPlaced: React.FC = () => { tests: "Lipid Profile", collector: "John Doe", status: "Pending", + priority: "Medium", }, ]; const [data, setData] = useState(initialData); + // Removed filters state as per previous conversation const handleFiltersChange = (appliedFilters: Filter[]) => { const filteredData = initialData.filter((row) => appliedFilters.every((filter) => { - const columnKey = filter.column as keyof typeof row; // Ensure column matches row keys - const value = row[columnKey]; // Access the row value + const columnKey = filter.column as keyof typeof row; + const value = row[columnKey]; if (filter.operator === "is") return value === filter.value; if (filter.operator === "is_not") return value !== filter.value; From 2c7b06b5c06f573e8b5b3de38dbf252f34b18700 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 2 Dec 2024 02:10:03 +0530 Subject: [PATCH 018/137] WIP: support multi select --- src/components/LabTest/OrderPlaced.tsx | 30 +++++-- src/components/LabTest/TableFilter.tsx | 117 ++++++++++++++++++------- 2 files changed, 104 insertions(+), 43 deletions(-) diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index b7a96a7fa21..3451e6d5f77 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -117,22 +117,34 @@ export const OrderPlaced: React.FC = () => { ]; const [data, setData] = useState(initialData); - // Removed filters state as per previous conversation + // Remove this function and use the actual API call to fetch data const handleFiltersChange = (appliedFilters: Filter[]) => { const filteredData = initialData.filter((row) => appliedFilters.every((filter) => { const columnKey = filter.column as keyof typeof row; const value = row[columnKey]; - if (filter.operator === "is") return value === filter.value; - if (filter.operator === "is_not") return value !== filter.value; - if (filter.operator === "contains") - return typeof value === "string" && value.includes(filter.value); - if (filter.operator === "does_not_contain") - return typeof value === "string" && !value.includes(filter.value); - if (filter.operator === "is_empty") return !value; - if (filter.operator === "is_not_empty") return !!value; + if (Array.isArray(filter.value)) { + if (filter.operator === "is any of") { + return filter.value.includes(value); + } + } else { + if (filter.operator === "is") return value === filter.value; + if (filter.operator === "is_not") return value !== filter.value; + if (filter.operator === "contains") + return ( + typeof value === "string" && + value.toLowerCase().includes(filter.value.toLowerCase()) + ); + if (filter.operator === "does_not_contain") + return ( + typeof value === "string" && + !value.toLowerCase().includes(filter.value.toLowerCase()) + ); + if (filter.operator === "is_empty") return !value; + if (filter.operator === "is_not_empty") return !!value; + } return true; }), ); diff --git a/src/components/LabTest/TableFilter.tsx b/src/components/LabTest/TableFilter.tsx index 1b3c0f69c74..44ce396627a 100644 --- a/src/components/LabTest/TableFilter.tsx +++ b/src/components/LabTest/TableFilter.tsx @@ -15,7 +15,7 @@ import { Separator } from "@/components/ui/separator"; export interface Filter { column: string; operator: string; - value: string; + value: string | string[]; } interface TableFilterProps { @@ -33,6 +33,7 @@ interface TableFilterProps { const TableFilter: React.FC = ({ keys, onFiltersChange }) => { const [filters, setFilters] = useState([]); const [currentFilter, setCurrentFilter] = useState>({}); + const [selectedOptions, setSelectedOptions] = useState([]); const addFilter = (filter: Filter) => { const updatedFilters = [...filters, filter]; @@ -47,7 +48,11 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { onFiltersChange(updatedFilters); }; - const updateFilter = (index: number, field: keyof Filter, value: string) => { + const updateFilter = ( + index: number, + field: keyof Filter, + value: string | string[], + ) => { const updatedFilters = [...filters]; updatedFilters[index] = { ...updatedFilters[index], [field]: value }; setFilters(updatedFilters); @@ -122,29 +127,51 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { variant="ghost" className="px-2 py-0 text-sm text-gray-700 hover:bg-gray-100" > - {filter.value} + {Array.isArray(filter.value) + ? filter.value.length > 1 + ? `${filter.value.length} selected` + : filter.value[0] + : filter.value} {keys.find((key) => key.key === filter.column)?.type === "checkbox" ? ( - keys - .find((key) => key.key === filter.column) - ?.options?.map((option) => ( -
- - updateFilter(index, "value", option) - } - /> - {option} -
- )) +
+ {keys + .find((key) => key.key === filter.column) + ?.options?.map((option) => ( +
+ { + let newValues = Array.isArray(filter.value) + ? [...filter.value] + : []; + if (checked) { + newValues.push(option); + } else { + newValues = newValues.filter( + (val) => val !== option, + ); + } + updateFilter(index, "value", newValues); + }} + /> + +
+ ))} +
) : keys.find((key) => key.key === filter.column)?.type === "radio" ? ( updateFilter(index, "value", val)} >
@@ -230,23 +257,45 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => {

Select Value

{keys.find((key) => key.key === currentFilter.column)?.type === "checkbox" ? ( - keys - .find((key) => key.key === currentFilter.column) - ?.options?.map((option) => ( -
- - addFilter({ - column: currentFilter.column!, - operator: currentFilter.operator || "is", - value: option, - }) - } - /> - {option} -
- )) +
+ {keys + .find((key) => key.key === currentFilter.column) + ?.options?.map((option) => ( +
+ { + let newValues = [...selectedOptions]; + if (checked) { + newValues.push(option); + } else { + newValues = newValues.filter( + (val) => val !== option, + ); + } + setSelectedOptions(newValues); + }} + /> + +
+ ))} + +
) : keys.find((key) => key.key === currentFilter.column)?.type === "radio" ? ( Date: Wed, 4 Dec 2024 03:07:38 +0530 Subject: [PATCH 019/137] Add shadcn Card and Collapsible components --- package-lock.json | 30 +++++++++++ package.json | 1 + src/components/ui/card.tsx | 83 +++++++++++++++++++++++++++++++ src/components/ui/collapsible.tsx | 9 ++++ 4 files changed, 123 insertions(+) create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/collapsible.tsx diff --git a/package-lock.json b/package-lock.json index 5c332e7a0fc..68e7a98f4c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-label": "^2.1.0", @@ -3696,6 +3697,35 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.1.tgz", + "integrity": "sha512-1///SnrfQHJEofLokyczERxQbWfCGQlQ2XsCZMucVs6it+lq9iw4vXy+uDn1edlb58cOZOWSldnfPAYcT4O/Yg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", diff --git a/package.json b/package.json index 2c9ce0cfa82..1bc1c0f796b 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-label": "^2.1.0", diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 00000000000..69acc1bc3b5 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 00000000000..5c28cbcc3bc --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; From f39490c0119369d01f64a3a2cb434da69e41a5d1 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Wed, 4 Dec 2024 03:13:12 +0530 Subject: [PATCH 020/137] WIP: Collect specimen page --- src/Routers/routes/LabTestRoutes.tsx | 2 + src/components/LabTest/CollectSpecimen.tsx | 219 +++++++++++++++++++++ src/components/LabTest/OrderPlaced.tsx | 3 +- src/components/LabTest/TableFilter.tsx | 2 +- 4 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 src/components/LabTest/CollectSpecimen.tsx diff --git a/src/Routers/routes/LabTestRoutes.tsx b/src/Routers/routes/LabTestRoutes.tsx index 95be026094d..e9caf3990d4 100644 --- a/src/Routers/routes/LabTestRoutes.tsx +++ b/src/Routers/routes/LabTestRoutes.tsx @@ -1,3 +1,4 @@ +import { CollectSpecimen } from "@/components/LabTest/CollectSpecimen"; import { LabTest } from "@/components/LabTest/Index"; import { AppRoutes } from "@/Routers/AppRouter"; @@ -6,6 +7,7 @@ const LabTestRoutes: AppRoutes = { "/lab_tests/order_placed": () => , "/lab_tests/specimen_collected": () => , "/lab_tests/sent_to_lab": () => , + "/lab_tests/:orderId": ({ orderId }) => , }; export default LabTestRoutes; diff --git a/src/components/LabTest/CollectSpecimen.tsx b/src/components/LabTest/CollectSpecimen.tsx new file mode 100644 index 00000000000..352386f293c --- /dev/null +++ b/src/components/LabTest/CollectSpecimen.tsx @@ -0,0 +1,219 @@ +import { ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"; +import React from "react"; + +import { Button } from "@/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; + +interface CollectSpecimenProps { + orderId: string; +} + +export const CollectSpecimen: React.FC = ({ + orderId, +}) => { + const [isOpen, setIsOpen] = React.useState(false); + return ( +
+ {/* Left - Navigation/Progress Bar */} + + + {/* Right - Main Content */} +
+ {/* Header Section */} + + +
+
+

Collect Specimen

+
+
+
+ Specimen Collected +
+ +
+
+ + {/* Patient Details Section */} +
+
+

Patient Name

+

John Honai

+
+
+

UHID

+

T105690908240017

+
+
+

Age/Sex

+

58/Male

+
+
+ + +
+
+
+ + Order id + +
+ + {orderId} + + + Pending + +
+
+
+ + Specimen to be collected:{" "} + Blood + +
+ + + +
+
+
+ + {/* Expanded Content */} + +
+
+ {/* Left Section */} +
+

+ Test +

+

Complete Blood Count (CBC)

+
+ + {/* Vertical Divider */} + {/*
*/} + + {/* Right Section */} +
+

+ Order Placed by +

+
+

+ Dr. Jahnab Dutta, +

+

+ Cardiologist +

+
+ +

+ Order Date/Time +

+

+ 28-Nov-2024, 2:30PM +

+ +

+ Priority +

+ + Stat + +
+
+ {/* Note Section */} +
+

Note:

+

+ Prescribed CBC to check for anemia or infection and LFT to + evaluate liver health due to complaints of fatigue and mild + abdominal discomfort. +

+
+
+
+
+
+
+
+ ); +}; diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 3451e6d5f77..79dec255958 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -1,3 +1,4 @@ +import { navigate } from "raviger"; import React, { useState } from "react"; import { Button } from "@/components/ui/button"; @@ -153,7 +154,7 @@ export const OrderPlaced: React.FC = () => { }; const handleAction = (row: Record) => { - alert(`Collect Specimen for ${row.orderId}`); + navigate(`/lab_tests/${row.orderId}`); }; return ( diff --git a/src/components/LabTest/TableFilter.tsx b/src/components/LabTest/TableFilter.tsx index 44ce396627a..712728a9aa7 100644 --- a/src/components/LabTest/TableFilter.tsx +++ b/src/components/LabTest/TableFilter.tsx @@ -65,7 +65,7 @@ const TableFilter: React.FC = ({ keys, onFiltersChange }) => { }; return ( -
+
{/* Display Applied Filters */}
{filters.map((filter, index) => ( From 462c3ae7a7e96c9457ee2cb35bfdb33235d761c9 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Fri, 6 Dec 2024 04:20:31 +0530 Subject: [PATCH 021/137] Add sample collection UI and refactors --- src/Routers/routes/LabTestRoutes.tsx | 2 +- src/components/LabTest/CollectSpecimen.tsx | 356 ++++++++++++++------- src/components/LabTest/OrderPlaced.tsx | 2 +- 3 files changed, 250 insertions(+), 110 deletions(-) diff --git a/src/Routers/routes/LabTestRoutes.tsx b/src/Routers/routes/LabTestRoutes.tsx index e9caf3990d4..9f4c39d6d43 100644 --- a/src/Routers/routes/LabTestRoutes.tsx +++ b/src/Routers/routes/LabTestRoutes.tsx @@ -7,7 +7,7 @@ const LabTestRoutes: AppRoutes = { "/lab_tests/order_placed": () => , "/lab_tests/specimen_collected": () => , "/lab_tests/sent_to_lab": () => , - "/lab_tests/:orderId": ({ orderId }) => , + "/lab_tests/:patientId/orders": ({ patientId }) => , }; export default LabTestRoutes; diff --git a/src/components/LabTest/CollectSpecimen.tsx b/src/components/LabTest/CollectSpecimen.tsx index 352386f293c..95596f8a435 100644 --- a/src/components/LabTest/CollectSpecimen.tsx +++ b/src/components/LabTest/CollectSpecimen.tsx @@ -1,5 +1,6 @@ import { ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"; import React from "react"; +import { FaDroplet } from "react-icons/fa6"; import { Button } from "@/components/ui/button"; import { @@ -7,15 +8,43 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; +import { Input } from "@/components/ui/input"; -interface CollectSpecimenProps { +interface Order { orderId: string; + specimen: string; + status: string; } -export const CollectSpecimen: React.FC = ({ - orderId, -}) => { - const [isOpen, setIsOpen] = React.useState(false); +// Mock Data +const orders: Order[] = [ + { orderId: "CARE_LAB-001", specimen: "Blood", status: "Pending" }, + { orderId: "CARE_LAB-002", specimen: "Urine", status: "Pending" }, +]; + +export const CollectSpecimen: React.FC = () => { + const [openOrders, setOpenOrders] = React.useState>( + {}, + ); + const [samples, setSamples] = React.useState>( + orders.reduce((acc, order) => ({ ...acc, [order.orderId]: 1 }), {}), + ); + + const toggleOrder = (orderId: string) => { + setOpenOrders((prev) => ({ ...prev, [orderId]: !prev[orderId] })); + }; + + const incrementSample = (orderId: string) => { + setSamples((prev) => ({ ...prev, [orderId]: prev[orderId] + 1 })); + }; + + const decrementSample = (orderId: string) => { + setSamples((prev) => ({ + ...prev, + [orderId]: Math.max(1, prev[orderId] - 1), + })); + }; + return (
{/* Left - Navigation/Progress Bar */} @@ -94,125 +123,236 @@ export const CollectSpecimen: React.FC = ({

Collect Specimen

-
- Specimen Collected -
- + +
{/* Patient Details Section */} -
-
-

Patient Name

-

John Honai

-
-
-

UHID

-

T105690908240017

+
+
+
+

Patient Name

+

John Honai

+
+
+

UHID

+

T105690908240017

+
+
+

Age/Sex

+

58/Male

+
-

Age/Sex

-

58/Male

+ + 2/2 orders Pending +
+ {orders.map((order) => ( +
+ toggleOrder(order.orderId)} + > +
+
+
+
+ + Order id + +
+ + {order.orderId} + + + Pending + +
+
+
+ + Specimen to be collected:{" "} + + Blood + + +
+ + + +
+
+
- -
-
-
- - Order id - -
- - {orderId} - - - Pending - -
-
-
- - Specimen to be collected:{" "} - Blood - -
- - - -
-
-
- {/* Expanded Content */} - -
-
- {/* Left Section */} -
-

- Test -

-

Complete Blood Count (CBC)

-
+
+ {/* Specimen Section */} +
+
+

+ Specimen: +

+

+ Number of samples +

+
- {/* Vertical Divider */} - {/*
*/} - - {/* Right Section */} -
-

- Order Placed by -

-
-

- Dr. Jahnab Dutta, -

-

- Cardiologist -

-
+
+
+ + {/* Specimen Icon */} + + + + Blood + +
+
+ + + {samples[order.orderId]} + + +
+
+
-

- Order Date/Time -

-

- 28-Nov-2024, 2:30PM -

- -

- Priority -

- - Stat - -
-
- {/* Note Section */} -
-

Note:

-

- Prescribed CBC to check for anemia or infection and LFT to - evaluate liver health due to complaints of fatigue and mild - abdominal discomfort. -

+ {Array.from( + { length: samples[order.orderId] }, + (_, index) => ( +
+
+
+

+ Sample {index + 1} +

+ + Collection Pending + +
+
+
+

+ Barcode +

+
+
+ +
+
+ OR +
+
+ +
+
+
+
+ ), + )} +
+
+
- +
- + ))}
); diff --git a/src/components/LabTest/OrderPlaced.tsx b/src/components/LabTest/OrderPlaced.tsx index 79dec255958..f54dcbacc97 100644 --- a/src/components/LabTest/OrderPlaced.tsx +++ b/src/components/LabTest/OrderPlaced.tsx @@ -154,7 +154,7 @@ export const OrderPlaced: React.FC = () => { }; const handleAction = (row: Record) => { - navigate(`/lab_tests/${row.orderId}`); + navigate(`/lab_tests/:patientId/orders`); }; return ( From 59e18d435673c0fb12b894758e4200362c5bf48f Mon Sep 17 00:00:00 2001 From: yash-learner Date: Fri, 6 Dec 2024 04:29:11 +0530 Subject: [PATCH 022/137] Fix layout alignment --- src/components/LabTest/CollectSpecimen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LabTest/CollectSpecimen.tsx b/src/components/LabTest/CollectSpecimen.tsx index 95596f8a435..c276ed26138 100644 --- a/src/components/LabTest/CollectSpecimen.tsx +++ b/src/components/LabTest/CollectSpecimen.tsx @@ -107,7 +107,7 @@ export const CollectSpecimen: React.FC = () => { {/* Right - Main Content */} -
+
{/* Header Section */}
-
-

Collect Specimen

-
+

Collect Specimen

- +
@@ -299,52 +344,132 @@ export const CollectSpecimen: React.FC = () => {
- - {Array.from( - { length: samples[order.orderId] }, - (_, index) => ( -
-
-
-

- Sample {index + 1} -

- - Collection Pending - -
-
-
-

- Barcode -

-
-
- -
-
- OR +
+ {Array.from( + { length: samples[order.orderId] }, + (_, index) => { + const sampleCollected = + order.samples[index].status === "Collected"; + return ( +
+
+
+

+ {order.samples[index].status === + "Collected" + ? order.specimenId + : `Sample ${index + 1}`} +

+ + {order.samples[index].status} + +
-
- +
+
+

+ Barcode +

+ + {sampleCollected ? ( + + ) : null} +
+ {sampleCollected ? ( +
+
+ {/* Success Badge */} +
+ + Success + + {/* Success Message */} + + Barcode scanned successfully + +
+ + {/* Barcode */} +
+ {/* Barcode Icon */} + filter + + P2828656-E + +
+
+
+
+

+ Tube Type +

+ + EDTA + +
+
+

+ Test +

+ + Liver Function Test (LFT) + +
+
+

+ Collection Date/Time +

+ + 28-Nov-2024, 2:30PM + +
+
+
+ ) : ( +
+
+ +
+
+ OR +
+
+ +
+
+ )}
-
-
- ), - )} + ); + }, + )} +
From c245b20ad3e08dc1c2c479dda9ff5fa706a20846 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 9 Dec 2024 01:55:24 +0530 Subject: [PATCH 025/137] Add SendSpecimen route and enhance SpecimenCollected component --- src/Routers/routes/LabTestRoutes.tsx | 2 + src/components/LabTest/SpecimenCollected.tsx | 205 ++++++++++++++++--- 2 files changed, 175 insertions(+), 32 deletions(-) diff --git a/src/Routers/routes/LabTestRoutes.tsx b/src/Routers/routes/LabTestRoutes.tsx index 9f4c39d6d43..fbc676589d9 100644 --- a/src/Routers/routes/LabTestRoutes.tsx +++ b/src/Routers/routes/LabTestRoutes.tsx @@ -1,5 +1,6 @@ import { CollectSpecimen } from "@/components/LabTest/CollectSpecimen"; import { LabTest } from "@/components/LabTest/Index"; +import { SendSpecimen } from "@/components/LabTest/SendSpecimen"; import { AppRoutes } from "@/Routers/AppRouter"; @@ -7,6 +8,7 @@ const LabTestRoutes: AppRoutes = { "/lab_tests/order_placed": () => , "/lab_tests/specimen_collected": () => , "/lab_tests/sent_to_lab": () => , + "/lab_tests/send_to_lab": () => , "/lab_tests/:patientId/orders": ({ patientId }) => , }; diff --git a/src/components/LabTest/SpecimenCollected.tsx b/src/components/LabTest/SpecimenCollected.tsx index 4012f5be0cd..e7025e788a6 100644 --- a/src/components/LabTest/SpecimenCollected.tsx +++ b/src/components/LabTest/SpecimenCollected.tsx @@ -1,39 +1,180 @@ -import { - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; +import { navigate } from "raviger"; +import React, { useState } from "react"; + +import { Button } from "@/components/ui/button"; + +import { DataTable } from "@/components/LabTest/DataTable"; +import TableFilter, { Filter } from "@/components/LabTest/TableFilter"; export const SpecimenCollected: React.FC = () => { - const data = [ - { id: 1, name: "John Doe", age: 28 }, - { id: 2, name: "Jane Smith", age: 34 }, - { id: 3, name: "Emily Johnson", age: 40 }, + const keys = [ + { + key: "specimenId", + label: "Specimen ID", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "orderId", + label: "Order ID", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "patientName", + label: "Patient Name", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "specimen", + label: "Specimen", + type: "checkbox", + options: ["Blood", "Swab", "Tissue"], + operators: ["is", "is_not"], + }, + { + key: "tests", + label: "Tests", + type: "text", + operators: ["is", "contains", "does_not_contain"], + }, + { + key: "collector", + label: "Collector", + type: "text", + operators: ["is", "is_not", "contains"], + }, + { + key: "status", + label: "Status", + type: "checkbox", + options: ["Pending", "Collected", "Completed", "Cancelled"], + operators: ["is", "is_not"], + render_as: "badge", + }, + { + key: "priority", + label: "Priority", + type: "radio", // New filter type + options: ["High", "Medium", "Low"], + operators: ["is"], // Typically, only "is" makes sense for radio + defaultOperator: "is", + }, ]; + // dummy data until we have API wired up + const initialData = [ + { + specimenId: "SPEC009213", + orderId: "CARE_LAB-001", + patientName: "John Honai", + specimen: "Blood", + tests: "Complete Blood Count (CBC)", + collector: "John Doe", + status: "Collected", + priority: "High", + }, + { + specimenId: "SPEC009412", + orderId: "CARE_LAB-002", + patientName: "Jane Doe", + specimen: "Swab", + tests: "COVID-19 PCR, Influenza Test", + collector: "Jane Doe", + status: "Collected", + priority: "Medium", + }, + { + specimenId: "SPEC009425", + orderId: "CARE_LAB-003", + patientName: "Narayani", + specimen: "Tissue", + tests: "Biopsy of tissue", + collector: "Dr. Rajmohan", + status: "Collected", + priority: "Low", + }, + { + specimenId: "SPEC009876", + orderId: "CARE_LAB-004", + patientName: "Tintu Ukken", + specimen: "Swab", + tests: "COVID-19 PCR", + collector: "Jeena Mathew", + status: "Collected", + priority: "High", + }, + { + specimenId: "SPEC001234", + orderId: "CARE_LAB-005", + patientName: "Paul Varghese", + specimen: "Blood", + tests: "Lipid Profile", + collector: "John Doe", + status: "Collected", + priority: "Medium", + }, + ]; + + const [data, setData] = useState(initialData); + + // Remove this function and use the actual API call to fetch data + const handleFiltersChange = (appliedFilters: Filter[]) => { + const filteredData = initialData.filter((row) => + appliedFilters.every((filter) => { + const columnKey = filter.column as keyof typeof row; + const value = row[columnKey]; + + if (Array.isArray(filter.value)) { + if (filter.operator === "is any of") { + return filter.value.includes(value); + } + } else { + if (filter.operator === "is") return value === filter.value; + if (filter.operator === "is_not") return value !== filter.value; + if (filter.operator === "contains") + return ( + typeof value === "string" && + value.toLowerCase().includes(filter.value.toLowerCase()) + ); + if (filter.operator === "does_not_contain") + return ( + typeof value === "string" && + !value.toLowerCase().includes(filter.value.toLowerCase()) + ); + if (filter.operator === "is_empty") return !value; + if (filter.operator === "is_not_empty") return !!value; + } + return true; + }), + ); + + setData(filteredData); + }; + return ( - - A simple example table - - - ID - Name - Age - - - - {data.map((row) => ( - - {row.id} - {row.name} - {row.age} - - ))} - -
+
+ {/* Table Filter */} +
+ + + +
+ {/* Data Table */} + ({ + label: key.label, + key: key.key, + render_as: key.render_as, + }))} + data={data} + /> +
); }; From 26913b9052505c858b90f3b40dab20457f3cca07 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 9 Dec 2024 01:56:43 +0530 Subject: [PATCH 026/137] Rename page title from "Lab Tests" to "Lab Orders" --- src/components/LabTest/Index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LabTest/Index.tsx b/src/components/LabTest/Index.tsx index f8e6f29def4..c30dbcd200a 100644 --- a/src/components/LabTest/Index.tsx +++ b/src/components/LabTest/Index.tsx @@ -26,7 +26,7 @@ export const LabTest = () => { }; return ( - +
{renderTabContent()}
diff --git a/src/components/LabTest/ReceiveSpecimen.tsx b/src/components/LabTest/ReceiveSpecimen.tsx new file mode 100644 index 00000000000..782eda5aaa9 --- /dev/null +++ b/src/components/LabTest/ReceiveSpecimen.tsx @@ -0,0 +1,152 @@ +import { ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"; +import React from "react"; + +import { Button } from "@/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +// Mock Labs Data +const labs = [ + { id: "lab-001", name: "Central Lab" }, + { id: "lab-002", name: "Northside Lab" }, + { id: "lab-003", name: "Southside Lab" }, + { id: "lab-004", name: "Eastside Lab" }, + { id: "lab-005", name: "Westside Lab" }, +]; + +export const ReceiveSpecimen: React.FC = () => { + return ( +
+ +

Receive Specimen at Lab

+
+
+ + +
+ +
+
+ + +
+
+
+
+ + Specimen id + +
+ + SPEC009213 + + + Received at Lab + +
+
+
+
+ + + +
+
+
+ + {/* Expanded Content */} + +
+
+
+ {/* Left Section */} +
+

+ Test +

+

+ Complete Blood Count (CBC) +

+
+ + {/* Right Section */} +
+

+ Order Placed by +

+
+

+ Dr. Jahnab Dutta, +

+

+ Cardiologist +

+
+ +

+ Order Date/Time +

+

+ 28-Nov-2024, 2:30PM +

+ +

+ Priority +

+ + Stat + +
+
+ {/* Note Section */} +
+

+ Note: +

+

+ Prescribed CBC to check for anemia or infection and LFT + to evaluate liver health due to complaints of fatigue + and mild abdominal discomfort. +

+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/src/components/LabTest/ReceivedAtLab.tsx b/src/components/LabTest/ReceivedAtLab.tsx new file mode 100644 index 00000000000..8b91f1bd5a2 --- /dev/null +++ b/src/components/LabTest/ReceivedAtLab.tsx @@ -0,0 +1,180 @@ +import { navigate } from "raviger"; +import React, { useState } from "react"; + +import { Button } from "@/components/ui/button"; + +import { DataTable } from "@/components/LabTest/DataTable"; +import TableFilter, { Filter } from "@/components/LabTest/TableFilter"; + +export const ReceivedAtLab: React.FC = () => { + const keys = [ + { + key: "specimenId", + label: "Specimen ID", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "orderId", + label: "Order ID", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "patientName", + label: "Patient Name", + type: "text", + operators: ["is", "is_not", "contains", "does_not_contain"], + }, + { + key: "specimen", + label: "Specimen", + type: "checkbox", + options: ["Blood", "Swab", "Tissue"], + operators: ["is", "is_not"], + }, + { + key: "tests", + label: "Tests", + type: "text", + operators: ["is", "contains", "does_not_contain"], + }, + { + key: "collector", + label: "Collector", + type: "text", + operators: ["is", "is_not", "contains"], + }, + { + key: "status", + label: "Status", + type: "checkbox", + options: ["Pending", "Collected", "Completed", "Cancelled"], + operators: ["is", "is_not"], + render_as: "badge", + }, + { + key: "priority", + label: "Priority", + type: "radio", // New filter type + options: ["High", "Medium", "Low"], + operators: ["is"], // Typically, only "is" makes sense for radio + defaultOperator: "is", + }, + ]; + + // dummy data until we have API wired up + const initialData = [ + { + specimenId: "SPEC009213", + orderId: "CARE_LAB-001", + patientName: "John Honai", + specimen: "Blood", + tests: "Complete Blood Count (CBC)", + collector: "John Doe", + status: "Transit", + priority: "High", + }, + { + specimenId: "SPEC009412", + orderId: "CARE_LAB-002", + patientName: "Jane Doe", + specimen: "Swab", + tests: "COVID-19 PCR, Influenza Test", + collector: "Jane Doe", + status: "Transit", + priority: "Medium", + }, + { + specimenId: "SPEC009425", + orderId: "CARE_LAB-003", + patientName: "Narayani", + specimen: "Tissue", + tests: "Biopsy of tissue", + collector: "Dr. Rajmohan", + status: "Transit", + priority: "Low", + }, + { + specimenId: "SPEC009876", + orderId: "CARE_LAB-004", + patientName: "Tintu Ukken", + specimen: "Swab", + tests: "COVID-19 PCR", + collector: "Jeena Mathew", + status: "Transit", + priority: "High", + }, + { + specimenId: "SPEC001234", + orderId: "CARE_LAB-005", + patientName: "Paul Varghese", + specimen: "Blood", + tests: "Lipid Profile", + collector: "John Doe", + status: "Transit", + priority: "Medium", + }, + ]; + + const [data, setData] = useState(initialData); + + // Remove this function and use the actual API call to fetch data + const handleFiltersChange = (appliedFilters: Filter[]) => { + const filteredData = initialData.filter((row) => + appliedFilters.every((filter) => { + const columnKey = filter.column as keyof typeof row; + const value = row[columnKey]; + + if (Array.isArray(filter.value)) { + if (filter.operator === "is any of") { + return filter.value.includes(value); + } + } else { + if (filter.operator === "is") return value === filter.value; + if (filter.operator === "is_not") return value !== filter.value; + if (filter.operator === "contains") + return ( + typeof value === "string" && + value.toLowerCase().includes(filter.value.toLowerCase()) + ); + if (filter.operator === "does_not_contain") + return ( + typeof value === "string" && + !value.toLowerCase().includes(filter.value.toLowerCase()) + ); + if (filter.operator === "is_empty") return !value; + if (filter.operator === "is_not_empty") return !!value; + } + return true; + }), + ); + + setData(filteredData); + }; + + return ( +
+ {/* Table Filter */} +
+ + + +
+ {/* Data Table */} + ({ + label: key.label, + key: key.key, + render_as: key.render_as, + }))} + data={data} + /> +
+ ); +}; From 6c0fecd509f347f083945ce7c8d9b5123c30cd7a Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 9 Dec 2024 02:37:30 +0530 Subject: [PATCH 029/137] Remove mock labs data from ReceiveSpecimen component --- src/components/LabTest/ReceiveSpecimen.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/LabTest/ReceiveSpecimen.tsx b/src/components/LabTest/ReceiveSpecimen.tsx index 782eda5aaa9..80e6c082441 100644 --- a/src/components/LabTest/ReceiveSpecimen.tsx +++ b/src/components/LabTest/ReceiveSpecimen.tsx @@ -10,15 +10,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -// Mock Labs Data -const labs = [ - { id: "lab-001", name: "Central Lab" }, - { id: "lab-002", name: "Northside Lab" }, - { id: "lab-003", name: "Southside Lab" }, - { id: "lab-004", name: "Eastside Lab" }, - { id: "lab-005", name: "Westside Lab" }, -]; - export const ReceiveSpecimen: React.FC = () => { return (
From caf36b7412876c45fc193d1430be6b08c8ceb763 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Mon, 9 Dec 2024 20:09:20 +0530 Subject: [PATCH 030/137] Get remote button working and move some values --- src/components/LabTest/CollectSpecimen.tsx | 339 ++++++++++++--------- 1 file changed, 193 insertions(+), 146 deletions(-) diff --git a/src/components/LabTest/CollectSpecimen.tsx b/src/components/LabTest/CollectSpecimen.tsx index 00081add380..7d875b25082 100644 --- a/src/components/LabTest/CollectSpecimen.tsx +++ b/src/components/LabTest/CollectSpecimen.tsx @@ -16,77 +16,117 @@ import { import { Input } from "@/components/ui/input"; interface Sample { + id: string; status: string; barcode?: string; tubeType?: string; - test: string; collectionDateTime?: string; + specimenType: string; } interface Order { orderId: string; - specimen: string; - specimenId: string; status: string; + test: string; samples: Sample[]; } -// Mock Data -const orders: Order[] = [ +// Updated Mock Data +const initialOrders: Order[] = [ { orderId: "CARE_LAB-001", - specimen: "Blood", - specimenId: "SPEC009213", status: "Pending", + test: "Complete Blood Count (CBC)", samples: [ { + id: "SPEC009213", + specimenType: "Blood", status: "Collection Pending", - test: "Complete Blood Count (CBC)", }, { + id: "SPEC009213-2", + specimenType: "Blood", status: "Collected", barcode: "123456789", tubeType: "EDTA", - test: "Liver Function Test (LFT)", collectionDateTime: "28-Nov-2024, 2:30PM", }, ], }, { orderId: "CARE_LAB-002", - specimen: "Urine", - specimenId: "SPEC009412", status: "Pending", + test: "Urine Analysis", samples: [ { + id: "SPEC009412", + specimenType: "Urine", status: "Collection Pending", - test: "Urine Analysis", }, ], }, ]; export const CollectSpecimen: React.FC = () => { + // Store orders in state so we can modify them + const [orderData, setOrderData] = React.useState(initialOrders); + const [openOrders, setOpenOrders] = React.useState>( {}, ); - const [samples, setSamples] = React.useState>( - orders.reduce((acc, order) => ({ ...acc, [order.orderId]: 1 }), {}), - ); const toggleOrder = (orderId: string) => { setOpenOrders((prev) => ({ ...prev, [orderId]: !prev[orderId] })); }; + // Add a new specimen (sample) to the order const incrementSample = (orderId: string) => { - setSamples((prev) => ({ ...prev, [orderId]: prev[orderId] + 1 })); + setOrderData((prevOrders) => + prevOrders.map((order) => { + if (order.orderId === orderId) { + const specimenType = order.samples[0]?.specimenType || "Unknown"; + + const newSample: Sample = { + id: "SPEC" + Date.now(), + specimenType, + status: "Collection Pending", + }; + return { ...order, samples: [...order.samples, newSample] }; + } + return order; + }), + ); }; const decrementSample = (orderId: string) => { - setSamples((prev) => ({ - ...prev, - [orderId]: Math.max(1, prev[orderId] - 1), - })); + setOrderData((prevOrders) => + prevOrders.map((order) => { + if (order.orderId === orderId && order.samples.length > 1) { + return { ...order, samples: order.samples.slice(0, -1) }; + } + return order; + }), + ); + }; + + // Handle removing a collected sample: set its status to "Collection Pending" + const handleRemoveCollectedSample = ( + orderId: string, + sampleIndex: number, + ) => { + setOrderData((prevOrders) => + prevOrders.map((order) => { + if (order.orderId === orderId) { + return { + ...order, + samples: order.samples.map((s, i) => + i === sampleIndex ? { ...s, status: "Collection Pending" } : s, + ), + }; + } + return order; + }), + ); }; return ( @@ -191,21 +231,22 @@ export const CollectSpecimen: React.FC = () => {
- 2/2 orders Pending + {orderData.filter((o) => o.status === "Pending").length}/ + {orderData.length} orders Pending
- {orders.map((order) => ( + {orderData.map((order) => (
toggleOrder(order.orderId)} >
@@ -217,7 +258,7 @@ export const CollectSpecimen: React.FC = () => { {order.orderId} - Pending + {order.status}
@@ -225,7 +266,7 @@ export const CollectSpecimen: React.FC = () => { Specimen to be collected:{" "} - Blood + {order.samples[0]?.specimenType}
@@ -254,9 +295,7 @@ export const CollectSpecimen: React.FC = () => {

Test

-

- Complete Blood Count (CBC) -

+

{order.test}

{/* Right Section */} @@ -316,11 +355,10 @@ export const CollectSpecimen: React.FC = () => {
- {/* Specimen Icon */} - Blood + {order.samples[0]?.specimenType}
@@ -332,7 +370,7 @@ export const CollectSpecimen: React.FC = () => { - - {samples[order.orderId]} + {order.samples.length}
- {Array.from( - { length: samples[order.orderId] }, - (_, index) => { - const sampleCollected = - order.samples[index].status === "Collected"; - return ( -
-
-
-

- {order.samples[index].status === - "Collected" - ? order.specimenId - : `Sample ${index + 1}`} -

- - {order.samples[index].status} - -
+ {order.samples.map((sample, index) => { + const sampleCollected = + sample.status === "Collected"; + return ( +
+
+
+

+ {sampleCollected + ? sample.id + : `Sample ${index + 1}`} +

+ + {sample.status} +
-
-
-

- Barcode -

+
+
+
+

+ Barcode +

- {sampleCollected ? ( - + )} +
+ {sampleCollected ? ( +
+
+ {/* Success Badge */} +
+ + Success + + {/* Success Message */} + + Barcode scanned successfully - - ) : null} -
- {sampleCollected ? ( -
-
- {/* Success Badge */} -
- - Success - - {/* Success Message */} - - Barcode scanned successfully - -
- - {/* Barcode */} -
- {/* Barcode Icon */} - filter - - P2828656-E - -
-
-
-

- Tube Type -

- - EDTA - -
-
-

- Test -

- - Liver Function Test (LFT) - -
-
-

- Collection Date/Time -

- - 28-Nov-2024, 2:30PM - -
+ + {/* Barcode */} +
+ filter + + {sample.barcode} +
- ) : ( -
-
- +
+
+

+ Tube Type +

+ + {sample.tubeType} +
-
- OR +
+

+ Test +

+ + {order.test} +
-
- +
+

+ Collection Date/Time +

+ + {sample.collectionDateTime} +
- )} -
+
+ ) : ( +
+
+ +
+
+ OR +
+
+ +
+
+ )}
- ); - }, - )} +
+ ); + })}
From 18653e7bac316bc074e5360ec5a4c521a7e81e4a Mon Sep 17 00:00:00 2001 From: yash-learner Date: Tue, 10 Dec 2024 02:19:42 +0530 Subject: [PATCH 031/137] Replace Cross2Icon with CrossCircledIcon --- src/components/LabTest/CollectSpecimen.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LabTest/CollectSpecimen.tsx b/src/components/LabTest/CollectSpecimen.tsx index 7d875b25082..583d702951e 100644 --- a/src/components/LabTest/CollectSpecimen.tsx +++ b/src/components/LabTest/CollectSpecimen.tsx @@ -2,7 +2,7 @@ import { ArrowRightIcon, ChevronDownIcon, ChevronUpIcon, - Cross2Icon, + CrossCircledIcon, } from "@radix-ui/react-icons"; import React from "react"; import { FaDroplet } from "react-icons/fa6"; @@ -429,7 +429,7 @@ export const CollectSpecimen: React.FC = () => { ) } > - + Remove From 8806e57d90f88595f4eaa9556e902067749eebb1 Mon Sep 17 00:00:00 2001 From: yash-learner Date: Tue, 10 Dec 2024 02:22:05 +0530 Subject: [PATCH 032/137] Add more UI elements in ReceiveSpecimen --- src/components/LabTest/ReceiveSpecimen.tsx | 337 +++++++++++++++++---- 1 file changed, 276 insertions(+), 61 deletions(-) diff --git a/src/components/LabTest/ReceiveSpecimen.tsx b/src/components/LabTest/ReceiveSpecimen.tsx index 80e6c082441..e012b0af5b1 100644 --- a/src/components/LabTest/ReceiveSpecimen.tsx +++ b/src/components/LabTest/ReceiveSpecimen.tsx @@ -1,6 +1,13 @@ -import { ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"; +import { + CheckCircledIcon, + ChevronDownIcon, + ChevronUpIcon, + CrossCircledIcon, +} from "@radix-ui/react-icons"; import React from "react"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + import { Button } from "@/components/ui/button"; import { Collapsible, @@ -9,10 +16,34 @@ import { } from "@/components/ui/collapsible"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; export const ReceiveSpecimen: React.FC = () => { + const [isBarcodeScanned, setIsBarcodeScanned] = React.useState(false); + + const handleScan = () => { + // Simulate barcode scanning success + setIsBarcodeScanned(true); + }; + + const specimenIntegrityChecks = [ + { parameter: "Clotting", options: ["No Clotting", "Clotted"] }, + { parameter: "Hemolysis", options: ["No Hemolysis", "Hemolyzed"] }, + { parameter: "Volume", options: ["Sufficient", "Insufficient"] }, + { parameter: "Labeling", options: ["Correct", "Incorrect"] }, + { parameter: "Container Condition", options: ["Intact", "Damaged"] }, + ]; + return ( -
+

Receive Specimen at Lab

-
- - -
- + {isBarcodeScanned ? ( +
+ {/* Barcode Success Message */} +
+
+ + Success + + + Barcode Scanned Successfully + +
+
+ +
+ {/* Specimen ID */} +
+

+ Specimen id +

+

+ SPC122532 +

+
+ + {/* Specimen Type */} +
+

+ Specimen type +

+

Blood

+
+ + {/* Date of Collection */} +
+

+ Date of collection +

+

+ 24 Nov 2024 +

+
+ + {/* Patient Name & ID */} +
+

+ Patient Name, ID +

+

+ John Honai +

+

T105690908240017

+
+ + {/* Order ID */} +
+

Order ID

+

+ CARE_LAB-001 +

+
+
+ + {/* Specimen Information */} +
+ {/* Specimen Integrity Check */} +
+
+ {/* Test and Tube Type */} +
+
+

+ Test +

+

+ Complete Blood Count (CBC) +

+
+
+

+ Tube Type +

+

+ EDTA +

+
+
+ + {/* Instruction Box */} +
+
+ +
+

+ Verify specimen integrity, including hemolysis, sufficient + volume, correct labeling, and proper container use, before + accepting or rejecting. +

+
+
+ + + + + Parameter + Evaluation + Notes (Optional) + + + + {specimenIntegrityChecks.map(({ parameter, options }) => ( + + {parameter} + + + {options.map((option) => ( +
+ + +
+ ))} +
+
+ + + +
+ ))} +
+
+ + {/* Action Buttons */} +
+ +
+ + +
+
+
+
+
+ ) : ( +
+ + { + if (e.key === "Enter") handleScan(); + }} + /> +
+ )}