Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b6d569a
Allow navigating to specific visit without automatically switching to…
nathantew14 Dec 11, 2025
2da509f
Improve visual consistency for PatientInfoHeaderSection buttons
nathantew14 Dec 11, 2025
4543dc0
Increase threshhold to 2 hrs for showing relative date for latest visit
nathantew14 Dec 11, 2025
50c5b58
Make approving/rejecting orders only update the order itself
noahseethorcodes Dec 11, 2025
8923231
Fix bug in editing consult
pohanson Dec 11, 2025
c98c044
Add documentation for PatientForm
nathantew14 Dec 11, 2025
830f511
Rename some fields for clarity
nathantew14 Dec 11, 2025
ffbce58
Add toasts
noahseethorcodes Dec 11, 2025
1ed8598
Add loading indicators to pharm orders' button to patient record
nathantew14 Dec 11, 2025
a730477
Modify onClick param to trigger loading state in Button component
keiran0 Dec 11, 2025
4da6349
Add loading indicator on edit patient submission
keiran0 Dec 11, 2025
f1bac78
Remove unused import
keiran0 Dec 11, 2025
5d7b0b2
Merge pull request #222 from NUS-Project-SaBai/fix-multiple-referral
pohanson Dec 12, 2025
f3e264b
Remove console logs and add back default prop value for PatientForm
keiran0 Dec 12, 2025
37548a5
Merge pull request #223 from NUS-Project-SaBai:improve-patient-header…
pohanson Dec 12, 2025
764bb36
Merge pull request #221 from NUS-Project-SaBai/noah/pharm-orders-reload
nathantew14 Dec 12, 2025
ae074b6
Remove unused library
nathantew14 Dec 12, 2025
8a0284e
Merge pull request #225 from NUS-Project-SaBai/loadingSubmitButtons
pohanson Dec 12, 2025
1daeda7
Merge remote-tracking branch 'origin/noah/pharm-orders-reload' into n…
nathantew14 Dec 12, 2025
24b78ff
Merge pull request #226 from NUS-Project-SaBai/noah/pharm-orders-reload
nathantew14 Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions src/app/pharmacy/orders/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import toast from 'react-hot-toast';
import { fetchAllPatientMedicationOrders } from './api';
import { Switch } from '@/components/ui/switch';
import Link from 'next/link';
import { LoadingLink } from '@/components/LoadingLink';

