diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts
index 6f9bf2b03e0..9911090eba0 100644
--- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts
+++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts
@@ -86,7 +86,7 @@ describe("Patient Consultation in multiple combination", () => {
patientConsultationPage.selectPatientPrincipalDiagnosis(diagnosis4);
patientTreatmentPlan.clickAddProcedure();
patientTreatmentPlan.typeProcedureName(procedureName);
- patientTreatmentPlan.typeProcedureTime("2024-02-22T12:30");
+ patientTreatmentPlan.typeProcedureTime("220220241230");
patientTreatmentPlan.typeTreatmentPlan(patientTreatment);
patientTreatmentPlan.typePatientGeneralInstruction(generalInstruction);
patientTreatmentPlan.typeSpecialInstruction(specialInstruction);
@@ -182,12 +182,12 @@ describe("Patient Consultation in multiple combination", () => {
patientConsultationPage.typeCauseOfDeath("Cause of Death");
patientConsultationPage.typePatientConsultationDate(
"#death_datetime",
- "2024-02-22T12:45",
+ "220220241230",
);
patientConsultationPage.typeDeathConfirmedBy(doctorName);
patientConsultationPage.typePatientConsultationDate(
"#encounter_date",
- "2024-02-22T12:30",
+ "220220241230",
);
cy.submitButton("Create Consultation");
cy.verifyNotification(
@@ -245,7 +245,7 @@ describe("Patient Consultation in multiple combination", () => {
);
patientConsultationPage.typePatientConsultationDate(
"#icu_admission_date",
- "2024-02-23T12:30",
+ "230220241230",
);
// add investigation
patientInvestigation.clickAddInvestigation();
diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts
index 4e493a23bff..36c08497c2c 100644
--- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts
+++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts
@@ -25,7 +25,7 @@ const getRelativeDateString = (deltaDays = 0) => {
month: "2-digit",
year: "numeric",
})
- .replace("/", "");
+ .replace(/\//g, "");
};
describe("Patient Creation with consultation", () => {
diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts
index 392c9ff987d..8e7236835e7 100644
--- a/cypress/e2e/users_spec/UsersCreation.cy.ts
+++ b/cypress/e2e/users_spec/UsersCreation.cy.ts
@@ -145,7 +145,7 @@ describe("User Creation", () => {
userCreationPage.typeIntoElementById("password", "Test@123");
userCreationPage.selectHomeFacility("Dummy Shifting Center");
userCreationPage.typeIntoElementById("phone_number", phone_number);
- userCreationPage.setInputDate("date_of_birth", "date-input", "25081999");
+ userCreationPage.setInputDate("date_of_birth", "25081999");
userCreationPage.selectDropdownOption("user_type", "Doctor");
userCreationPage.typeIntoElementById("c_password", "Test@123");
userCreationPage.typeIntoElementById("qualification", "MBBS");
diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts
index f0b2f1b74eb..ec631768154 100644
--- a/cypress/pageobject/Asset/AssetCreation.ts
+++ b/cypress/pageobject/Asset/AssetCreation.ts
@@ -59,10 +59,10 @@ export class AssetPage {
cy.get("[data-testid=asset-support-email-input] input").type(supportEmail);
cy.get("[data-testid=asset-vendor-name-input] input").type(vendorName);
cy.get("[data-testid=asset-serial-number-input] input").type(serialNumber);
- cy.get(
- "[data-testid=asset-last-serviced-on-input] input[type='text']",
- ).click();
- cy.get("#date-input").click().type(lastServicedOn);
+ cy.clickAndTypeDate(
+ "[data-testid=asset-last-serviced-on-input]",
+ lastServicedOn,
+ );
cy.get("[data-testid=asset-notes-input] textarea").type(notes);
}
@@ -117,10 +117,10 @@ export class AssetPage {
cy.get("[data-testid=asset-vendor-name-input] input")
.clear()
.type(vendorName);
- cy.get(
- "[data-testid=asset-last-serviced-on-input] input[type='text']",
- ).click();
- cy.get("#date-input").click().clear().type(lastServicedOn);
+ cy.clickAndTypeDate(
+ "[data-testid=asset-last-serviced-on-input]",
+ lastServicedOn,
+ );
cy.get("[data-testid=asset-notes-input] textarea").clear().type(notes);
}
@@ -267,8 +267,7 @@ export class AssetPage {
}
enterAssetservicedate(text: string) {
- cy.get("input[name='last_serviced_on']").click();
- cy.get("#date-input").click().type(text);
+ cy.clickAndTypeDate("input[name='last_serviced_on']", text);
}
scrollintoWarrantyDetails() {
diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts
index 159b8660b43..a48ffe0856b 100644
--- a/cypress/pageobject/Facility/FacilityCreation.ts
+++ b/cypress/pageobject/Facility/FacilityCreation.ts
@@ -245,8 +245,7 @@ class FacilityPage {
}
fillEntryDate(date: string) {
- cy.get("#entry_date").click();
- cy.get("#date-input").click().type(date);
+ cy.clickAndTypeDate("#entry_date", date);
}
clickEditButton() {
diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts
index 71a0fbb3909..e0b51b9265e 100644
--- a/cypress/pageobject/Patient/PatientConsultation.ts
+++ b/cypress/pageobject/Patient/PatientConsultation.ts
@@ -61,7 +61,7 @@ export class PatientConsultationPage {
}
typePatientConsultationDate(selector: string, date: string) {
- cy.get(selector).clear().click().type(date);
+ cy.clickAndTypeDate(selector, date);
}
clickPatientDetails() {
diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts
index a583844a632..41b3c02977d 100644
--- a/cypress/pageobject/Patient/PatientCreation.ts
+++ b/cypress/pageobject/Patient/PatientCreation.ts
@@ -52,9 +52,7 @@ export class PatientPage {
typePatientDateOfBirth(dateOfBirth: string) {
cy.clickAndSelectOption("#patientAge", "DOB");
- cy.get("#date_of_birth").scrollIntoView();
- cy.get("#date_of_birth").should("be.visible").click();
- cy.get("#date-input").click().type(dateOfBirth);
+ cy.clickAndTypeDate("#date_of_birth", dateOfBirth);
}
typePatientAge(age: string) {
@@ -80,13 +78,11 @@ export class PatientPage {
}
typeLastMenstruationStartDate(date: string) {
- cy.get("#last_menstruation_start_date").click();
- cy.get("#date-input").click().type(date);
+ cy.clickAndTypeDate("#last_menstruation_start_date", date);
}
typeDateOfDelivery(date: string) {
- cy.get("#date_of_delivery").click();
- cy.get("#date-input").click().type(date);
+ cy.clickAndTypeDate("#date_of_delivery", date);
}
clickPermanentAddress() {
diff --git a/cypress/pageobject/Patient/PatientTreatmentPlan.ts b/cypress/pageobject/Patient/PatientTreatmentPlan.ts
index 0bbddbf70bc..02b2f9b150d 100644
--- a/cypress/pageobject/Patient/PatientTreatmentPlan.ts
+++ b/cypress/pageobject/Patient/PatientTreatmentPlan.ts
@@ -33,7 +33,7 @@ class PatientTreatmentPlan {
}
typeProcedureTime(time: string) {
- cy.get("#procedure-time").type(time);
+ cy.clickAndTypeDate("#procedure-time", time);
}
typeTreatmentPlan(treatment: string) {
diff --git a/cypress/pageobject/Shift/ShiftFilters.ts b/cypress/pageobject/Shift/ShiftFilters.ts
index 1f824cebbb3..fe20b97bd9f 100644
--- a/cypress/pageobject/Shift/ShiftFilters.ts
+++ b/cypress/pageobject/Shift/ShiftFilters.ts
@@ -139,18 +139,12 @@ class ShiftingPage {
modified_date_end: string,
) {
this.createdDateStartInput().click();
- cy.get("[id^='headlessui-popover-panel-'] .care-l-angle-left-b")
- .eq(0)
- .closest("button")
- .click();
+ cy.get("[data-test-id='increment-date-range']").click();
cy.get(created_date_start).click();
cy.get(created_date_end).click();
this.modifiedDateStartInput().click();
- cy.get("[id^='headlessui-popover-panel-'] .care-l-angle-left-b")
- .eq(0)
- .closest("button")
- .click();
+ cy.get("[data-test-id='increment-date-range']").click();
cy.get(modified_date_start).click();
cy.get(modified_date_end).click();
diff --git a/cypress/pageobject/Users/UserCreation.ts b/cypress/pageobject/Users/UserCreation.ts
index 7503ea3fb07..906c07e797a 100644
--- a/cypress/pageobject/Users/UserCreation.ts
+++ b/cypress/pageobject/Users/UserCreation.ts
@@ -18,8 +18,7 @@ export class UserCreationPage {
.type(value);
}
typeIntoElementByIdPostClearDob(elementId: string, value: string) {
- cy.get("#" + elementId).click();
- cy.get("#date-input").clear().type(value);
+ cy.clickAndTypeDate("#" + elementId, value);
}
clearIntoElementById(elementId: string) {
cy.get("#" + elementId)
@@ -54,13 +53,8 @@ export class UserCreationPage {
this.selectOptionContainingText(name);
}
- setInputDate(
- dateElementId: string,
- inputElementId: string,
- dateValue: string,
- ) {
- this.clickElementById(dateElementId);
- this.typeIntoElementById(inputElementId, dateValue);
+ setInputDate(dateElementId: string, dateValue: string) {
+ cy.clickAndTypeDate("#" + dateElementId, dateValue);
}
selectDropdownOption(dropdownId: string, optionText: string) {
diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts
index 20fd1911c49..3744c5a5d82 100644
--- a/cypress/pageobject/Users/UserProfilePage.ts
+++ b/cypress/pageobject/Users/UserProfilePage.ts
@@ -16,9 +16,7 @@ export default class UserProfilePage {
}
typedate_of_birth(date_of_birth: string) {
- //check
- cy.get("#date_of_birth").click();
- cy.get("#date-input").clear().type(date_of_birth);
+ cy.clickAndTypeDate("#date_of_birth", date_of_birth);
}
selectGender(gender: string) {
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index da78361f6e8..9af5f97e5d4 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -183,10 +183,16 @@ Cypress.Commands.add("selectRadioOption", (name: string, value: string) => {
cy.get(`input[type='radio'][name='${name}'][value=${value}]`).click();
});
-Cypress.Commands.add("clickAndTypeDate", (selector: string, date: string) => {
+Cypress.Commands.add("clickAndTypeDate", (selector, date) => {
cy.get(selector).scrollIntoView();
cy.get(selector).click();
- cy.get("#date-input").click().type(date);
+ cy.get('[data-test-id="date-input"]:visible [data-time-input]').each((el) =>
+ cy.wrap(el).clear(),
+ );
+ cy.get(`[data-test-id="date-input"]:visible [data-time-input="0"]`)
+ .click()
+ .type(date);
+ cy.get("body").click(0, 0);
});
Cypress.Commands.add(
diff --git a/src/CAREUI/interactive/SlideOver.tsx b/src/CAREUI/interactive/SlideOver.tsx
index bd38b32137a..78982efc39d 100644
--- a/src/CAREUI/interactive/SlideOver.tsx
+++ b/src/CAREUI/interactive/SlideOver.tsx
@@ -124,7 +124,12 @@ export default function SlideOver({
{title}
- {children}
+
+ {children}
+
)}
diff --git a/src/components/ABDM/FetchRecordsModal.tsx b/src/components/ABDM/FetchRecordsModal.tsx
index 34d70255846..441bee0ddbd 100644
--- a/src/components/ABDM/FetchRecordsModal.tsx
+++ b/src/components/ABDM/FetchRecordsModal.tsx
@@ -193,7 +193,6 @@ export default function FetchRecordsModal({ abha, show, onClose }: IProps) {
label={t("consent_request__expiry")}
required
disablePast
- position="TOP-RIGHT"
/>
diff --git a/src/components/Assets/AssetServiceEditModal.tsx b/src/components/Assets/AssetServiceEditModal.tsx
index 9accf5c3d66..fecea8ad124 100644
--- a/src/components/Assets/AssetServiceEditModal.tsx
+++ b/src/components/Assets/AssetServiceEditModal.tsx
@@ -196,7 +196,6 @@ export const AssetServiceEditModal = (props: {
label={t("serviced_on")}
name="serviced_on"
className="mt-2"
- position="LEFT"
value={new Date(form.serviced_on)}
max={new Date(props.service_record.created_date)}
onChange={(date) => {
diff --git a/src/components/Common/DateInputV2.tsx b/src/components/Common/DateInputV2.tsx
index 37f67c8ad80..87a29debe5b 100644
--- a/src/components/Common/DateInputV2.tsx
+++ b/src/components/Common/DateInputV2.tsx
@@ -1,4 +1,4 @@
-import { MutableRefObject, useEffect, useState } from "react";
+import { MutableRefObject, useEffect, useRef, useState } from "react";
import CareIcon from "../../CAREUI/icons/CareIcon";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
@@ -6,15 +6,9 @@ import { classNames } from "../../Utils/utils";
import dayjs from "../../Utils/dayjs";
import * as Notification from "../../Utils/Notifications";
import { t } from "i18next";
+import DateTextInput from "./DateTextInput";
type DatePickerType = "date" | "month" | "year";
-export type DatePickerPosition =
- | "LEFT"
- | "RIGHT"
- | "CENTER"
- | "TOP-LEFT"
- | "TOP-RIGHT"
- | "TOP-CENTER";
interface Props {
id?: string;
@@ -25,12 +19,13 @@ interface Props {
min?: Date;
max?: Date;
outOfLimitsErrorMessage?: string;
- onChange: (date: Date) => void;
- position?: DatePickerPosition;
+ onChange: (date: Date | undefined) => void;
disabled?: boolean;
placeholder?: string;
isOpen?: boolean;
setIsOpen?: (isOpen: boolean) => void;
+ allowTime?: boolean;
+ popOverClassName?: string;
}
const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -45,21 +40,42 @@ const DateInputV2: React.FC
= ({
max,
outOfLimitsErrorMessage,
onChange,
- position = "CENTER",
disabled,
placeholder,
- isOpen,
setIsOpen,
+ allowTime,
+ isOpen,
+ popOverClassName,
}) => {
const [dayCount, setDayCount] = useState>([]);
const [blankDays, setBlankDays] = useState>([]);
- const [datePickerHeaderDate, setDatePickerHeaderDate] = useState(new Date());
+ const [datePickerHeaderDate, setDatePickerHeaderDate] = useState(
+ value || new Date(),
+ );
const [type, setType] = useState("date");
const [year, setYear] = useState(new Date());
- const [displayValue, setDisplayValue] = useState(
- value ? dayjs(value).format("DDMMYYYY") : "",
- );
+
+ const [popOverOpen, setPopOverOpen] = useState(false);
+
+ const hours = dayjs(value).hour() % 12;
+ const minutes = dayjs(value).minute();
+ const ampm = dayjs(value).hour() > 11 ? "PM" : "AM";
+
+ const hourScrollerRef = useRef(null);
+ const minuteScrollerRef = useRef(null);
+
+ const popoverButtonRef = useRef(null);
+
+ const getDayStart = (date: Date) => {
+ const newDate = new Date(date);
+ newDate.setHours(0, 0, 0, 0);
+ return newDate;
+ };
+
+ const handleChange = (date: Date) => {
+ onChange(allowTime ? date : getDayStart(date));
+ };
const decrement = () => {
switch (type) {
@@ -97,14 +113,6 @@ const DateInputV2: React.FC = ({
}
};
- const isSelectedDate = (date: number) => {
- if (value) {
- return dayjs(
- new Date(value.getFullYear(), value.getMonth(), date),
- ).isSame(dayjs(value));
- }
- };
-
type CloseFunction = (
focusableElement?: HTMLElement | MutableRefObject,
) => void;
@@ -112,21 +120,43 @@ const DateInputV2: React.FC = ({
const setDateValue = (date: number, close: CloseFunction) => () => {
isDateWithinConstraints(date)
? (() => {
- onChange(
+ handleChange(
new Date(
datePickerHeaderDate.getFullYear(),
datePickerHeaderDate.getMonth(),
date,
+ datePickerHeaderDate.getHours(),
+ datePickerHeaderDate.getMinutes(),
+ datePickerHeaderDate.getSeconds(),
),
);
- close();
- setIsOpen?.(false);
+ if (!allowTime) {
+ close();
+ setIsOpen?.(false);
+ }
})()
: Notification.Error({
msg: outOfLimitsErrorMessage ?? "Cannot select date out of range",
});
};
+ const handleTimeChange = (options: {
+ newHours?: typeof hours;
+ newMinutes?: typeof minutes;
+ newAmpm?: typeof ampm;
+ }) => {
+ const { newHours = hours, newMinutes = minutes, newAmpm = ampm } = options;
+ handleChange(
+ new Date(
+ datePickerHeaderDate.getFullYear(),
+ datePickerHeaderDate.getMonth(),
+ datePickerHeaderDate.getDate(),
+ newAmpm === "PM" ? (newHours % 12) + 12 : newHours % 12,
+ newMinutes,
+ ),
+ );
+ };
+
const getDayCount = (date: Date) => {
const daysInMonth = dayjs(date).daysInMonth();
@@ -165,22 +195,6 @@ const DateInputV2: React.FC = ({
return true;
};
- const isDateWithinLimits = (parsedDate: dayjs.Dayjs): boolean => {
- if (parsedDate?.isValid()) {
- if (
- (max && parsedDate.toDate() > max) ||
- (min && parsedDate.toDate() < min)
- ) {
- Notification.Error({
- msg: outOfLimitsErrorMessage ?? "Cannot select date out of range",
- });
- return false;
- }
- return true;
- }
- return false;
- };
-
const isSelectedMonth = (month: number) =>
month === datePickerHeaderDate.getMonth();
@@ -209,35 +223,58 @@ const DateInputV2: React.FC = ({
setType("date");
};
- const showMonthPicker = () => setType("month");
-
- const showYearPicker = () => setType("year");
-
useEffect(() => {
getDayCount(datePickerHeaderDate);
}, [datePickerHeaderDate]);
+ const scrollTime = (smooth: boolean = true) => {
+ const timeScrollers = [hourScrollerRef, minuteScrollerRef];
+ timeScrollers.forEach((scroller) => {
+ if (!scroller.current) return;
+ const selected = scroller.current.querySelector("[data-selected=true]");
+ if (selected) {
+ const selectedPosition = (
+ selected as HTMLDivElement
+ ).getBoundingClientRect().top;
+
+ const toScroll =
+ selectedPosition - scroller.current.getBoundingClientRect().top;
+
+ selected.parentElement?.scrollBy({
+ top: toScroll,
+ behavior: smooth ? "smooth" : "instant",
+ });
+ }
+ });
+ };
+
useEffect(() => {
- value && setDatePickerHeaderDate(new Date(value));
+ value && setDatePickerHeaderDate(value);
+ scrollTime();
}, [value]);
+ useEffect(() => {
+ if (!popOverOpen) return;
+ scrollTime(false);
+ }, [popOverOpen]);
+
+ useEffect(() => {
+ isOpen && popoverButtonRef.current?.click();
+ }, [isOpen]);
+
+ const dateFormat = `DD/MM/YYYY${allowTime ? " hh:mm a" : ""}`;
+
const getPosition = () => {
- switch (position) {
- case "LEFT":
- return "left-0";
- case "RIGHT":
- return "right-0 transform translate-x-1/2";
- case "CENTER":
- return "transform -translate-x-1/2";
- case "TOP-LEFT":
- return "bottom-full left-full";
- case "TOP-RIGHT":
- return "bottom-full right-0";
- case "TOP-CENTER":
- return "bottom-full left-1/2 transform -translate-x-1/2";
- default:
- return "left-0";
- }
+ const viewportWidth = document.documentElement.clientWidth;
+ const viewportHeight = document.documentElement.clientHeight;
+
+ const popOverX = popoverButtonRef.current?.getBoundingClientRect().x || 0;
+ const popOverY = popoverButtonRef.current?.getBoundingClientRect().y || 0;
+
+ const right = popOverX > viewportWidth - (allowTime ? 420 : 300);
+ const top = popOverY > viewportHeight - 400;
+
+ return `${right ? "md:-translate-x-1/2" : ""} ${top ? "md:-translate-y-[calc(100%+50px)]" : ""}`;
};
return (
@@ -246,238 +283,357 @@ const DateInputV2: React.FC = ({
className={`${containerClassName ?? "container mx-auto text-black"}`}
>
- {({ open, close }) => (
-
-
-
- {
+ setPopOverOpen(open);
+ return (
+
+
-
-
-
-
-
- {(open || isOpen) && (
-
-
+
+
+
+
+
+ {open && (
+
-
- [dd, mm, yyyy].filter(Boolean).join("/"),
- ) || ""
- } // Display the value in DD/MM/YYYY format
- placeholder={t("DD/MM/YYYY")}
- onChange={(e) => {
- setDisplayValue(e.target.value.replaceAll("/", ""));
- const value = dayjs(e.target.value, "DD/MM/YYYY", true);
- if (isDateWithinLimits(value)) {
- onChange(value.toDate());
- close();
- setIsOpen?.(false);
+
+
close()}
+ error={
+ value &&
+ (!dayjs(value).isValid() ||
+ (!!max && value > max) ||
+ (!!min && value < min))
+ ? "Cannot select date out of range"
+ : undefined
}
- }}
- />
-
-
-
-
-
- {type === "date" && (
-
+
+
+
+
+
- )}
-
-
- {type == "year"
- ? year.getFullYear()
- : dayjs(datePickerHeaderDate).format("YYYY")}
-
-
-
-
-
+
+
- {type === "date" && (
- <>
-
- {DAYS.map((day, i) => (
+
+ {type === "date" && (
+
setType("month")}
+ className="cursor-pointer rounded px-3 py-1 text-center font-medium text-black hover:bg-secondary-300"
+ >
+ {dayjs(datePickerHeaderDate).format("MMMM")}
+
+ )}
setType("year")}
+ className="cursor-pointer rounded px-3 py-1 font-medium text-black hover:bg-secondary-300"
>
-
- {day}
-
+
+ {type == "year"
+ ? year.getFullYear()
+ : dayjs(datePickerHeaderDate).format(
+ "YYYY",
+ )}
+
- ))}
-
-
- {blankDays.map((_, i) => (
-
- ))}
- {dayCount.map((d, i) => {
- const withinConstraints =
- isDateWithinConstraints(d);
- const selected = value && isSelectedDate(d);
-
- const baseClasses =
- "flex h-full items-center justify-center rounded text-center text-sm leading-loose transition duration-100 ease-in-out";
- let conditionalClasses = "";
-
- if (withinConstraints) {
- if (selected) {
- conditionalClasses =
- "bg-primary-500 font-bold text-white";
- } else {
- conditionalClasses =
- "hover:bg-secondary-300 cursor-pointer";
- }
- } else {
- conditionalClasses =
- "!cursor-not-allowed !text-secondary-400";
+
+
+
+
+ {type === "date" && (
+ <>
+
+ {DAYS.map((day, i) => (
-
- );
- })}
-
- >
- )}
- {type === "month" && (
-
- {Array(12)
- .fill(null)
- .map((_, i) => (
+ ))}
+
+
+ {blankDays.map((_, i) => (
+
+ ))}
+ {dayCount.map((d, i) => {
+ const withinConstraints =
+ isDateWithinConstraints(d);
+ let selected;
+ if (value) {
+ const newDate = new Date(
+ datePickerHeaderDate,
+ );
+ newDate.setDate(d);
+ selected =
+ value.toDateString() ===
+ newDate.toDateString();
+ }
+
+ const baseClasses =
+ "flex h-full items-center justify-center rounded text-center text-sm leading-loose transition duration-100 ease-in-out";
+ let conditionalClasses = "";
+
+ if (withinConstraints) {
+ if (selected) {
+ conditionalClasses =
+ "bg-primary-500 font-bold text-white";
+ } else {
+ conditionalClasses =
+ "hover:bg-secondary-300 cursor-pointer";
+ }
+ } else {
+ conditionalClasses =
+ "!cursor-not-allowed !text-secondary-400";
+ }
+ return (
+
+
+
+ );
+ })}
+
+ >
+ )}
+ {type === "month" && (
+
+ {Array(12)
+ .fill(null)
+ .map((_, i) => (
+
+ {dayjs(
+ new Date(
+ datePickerHeaderDate.getFullYear(),
+ i,
+ 1,
+ ),
+ ).format("MMM")}
+
+ ))}
+
+ )}
+ {type === "year" && (
+
+ {Array(12)
+ .fill(null)
+ .map((_, i) => {
+ const y = year.getFullYear() - 11 + i;
+ return (
+
+ {y}
+
+ );
+ })}
+
+ )}
+
+ {allowTime && (
+
+ {(
+ [
+ {
+ name: "Hours",
+ value: hours,
+ options: Array.from(
+ { length: 12 },
+ (_, i) => i + 1,
+ ),
+ onChange: (val: any) => {
+ handleTimeChange({
+ newHours: val,
+ });
+ },
+ ref: hourScrollerRef,
+ },
+ {
+ name: "Minutes",
+ value: minutes,
+ options: Array.from(
+ { length: 60 },
+ (_, i) => i,
+ ),
+ onChange: (val: any) => {
+ handleTimeChange({
+ newMinutes: val,
+ });
+ },
+ ref: minuteScrollerRef,
+ },
+ {
+ name: "am/pm",
+ value: ampm,
+ options: ["AM", "PM"],
+ onChange: (val: any) => {
+ handleTimeChange({
+ newAmpm: val,
+ });
+ },
+ ref: undefined,
+ },
+ ] as const
+ ).map((input, i) => (
{
+ const optionsHeight =
+ e.currentTarget.scrollHeight / 3;
+ const scrollTop = e.currentTarget.scrollTop;
+ const containerHeight =
+ e.currentTarget.clientHeight;
+ if (scrollTop >= optionsHeight * 2) {
+ e.currentTarget.scrollTo({
+ top: optionsHeight,
+ });
+ }
+ if (
+ scrollTop + containerHeight <=
+ optionsHeight
+ ) {
+ e.currentTarget.scrollTo({
+ top: optionsHeight + scrollTop,
+ });
+ }
+ }}
>
- {dayjs(
- new Date(
- datePickerHeaderDate.getFullYear(),
- i,
- 1,
- ),
- ).format("MMM")}
+ {[
+ ...input.options,
+ ...(input.name === "am/pm"
+ ? []
+ : input.options),
+ ...(input.name === "am/pm"
+ ? []
+ : input.options),
+ ].map((option, j) => (
+
+ ))}
))}
-
- )}
- {type === "year" && (
-
- {Array(12)
- .fill(null)
- .map((_, i) => {
- const y = year.getFullYear() - 11 + i;
- return (
-
- {y}
-
- );
- })}
-
- )}
+
+ )}
+
-
-
- )}
-
- )}
+
+ )}
+
+ );
+ }}
diff --git a/src/components/Common/DateRangeInputV2.tsx b/src/components/Common/DateRangeInputV2.tsx
index 0c3a7559ff9..aa426ad0cb2 100644
--- a/src/components/Common/DateRangeInputV2.tsx
+++ b/src/components/Common/DateRangeInputV2.tsx
@@ -14,6 +14,7 @@ type Props = {
disabled?: boolean;
max?: Date;
min?: Date;
+ allowTime?: boolean;
};
const DateRangeInputV2 = ({ value, onChange, ...props }: Props) => {
@@ -33,9 +34,9 @@ const DateRangeInputV2 = ({ value, onChange, ...props }: Props) => {
}}
min={props.min}
max={end || props.max}
- position="RIGHT"
placeholder="Start date"
disabled={props.disabled}
+ allowTime={props.allowTime}
/>
@@ -46,11 +47,11 @@ const DateRangeInputV2 = ({ value, onChange, ...props }: Props) => {
onChange={(end) => onChange({ start, end })}
min={start || props.min}
max={props.max}
- position="CENTER"
disabled={props.disabled || !start}
placeholder="End date"
isOpen={showEndPicker}
setIsOpen={setShowEndPicker}
+ allowTime={props.allowTime}
/>
diff --git a/src/components/Common/DateTextInput.tsx b/src/components/Common/DateTextInput.tsx
new file mode 100644
index 00000000000..38e13b23283
--- /dev/null
+++ b/src/components/Common/DateTextInput.tsx
@@ -0,0 +1,203 @@
+import CareIcon from "@/CAREUI/icons/CareIcon";
+import { classNames } from "@/Utils/utils";
+import dayjs from "dayjs";
+import { Fragment, KeyboardEvent, useEffect, useState } from "react";
+
+/**
+ * DateTextInput component.
+ *
+ * @param {Object} props - Component properties.
+ * @param {boolean} props.allowTime - If true, shows time input fields (hour and minute).
+ * @param {Date} props.value - The current date value.
+ * @param {function(Date):void} props.onChange - Callback function when date value changes.
+ * @param {function():void} props.onFinishInitialTyping - Callback function when a user successfuly types in the date on the first input
+ * @param {String} props.error - Shows an error if specified
+ *
+ * @returns {JSX.Element} The date text input component.
+ */
+export default function DateTextInput(props: {
+ allowTime: boolean;
+ value?: Date;
+ onChange: (date: Date | undefined) => unknown;
+ onFinishInitialTyping?: () => unknown;
+ error?: string;
+}) {
+ const { value, onChange, allowTime, error, onFinishInitialTyping } = props;
+
+ const [editingText, setDirtyEditingText] = useState({
+ date: `${value ? value?.getDate() : ""}`,
+ month: `${value ? value.getMonth() + 1 : ""} `,
+ year: `${value ? value.getFullYear() : ""}`,
+ hour: `${value ? value.getHours() : ""}`,
+ minute: `${value ? value.getMinutes() : ""}`,
+ });
+
+ const setEditingText = (et: typeof editingText) => {
+ setDirtyEditingText(et);
+ const newDate = new Date(
+ parseInt(et.year),
+ parseInt(et.month) - 1,
+ parseInt(et.date),
+ allowTime ? parseInt(et.hour) : 0,
+ allowTime ? parseInt(et.minute) : 0,
+ );
+ if (et.year.length > 3 && dayjs(newDate).isValid()) {
+ if (!value && !allowTime) onFinishInitialTyping?.();
+ if (!value && allowTime && et.minute.length > 1)
+ onFinishInitialTyping?.();
+ onChange(newDate);
+ }
+ };
+
+ const handleBlur = (rawValue: string, key: string) => {
+ const val = getBlurredValue(rawValue, key);
+ setEditingText({
+ ...editingText,
+ [key]: val,
+ });
+ };
+
+ const getBlurredValue = (rawValue: string, key: string) => {
+ const maxMap = [31, 12, 2999, 23, 59];
+ const index = Object.keys(editingText).findIndex((et) => et === key);
+ const value = Math.min(maxMap[index], parseInt(rawValue));
+ const finalValue =
+ rawValue.trim() !== ""
+ ? ("000" + value).slice(key === "year" ? -4 : -2)
+ : "";
+ return finalValue;
+ };
+
+ const goToInput = (i: number) => {
+ if (i < 0 || i > 4) return;
+ (
+ document.querySelectorAll(
+ `[data-time-input]`,
+ ) as NodeListOf
+ ).forEach((i) => i.blur());
+ (
+ document.querySelector(`[data-time-input="${i}"]`) as HTMLInputElement
+ )?.focus();
+ };
+
+ const handleKeyDown = (event: KeyboardEvent, i: number) => {
+ const keyboardKey: number = event.keyCode || event.charCode;
+ const target = event.target as HTMLInputElement;
+
+ // check for backspace
+ if ([8].includes(keyboardKey) && target.value === "") goToInput(i - 1);
+
+ // check for delete
+ if ([46].includes(keyboardKey) && target.value === "") goToInput(i + 1);
+
+ // check for left arrow key
+ if ([37].includes(keyboardKey) && (target.selectionStart || 0) < 1)
+ goToInput(i - 1);
+
+ // check for right arrow key
+ if ([39].includes(keyboardKey) && (target.selectionStart || 0) > 1)
+ goToInput(i + 1);
+ };
+
+ useEffect(() => {
+ const formatUnfocused = (value: number, id: number, digits: number = 2) => {
+ const activeElementIdRaw =
+ document.activeElement?.getAttribute("data-time-input");
+ const activeElementId = activeElementIdRaw
+ ? parseInt(activeElementIdRaw)
+ : undefined;
+ if (id === activeElementId) return value;
+ return ("000" + value).slice(-digits);
+ };
+
+ setDirtyEditingText({
+ date: `${value ? formatUnfocused(value.getDate(), 0) : ""}`,
+ month: `${value ? formatUnfocused(value.getMonth() + 1, 1) : ""}`,
+ year: `${value ? formatUnfocused(value.getFullYear(), 2, 4) : ""}`,
+ hour: `${value ? formatUnfocused(value.getHours(), 3) : ""}`,
+ minute: `${value ? formatUnfocused(value.getMinutes(), 4) : ""}`,
+ });
+ }, [value]);
+
+ return (
+
+
+ e.target === e.currentTarget &&
+ (value ? goToInput(allowTime ? 4 : 2) : goToInput(0))
+ }
+ data-test-id="date-input"
+ >
+ {Object.entries(editingText)
+ .slice(0, allowTime ? 5 : 3)
+ .map(([key, val], i) => (
+
+ handleKeyDown(e, i)}
+ data-time-input={i}
+ onChange={(e) => {
+ const value = e.target.value;
+ if (
+ (value.endsWith("/") ||
+ value.endsWith(" ") ||
+ value.endsWith(":") ||
+ value.length > (key === "year" ? 3 : 1)) &&
+ i < 4
+ ) {
+ goToInput(i + 1);
+ } else {
+ setEditingText({
+ ...editingText,
+ [key]: value
+ .replace(/\D/g, "")
+ .slice(0, key === "year" ? 4 : 2),
+ });
+ }
+ }}
+ onBlur={(e) => handleBlur(e.target.value, key)}
+ />
+
+ {["date", "month"].includes(key)
+ ? "/"
+ : key === "hour"
+ ? ":"
+ : " "}
+
+
+ ))}
+
+
+
+ {error &&
{error}}
+
+ );
+}
diff --git a/src/components/Common/SortDropdown.tsx b/src/components/Common/SortDropdown.tsx
index 0c4fddec58c..7f190fde646 100644
--- a/src/components/Common/SortDropdown.tsx
+++ b/src/components/Common/SortDropdown.tsx
@@ -27,9 +27,9 @@ export default function SortDropdownMenu(props: Props) {
icon={}
containerClassName="w-full md:w-auto"
>
- {props.options.map(({ isAscending, value }) => (
+ {props.options.map(({ isAscending, value }, i) => (
) : (
-
- Time{" *"}
- {
- setItem(
- {
- ...investigation,
- time: e.currentTarget.value,
- },
- i,
- );
- }}
- onFocus={() => setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- />
-
+
+ setItem(
+ {
+ ...investigation,
+ time: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ },
+ i,
+ )
+ }
+ allowTime
+ errorClassName="hidden"
+ className="w-full"
+ onFocus={() => setActiveIdx(i)}
+ onBlur={() => setActiveIdx(null)}
+ />
)}
diff --git a/src/components/Common/prescription-builder/ProcedureBuilder.tsx b/src/components/Common/prescription-builder/ProcedureBuilder.tsx
index fe7ec3cda3c..36536c2435b 100644
--- a/src/components/Common/prescription-builder/ProcedureBuilder.tsx
+++ b/src/components/Common/prescription-builder/ProcedureBuilder.tsx
@@ -1,6 +1,8 @@
import { useState } from "react";
import { PrescriptionDropdown } from "./PrescriptionDropdown";
import CareIcon from "../../../CAREUI/icons/CareIcon";
+import dayjs from "dayjs";
+import DateFormField from "../../Form/FormFields/DateFormField";
export type ProcedureType = {
procedure?: string;
@@ -131,28 +133,29 @@ export default function ProcedureBuilder(props: Props
) {
/>
) : (
-
-
- Time{" *"}
-
-
setActiveIdx(i)}
- onBlur={() => setActiveIdx(null)}
- onChange={(e) => {
- setItem(
- {
- ...procedure,
- time: e.currentTarget.value,
- },
- i,
- );
- }}
- />
-
+
+ setItem(
+ {
+ ...procedure,
+ time: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ },
+ i,
+ )
+ }
+ allowTime
+ errorClassName="hidden"
+ className="w-full"
+ onFocus={() => setActiveIdx(i)}
+ onBlur={() => setActiveIdx(null)}
+ />
)}
diff --git a/src/components/DeathReport/DeathReport.tsx b/src/components/DeathReport/DeathReport.tsx
index 702a486bdfb..00f6d6bd773 100644
--- a/src/components/DeathReport/DeathReport.tsx
+++ b/src/components/DeathReport/DeathReport.tsx
@@ -410,7 +410,6 @@ export default function PrintDeathReport(props: { id: string }) {
@@ -428,14 +427,12 @@ export default function PrintDeathReport(props: { id: string }) {
@@ -477,7 +474,6 @@ export default function PrintDeathReport(props: { id: string }) {
@@ -485,7 +481,6 @@ export default function PrintDeathReport(props: { id: string }) {
@@ -534,7 +529,6 @@ export default function PrintDeathReport(props: { id: string }) {
diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx
index 40bf4d276dd..60676f9ca18 100644
--- a/src/components/Facility/AssetCreate.tsx
+++ b/src/components/Facility/AssetCreate.tsx
@@ -834,7 +834,6 @@ const AssetCreate = (props: AssetProps) => {
label={t("last_serviced_on")}
name="last_serviced_on"
className="mt-2"
- position="RIGHT"
disableFuture
value={last_serviced_on && new Date(last_serviced_on)}
onChange={(date) => {
diff --git a/src/components/Facility/ConsultationForm.tsx b/src/components/Facility/ConsultationForm.tsx
index 47ba2eea77f..9fe281635f2 100644
--- a/src/components/Facility/ConsultationForm.tsx
+++ b/src/components/Facility/ConsultationForm.tsx
@@ -65,6 +65,7 @@ import {
CreateSymptomsBuilder,
} from "../Symptoms/SymptomsBuilder";
import careConfig from "@careConfig";
+import DateFormField from "../Form/FormFields/DateFormField.js";
import Loading from "@/components/Common/Loading";
import PageTitle from "@/components/Common/PageTitle";
@@ -1178,13 +1179,24 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
className="col-span-6"
ref={fieldRef["death_datetime"]}
>
-
+ field("death_datetime").onChange({
+ ...e,
+ value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ })
+ }
+ allowTime
+ errorClassName="hidden"
/>
{
)}
ref={fieldRef["encounter_date"]}
>
-
{
label={t(
`encounter_date_field_label__${state.form.suggestion}`,
)}
- type="datetime-local"
- value={dayjs(state.form.encounter_date).format(
- "YYYY-MM-DDTHH:mm",
- )}
- max={dayjs().format("YYYY-MM-DDTHH:mm")}
- min={dayjs(careConfig.minEncounterDate).format(
- "YYYY-MM-DDTHH:mm",
- )}
+ value={
+ !state.form.encounter_date
+ ? new Date()
+ : state.form.encounter_date
+ }
+ max={new Date()}
+ min={careConfig.minEncounterDate}
+ onChange={(e) =>
+ field("encounter_date").onChange({
+ ...e,
+ value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ })
+ }
+ allowTime
+ errorClassName="hidden"
/>
{dayjs().diff(state.form.encounter_date, "day") > 30 && (
@@ -1252,16 +1271,18 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
)}
ref={fieldRef["icu_admission_date"]}
>
-
+ field("icu_admission_date").onChange({
+ ...e,
+ value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ })
}
+ allowTime
+ errorClassName="hidden"
/>
)}
diff --git a/src/components/Facility/Consultations/Beds.tsx b/src/components/Facility/Consultations/Beds.tsx
index 4c8409fbebf..5d43ffff7fd 100644
--- a/src/components/Facility/Consultations/Beds.tsx
+++ b/src/components/Facility/Consultations/Beds.tsx
@@ -12,7 +12,6 @@ import CareIcon from "../../../CAREUI/icons/CareIcon";
import CircularProgress from "@/components/Common/components/CircularProgress";
import { FieldLabel } from "../../Form/FormFields/FormField";
import Loading from "@/components/Common/Loading";
-import TextFormField from "../../Form/FormFields/TextFormField";
import dayjs from "../../../Utils/dayjs";
import { AssetSelect } from "@/components/Common/AssetSelect";
import DialogModal from "@/components/Common/Dialog";
@@ -24,6 +23,7 @@ import {
} from "../../Assets/AssetTypes";
import Chip from "../../../CAREUI/display/Chip";
import BedActivityTimeline from "./BedActivityTimeline";
+import DateFormField from "../../Form/FormFields/DateFormField.js";
interface BedsProps {
facilityId: string;
@@ -204,16 +204,18 @@ const Beds = (props: BedsProps) => {
unoccupiedOnly
/>
- setStartDate(e.value)}
- max={dayjs().format("YYYY-MM-DDTHH:mm")}
+ value={startDate ? new Date(startDate) : new Date()}
+ onChange={(e) =>
+ setStartDate(dayjs(e.value).format("YYYY-MM-DDTHH:mm"))
+ }
+ max={new Date()}
error=""
errorClassName="hidden"
+ allowTime
/>
Link Assets
diff --git a/src/components/Facility/Consultations/DailyRoundsFilter.tsx b/src/components/Facility/Consultations/DailyRoundsFilter.tsx
index 2df3b506d68..503952eacc1 100644
--- a/src/components/Facility/Consultations/DailyRoundsFilter.tsx
+++ b/src/components/Facility/Consultations/DailyRoundsFilter.tsx
@@ -6,13 +6,13 @@ import {
} from "@headlessui/react";
import ButtonV2 from "@/components/Common/components/ButtonV2";
import { SelectFormField } from "../../Form/FormFields/SelectFormField";
-import TextFormField from "../../Form/FormFields/TextFormField";
import CareIcon from "../../../CAREUI/icons/CareIcon";
import dayjs from "dayjs";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { DailyRoundTypes, DailyRoundsModel } from "../../Patient/models";
import { FieldChangeEvent } from "../../Form/FormFields/Utils";
+import DateFormField from "../../Form/FormFields/DateFormField";
type FilterState = {
rounds_type?: DailyRoundsModel["rounds_type"];
@@ -77,17 +77,41 @@ export default function DailyRoundsFilter(props: Props) {
optionLabel={(o) => t(`ROUNDS_TYPE__${o}`)}
optionValue={(o) => o}
/>
-
+ field("taken_at_after").onChange({
+ ...e,
+ value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ })
+ }
+ max={new Date()}
+ errorClassName="hidden"
+ allowTime
/>
-
+ field("taken_at_before").onChange({
+ ...e,
+ value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ })
+ }
+ max={new Date()}
+ errorClassName="hidden"
+ allowTime
/>
diff --git a/src/components/Facility/DischargeModal.tsx b/src/components/Facility/DischargeModal.tsx
index 122aacd1368..b5c15dc98d2 100644
--- a/src/components/Facility/DischargeModal.tsx
+++ b/src/components/Facility/DischargeModal.tsx
@@ -29,6 +29,7 @@ import routes from "../../Redux/api";
import { EditDiagnosesBuilder } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder";
import Loading from "@/components/Common/Loading";
import careConfig from "@careConfig";
+import DateFormField from "../Form/FormFields/DateFormField";
import request from "../../Utils/request/request";
interface PreDischargeFormInterface {
@@ -210,6 +211,13 @@ const DischargeModal = ({
const confirmationRequired = encounterDuration.asDays() >= 30;
+ const dischargeOrDeathTime =
+ preDischargeForm[
+ discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
+ ? "death_datetime"
+ : "discharge_date"
+ ];
if (initialDiagnoses == null) {
return ;
}
@@ -297,7 +305,7 @@ const DischargeModal = ({
/>
)}
- i.text == "Expired")?.id
@@ -310,34 +318,28 @@ const DischargeModal = ({
? "Date of Death"
: "Date and Time of Discharge"
}
- type="datetime-local"
value={
- preDischargeForm[
- discharge_reason ===
- DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
- ? "death_datetime"
- : "discharge_date"
- ]
+ dischargeOrDeathTime ? new Date(dischargeOrDeathTime) : new Date()
}
+ popOverClassName="max-h-[50vh]"
onChange={(e) => {
const updates: Record = {
discharge_date: undefined,
death_datetime: undefined,
};
- updates[e.name] = e.value;
+ updates[e.name] = dayjs(e.value).format("YYYY-MM-DDTHH:mm");
setPreDischargeForm((form) => ({ ...form, ...updates }));
}}
required
- min={dayjs(consultationData?.encounter_date).format(
- "YYYY-MM-DDTHH:mm",
- )}
- max={dayjs().format("YYYY-MM-DDTHH:mm")}
+ min={new Date(consultationData?.encounter_date)}
+ max={new Date()}
error={
discharge_reason ===
DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
? errors?.death_datetime
: errors?.discharge_date
}
+ allowTime
/>
{discharge_reason !==
diff --git a/src/components/Facility/TriageForm.tsx b/src/components/Facility/TriageForm.tsx
index 72b391f2318..fe4499e3758 100644
--- a/src/components/Facility/TriageForm.tsx
+++ b/src/components/Facility/TriageForm.tsx
@@ -250,7 +250,6 @@ export const TriageForm = ({ facilityId, id }: Props) => {
value={state.form.entry_date}
disableFuture
onChange={handleFormFieldChange}
- position="LEFT"
placeholder="Entry Date"
error={state.errors.entry_date}
/>
diff --git a/src/components/Form/FormFields/DateFormField.tsx b/src/components/Form/FormFields/DateFormField.tsx
index 674d2572b6d..c27e7421cbb 100644
--- a/src/components/Form/FormFields/DateFormField.tsx
+++ b/src/components/Form/FormFields/DateFormField.tsx
@@ -1,6 +1,4 @@
-import DateInputV2, {
- DatePickerPosition,
-} from "@/components/Common/DateInputV2";
+import DateInputV2 from "@/components/Common/DateInputV2";
import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils";
import FormField from "./FormField";
@@ -11,9 +9,10 @@ type Props = FormFieldBaseProps & {
placeholder?: string;
max?: Date;
min?: Date;
- position?: DatePickerPosition;
disableFuture?: boolean;
disablePast?: boolean;
+ allowTime?: boolean;
+ popOverClassName?: string;
};
/**
@@ -44,12 +43,13 @@ const DateFormField = (props: Props) => {
? new Date(field.value)
: field.value
}
- onChange={field.handleChange}
+ onChange={field.handleChange as (d?: Date) => void}
disabled={field.disabled}
max={props.max ?? (props.disableFuture ? new Date() : undefined)}
min={props.min ?? (props.disablePast ? yesterday() : undefined)}
- position={props.position ?? "RIGHT"}
placeholder={props.placeholder}
+ allowTime={props.allowTime}
+ popOverClassName={props.popOverClassName}
/>
);
diff --git a/src/components/Form/FormFields/DateRangeFormField.tsx b/src/components/Form/FormFields/DateRangeFormField.tsx
index d7b7a62dbce..a5e814278b8 100644
--- a/src/components/Form/FormFields/DateRangeFormField.tsx
+++ b/src/components/Form/FormFields/DateRangeFormField.tsx
@@ -10,6 +10,7 @@ type Props = FormFieldBaseProps & {
min?: Date;
disableFuture?: boolean;
disablePast?: boolean;
+ allowTime?: boolean;
};
/**
@@ -23,6 +24,7 @@ type Props = FormFieldBaseProps & {
* label="Predicted date of birth"
* required
* disablePast // equivalent to min={new Date()}
+ * time={true} // allows picking time as well
* />
* ```
*/
@@ -38,6 +40,7 @@ const DateRangeFormField = (props: Props) => {
disabled={field.disabled}
min={props.min || (props.disableFuture ? new Date() : undefined)}
max={props.max || (props.disablePast ? new Date() : undefined)}
+ allowTime={props.allowTime}
/>
);
diff --git a/src/components/Form/FormFields/Utils.ts b/src/components/Form/FormFields/Utils.ts
index ed01f01c4ab..d7d2848a51e 100644
--- a/src/components/Form/FormFields/Utils.ts
+++ b/src/components/Form/FormFields/Utils.ts
@@ -1,3 +1,4 @@
+import { FocusEvent } from "react";
import { FieldError } from "../FieldValidators";
export type FieldChangeEvent = { name: string; value: T };
@@ -29,6 +30,8 @@ export type FormFieldBaseProps = {
onChange: FieldChangeEventHandler;
value?: T;
error?: FieldError;
+ onFocus?: (event: FocusEvent) => void;
+ onBlur?: (event: FocusEvent) => void;
};
/**
diff --git a/src/components/Medicine/AdministerMedicine.tsx b/src/components/Medicine/AdministerMedicine.tsx
index 2813999895b..13c6f4bab1a 100644
--- a/src/components/Medicine/AdministerMedicine.tsx
+++ b/src/components/Medicine/AdministerMedicine.tsx
@@ -8,13 +8,13 @@ import CareIcon from "../../CAREUI/icons/CareIcon";
import { formatDateTime } from "../../Utils/utils";
import { useTranslation } from "react-i18next";
import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
-import TextFormField from "../Form/FormFields/TextFormField";
import dayjs from "../../Utils/dayjs";
import useSlug from "@/common/hooks/useSlug";
import request from "../../Utils/request/request";
import MedicineRoutes from "./routes";
import DosageFormField from "../Form/FormFields/DosageFormField";
import { AdministrationDosageValidator } from "./validators";
+import DateFormField from "../Form/FormFields/DateFormField";
interface Props {
prescription: Prescription;
@@ -141,14 +141,17 @@ export default function AdministerMedicine({ prescription, ...props }: Props) {
}}
errorClassName="hidden"
/>
- setCustomTime(value)}
+ value={customTime ? new Date(customTime) : new Date()}
+ onChange={({ value }) =>
+ setCustomTime(dayjs(value).format("YYYY-MM-DDTHH:mm"))
+ }
disabled={!isCustomTime}
- min={dayjs(prescription.created_date).format("YYYY-MM-DDTHH:mm")}
- max={dayjs().format("YYYY-MM-DDTHH:mm")}
+ min={new Date(prescription.created_date)}
+ max={new Date()}
+ errorClassName="hidden"
+ allowTime
/>
diff --git a/src/components/Medicine/MedicineAdministration.tsx b/src/components/Medicine/MedicineAdministration.tsx
index 1b29fa7d108..d2daf370207 100644
--- a/src/components/Medicine/MedicineAdministration.tsx
+++ b/src/components/Medicine/MedicineAdministration.tsx
@@ -9,12 +9,12 @@ import { Error, Success } from "../../Utils/Notifications";
import { formatDateTime } from "../../Utils/utils";
import { useTranslation } from "react-i18next";
import dayjs from "../../Utils/dayjs";
-import TextFormField from "../Form/FormFields/TextFormField";
import request from "../../Utils/request/request";
import MedicineRoutes from "./routes";
import useSlug from "@/common/hooks/useSlug";
import DosageFormField from "../Form/FormFields/DosageFormField";
import { AdministrationDosageValidator } from "./validators";
+import DateFormField from "../Form/FormFields/DateFormField";
interface Props {
prescriptions: Prescription[];
@@ -179,7 +179,7 @@ export default function MedicineAdministration(props: Props) {
}
errorClassName="hidden"
/>
-
+
- {
setCustomTime((arr) => {
const newArr = [...arr];
- newArr[index] = value;
+ newArr[index] = dayjs(value).format("YYYY-MM-DDTHH:mm");
return newArr;
});
}}
disabled={!shouldAdminister[index] || !isCustomTime[index]}
- min={dayjs(obj.created_date).format("YYYY-MM-DDTHH:mm")}
- max={dayjs().format("YYYY-MM-DDTHH:mm")}
+ min={new Date(obj.created_date)}
+ max={new Date()}
+ className="w-full"
+ errorClassName="hidden"
+ allowTime
/>
diff --git a/src/components/Patient/DailyRounds.tsx b/src/components/Patient/DailyRounds.tsx
index d37e942c109..f6ac117c2ec 100644
--- a/src/components/Patient/DailyRounds.tsx
+++ b/src/components/Patient/DailyRounds.tsx
@@ -29,7 +29,6 @@ import Page from "@/components/Common/components/Page";
import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField";
import { SelectFormField } from "../Form/FormFields/SelectFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
-import TextFormField from "../Form/FormFields/TextFormField";
import { FieldChangeEvent } from "../Form/FormFields/Utils";
import PatientCategorySelect from "./PatientCategorySelect";
import RadioFormField from "../Form/FormFields/RadioFormField";
@@ -55,6 +54,7 @@ import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
import SymptomsApi from "../Symptoms/api";
import { scrollTo } from "../../Utils/utils";
import { ICD11DiagnosisModel } from "../Facility/models";
+import DateFormField from "../Form/FormFields/DateFormField";
import NursingCare from "../LogUpdate/Sections/NursingCare";
import Loading from "@/components/Common/Loading";
@@ -634,16 +634,24 @@ export const DailyRounds = (props: any) => {
/>
-
+ handleFormFieldChange({
+ ...e,
+ value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"),
+ })
+ }
+ allowTime
+ errorClassName="hidden"
/>
diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx
index 77f43c04251..6aab7799772 100644
--- a/src/components/Patient/PatientRegister.tsx
+++ b/src/components/Patient/PatientRegister.tsx
@@ -1219,7 +1219,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {
{...field("date_of_birth")}
errorClassName="hidden"
required
- position="LEFT"
disableFuture
/>
@@ -1330,7 +1329,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {
containerClassName="w-full"
{...field("last_menstruation_start_date")}
label="Last Menstruation Start Date"
- position="LEFT"
disableFuture
required
/>
@@ -1355,7 +1353,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {
containerClassName="w-full"
{...field("date_of_delivery")}
label="Date of Delivery"
- position="LEFT"
disableFuture
required
/>
@@ -1727,7 +1724,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {
{...field("last_vaccinated_date")}
label="Last Date of Vaccination"
disableFuture={true}
- position="LEFT"
/>
@@ -1759,7 +1755,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {
{...field("date_declared_positive")}
label="Date Patient is Declared Positive for COVID"
disableFuture
- position="LEFT"
/>
@@ -1770,7 +1765,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {
id="date_of_test"
label="Date of Sample given for COVID Test"
disableFuture
- position="LEFT"
/>
diff --git a/src/components/Symptoms/SymptomsBuilder.tsx b/src/components/Symptoms/SymptomsBuilder.tsx
index 1be74797278..3676e2ad607 100644
--- a/src/components/Symptoms/SymptomsBuilder.tsx
+++ b/src/components/Symptoms/SymptomsBuilder.tsx
@@ -191,7 +191,6 @@ const SymptomEntry = (props: {
name="cure_date"
value={symptom.cure_date ? new Date(symptom.cure_date) : undefined}
disableFuture
- position="CENTER"
placeholder="Date of cure"
min={new Date(symptom.onset_date)}
disabled={disabled}
@@ -293,7 +292,6 @@ const AddSymptom = (props: {
return (
{
required
value={getDate(state.form.date_of_birth)}
onChange={handleDateChange}
- position="LEFT"
disableFuture
/>
diff --git a/src/components/Users/UserProfile.tsx b/src/components/Users/UserProfile.tsx
index b84ad7e2bec..edbb5dafdfa 100644
--- a/src/components/Users/UserProfile.tsx
+++ b/src/components/Users/UserProfile.tsx
@@ -777,7 +777,6 @@ export default function UserProfile() {
required
className="col-span-6 sm:col-span-3"
value={getDate(states.form.date_of_birth)}
- position="LEFT"
disableFuture={true}
/>