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) => (
- {d} +
+ {day} +
-
- ); - })} -
- - )} - {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} />