type OrderRowData = {
patient: {
Expand Down Expand Up @@ -104,6 +104,38 @@ export default function OrdersPage() {
return () => clearInterval(intervalId);
}, [isAutoRefreshEnabled]);

const handleApproveOrRejectOrder = (orderId: number) => {
setOrderRowData(prev =>
prev
.map(row => ({
...row,
data: row.data
.map(visit => ({
...visit,
orders: visit.orders.filter(order => order.order_id !== orderId),
}))
// Drop visits that no longer have any orders
.filter(visit => visit.orders.length > 0),
}))
// Drop patients that no longer have any visits
.filter(row => row.data.length > 0)
);

setFilteredOrderRowData(prev =>
prev
.map(row => ({
...row,
data: row.data
.map(visit => ({
...visit,
orders: visit.orders.filter(order => order.order_id !== orderId),
}))
.filter(visit => visit.orders.length > 0),
}))
.filter(row => row.data.length > 0)
);
};

return (
<LoadingPage isLoading={isLoading} message="Loading Pending Orders...">
<div className="p-2">
Expand Down Expand Up @@ -180,7 +212,7 @@ export default function OrdersPage() {
<OrderRow
key={x.patient?.patient_id || index}
orderRowData={x}
removeNonPendingOrder={fetchPendingOrders}
onApproveOrRejectOrder={handleApproveOrRejectOrder}
/>
))
)}
Expand All @@ -194,10 +226,10 @@ export default function OrdersPage() {

function OrderRow({
orderRowData: { patient, data },
removeNonPendingOrder,
onApproveOrRejectOrder,
}: {
orderRowData: OrderRowData;
removeNonPendingOrder: (id: number) => void;
onApproveOrRejectOrder: (id: number) => void;
}) {
const onPatchError = (err: Error, medicine_name: string) => {
toast.error(() => (
Expand All @@ -220,9 +252,9 @@ function OrderRow({
{/* rowSpan is data.length + 1 because the current row has no data. */}
<td className="px-0" rowSpan={data.length + 1}>
{/* Hacky regex workaround cuz backend send patient_id with village prefix and padded zeroes*/}
<Link
<LoadingLink
href={`/records/patient-record?id=${patient.patient_id.replace(/^..0*/, "")}`}
className="block rounded-lg border border-gray-200 p-4
className="rounded-lg border border-gray-200 p-4
transition-all hover:shadow-md hover:-translate-y-0.5 hover:bg-gray-50 cursor-pointer
flex flex-col items-center bg-slate-50"
>
Expand All @@ -240,7 +272,7 @@ function OrderRow({
height={80}
width={80}
/>
</Link>
</LoadingLink>
</td>
</tr>
{data.map(({ visit_id, visit_date, orders, diagnoses }) => (
Expand Down Expand Up @@ -296,7 +328,8 @@ function OrderRow({
onPatchError(new Error(r.error), o.medication_name);
return;
}
removeNonPendingOrder(o.order_id)
onApproveOrRejectOrder(o.order_id)
toast.success(`Order Approved`);
})
}}
handleCancelOrder={async () => {
Expand All @@ -307,7 +340,8 @@ function OrderRow({
onPatchError(new Error(r.error), o.medication_name);
return;
}
removeNonPendingOrder(o.order_id)
onApproveOrRejectOrder(o.order_id)
toast.success(`Order Rejected`);
})
}}
/>
Expand Down
7 changes: 7 additions & 0 deletions src/app/records/patient-record/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Loading() {
return (
<div className="flex justify-center items-center h-full p-10">
<p className="text-gray-600 animate-pulse">Loading patient record...</p>
</div>
);
}
42 changes: 42 additions & 0 deletions src/components/LoadingLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { useRouter } from 'next/navigation';
import { useTransition } from 'react';

export function LoadingLink({
href,
children,
className,
...props
}: {
href: string;
children: React.ReactNode;
className?: string;
}) {
const router = useRouter();
const [isPending, startTransition] = useTransition();

return (
<div
{...props}
onClick={e => {
e.preventDefault();
startTransition(() => {
router.push(href);
});
}}
className={`relative ${className}`}
>
{isPending && (
<div className="absolute inset-0 z-10 flex flex-col gap-2 items-center justify-center bg-white/30 backdrop-blur-sm">
<div
className={`h-6 w-6 animate-spin rounded-full border-y-2 border-blue-500`}
/>
<p className="font-semibold text-gray-700">Loading...</p>
</div>
)}

{children}
</div>
);
}
22 changes: 16 additions & 6 deletions src/components/NavigationButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,29 @@ export function VitalsButton({
setIsHovered = () => {},
labelOverwrite,
visitId,
className,
}: {
patient: Patient;
setIsHovered?: Dispatch<SetStateAction<boolean>>;
labelOverwrite?: string;
visitId?: string;
className?: string;
}) {
console.log(visitId)
return (
<Link
href={`/records/patient-vitals?id=${patient.pk}&visit=${visitId ?? patient.last_visit_id}`}
href={`/records/patient-vitals?id=${patient.pk}&visit=${visitId ?? patient.last_visit_id}&${visitId && 'specificVisit=true'}`}
onClick={e => e.stopPropagation()}
onMouseEnter={e => {
e.stopPropagation();
setIsHovered(false);
}}
onMouseLeave={() => setIsHovered(true)}
>
<Button text={labelOverwrite ?? 'Vitals'} colour="red" />
<Button
text={labelOverwrite ?? 'Vitals'}
colour="red"
className={className}
/>
</Link>
);
}
Expand All @@ -35,24 +40,29 @@ export function ConsultationButton({
setIsHovered = () => {},
labelOverwrite,
visitId,
className,
}: {
patient: Patient;
setIsHovered?: Dispatch<SetStateAction<boolean>>;
labelOverwrite?: string;
visitId?: string;
className?: string;
}) {
console.log(visitId)
return (
<Link
href={`/records/patient-consultation?id=${patient.pk}&visit=${visitId ?? patient.last_visit_id}`}
href={`/records/patient-consultation?id=${patient.pk}&visit=${visitId ?? patient.last_visit_id}&${visitId && 'specificVisit=true'}`}
onClick={e => e.stopPropagation()}
onMouseEnter={e => {
e.stopPropagation();
setIsHovered(false);
}}
onMouseLeave={() => setIsHovered(true)}
>
<Button text={labelOverwrite ?? 'Consultation'} colour="indigo" />
<Button
text={labelOverwrite ?? 'Consultation'}
colour="indigo"
className={className}
/>
</Link>
);
}
4 changes: 3 additions & 1 deletion src/components/VisitDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ export function VisitDropdown({
searchParams.get('visit') ?? ''
);

// automatically select the latest visit unless viewing a specific visit
useEffect(() => {
setVisitId(items[0].value)
const viewingSpecificVisit = searchParams.get("specificVisit") === "true"
if (!viewingSpecificVisit) setVisitId(items[0].value)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand Down
5 changes: 3 additions & 2 deletions src/components/records/PatientRecordTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function PatientRecordRow({ patient }: { patient: Patient }) {
/>
<div className="flex flex-col items-center gap-2 rounded-md border p-2 sm:flex-row">
<div className="w-[115px]">
<p>Last visit:</p>
<p>Latest visit:</p>
<p>{lastVisitLabel}</p>
</div>
<VitalsButton patient={patient} setIsHovered={setIsHovered} />
Expand Down Expand Up @@ -171,14 +171,15 @@ function getLastVisitLabel(lastVisitDate: string): string {
if (!lastVisitDate) {
return 'Missing last visit date';
}
// lastVisitDate = "2025-12-11T15:30:56.296898Z"
const lastVisitDateLuxon = DateTime.fromISO(lastVisitDate);
// Positive duration since last visit
const timeSinceLastVisit = Duration.fromMillis(
DateTime.now().diff(lastVisitDateLuxon).toMillis()
);
return timeSinceLastVisit < Duration.fromObject({ seconds: 5 })
? 'Just now'
: timeSinceLastVisit < Duration.fromObject({ minutes: 10 })
: timeSinceLastVisit < Duration.fromObject({ hours: 2 })
? lastVisitDateLuxon.toRelative() || 'Missing relative time?'
: formatDate(lastVisitDate, 'datetime');
}
26 changes: 17 additions & 9 deletions src/components/records/consultation/ConsultationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { deleteDiagnosis } from '@/data/diagnosis/deleteDiagnosis';
import { getDiagnosisByConsult } from '@/data/diagnosis/getDiagnosis';
import { patchDiagnosis } from '@/data/diagnosis/patchDiagnosis';
import { createReferral } from '@/data/referrals/createReferral';
import { patchReferralByConsultId } from '@/data/referrals/patchReferral';
import { useSaveOnWrite } from '@/hooks/useSaveOnWrite';
import { ConsultMedicationOrder } from '@/types/ConsultMedicationOrder';
import { Patient } from '@/types/Patient';
Expand Down Expand Up @@ -313,15 +314,22 @@ export function ConsultationForm({
referralPayload['referral_outcome'] = '';

try {
const result = createReferral(referralPayload);
result
.then(
() => {
toast.success('Referral submitted!');
},
() => console.log('error')
)
.catch(() => toast.error('Error submitting consultation form'));
if (isEditing) {
patchReferralByConsultId(
referralPayload,
Number(consultID)
).catch(() => toast.error('Error updating referral'));
} else {
const result = createReferral(referralPayload);
result
.then(
() => {
toast.success('Referral submitted!');
},
() => console.log('error')
)
.catch(() => toast.error('Error creating referral'));
}
} catch (error) {
console.error('Error submitting referral form:', error);
toast.error('Unknown Error');
Expand Down
6 changes: 3 additions & 3 deletions src/components/records/document/UploadDocument.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export function UploadDocument({
const { isLoading, withLoading } = useLoadingState(false);

return (
<>
<div>
<Button
text="Upload Document"
colour="green"
onClick={() => setIsOpen(true)}
className='w-24 h-16'
className="w-44 sm:w-28"
/>
<Modal
isOpen={isOpen}
Expand Down Expand Up @@ -127,6 +127,6 @@ export function UploadDocument({
</form>
</FormProvider>
</Modal>
</>
</div>
);
}
6 changes: 3 additions & 3 deletions src/components/records/document/ViewDocument.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ export function ViewDocument({
};

return (
<>
<div>
<Button
text="View Documents"
onClick={() => setIsOpen(true)}
colour="blue"
className='w-24 h-16'
className='w-44 sm:w-28'
/>
<Modal
isOpen={isOpen}
Expand Down Expand Up @@ -155,6 +155,6 @@ export function ViewDocument({
</table>
)}
</Modal>
</>
</div>
);
}
10 changes: 7 additions & 3 deletions src/components/records/patient/EditPatient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ export function EditPatient({ patient }: { patient: Patient }) {
},
});
const router = useRouter();
const [submitting, setSubmitting] = useState(false)
return (
<>
<div>
<Button
text="Edit Patient Details"
colour="orange"
onClick={() => setIsOpen(true)}
className='w-28 h-16'
className='w-44 sm:w-28'
/>
<Modal
isOpen={isOpen}
Expand All @@ -42,8 +43,10 @@ export function EditPatient({ patient }: { patient: Patient }) {
<PatientForm
closeForm={closeModal}
isEditing
isSubmitting={submitting}
onSubmit={e => {
e.preventDefault();
setSubmitting(true);

useFormReturn.handleSubmit(
async fieldValues => {
Expand Down Expand Up @@ -72,6 +75,7 @@ export function EditPatient({ patient }: { patient: Patient }) {
}
patchPatient(fieldValues.pk, formData).then(() => {
toast.success('Patient Updated!');
setSubmitting(false)
router.refresh();
closeModal();
});
Expand All @@ -84,6 +88,6 @@ export function EditPatient({ patient }: { patient: Patient }) {
/>
</FormProvider>
</Modal>
</>
</div>
);
}
Loading