diff --git a/src/App.js b/src/App.js index 26a3b6b..7279df8 100644 --- a/src/App.js +++ b/src/App.js @@ -67,9 +67,9 @@ const MedPlanAssistant = () => { -
+
{/* Left Column - Controls */} -
+
{
{/* Center Column - Chart */} -
+
{/* Right Column - Settings */} -
+
onDeviationChange(index, 'time', newTime)} + errorMessage={t.timeRequired} />
- - +
+
+ + + + {allowEmpty && ( + + )} + {hasError && ( +
+ )} +
{unit && {unit}} + {hasError && showErrorTooltip && ( +
+
+ + + + +
+ {errorMessage} +
+ )}
); }; diff --git a/src/components/Settings.js b/src/components/Settings.js index b89adcc..c0a82d7 100644 --- a/src/components/Settings.js +++ b/src/components/Settings.js @@ -39,6 +39,7 @@ const Settings = ({ min={2} max={7} unit={t.days} + errorMessage={t.fieldRequired} />
@@ -51,19 +52,20 @@ const Settings = ({ min={1} max={parseInt(simulationDays, 10) || 1} unit={t.days} + errorMessage={t.fieldRequired} />
-
-
+
+
onUpdateUiSetting('yAxisMin', val)} increment={'5'} min={0} placeholder={t.auto} - unit="ng/ml" + allowEmpty={true} />
- @@ -75,20 +77,21 @@ const Settings = ({ min={0} placeholder={t.auto} unit="ng/ml" + allowEmpty={true} />
-
-
+
+
onUpdateTherapeuticRange('min', val)} increment={'0.5'} min={0} placeholder={t.min} - unit="ng/ml" + errorMessage={t.fieldRequired} />
- @@ -100,6 +103,7 @@ const Settings = ({ min={0} placeholder={t.max} unit="ng/ml" + errorMessage={t.fieldRequired} />
@@ -113,6 +117,7 @@ const Settings = ({ increment={'0.5'} min={0.1} unit="h" + errorMessage={t.fieldRequired} />
@@ -125,6 +130,7 @@ const Settings = ({ increment={'0.1'} min={0.1} unit="h" + errorMessage={t.fieldRequired} />
@@ -135,6 +141,7 @@ const Settings = ({ increment={'0.1'} min={0.1} unit={t.faster} + errorMessage={t.fieldRequired} />
diff --git a/src/components/TimeInput.js b/src/components/TimeInput.js index b438b8d..2aa3852 100644 --- a/src/components/TimeInput.js +++ b/src/components/TimeInput.js @@ -1,10 +1,13 @@ import React from 'react'; -const TimeInput = ({ value, onChange }) => { +const TimeInput = ({ value, onChange, errorMessage = 'Time is required' }) => { const [displayValue, setDisplayValue] = React.useState(value); const [isPickerOpen, setIsPickerOpen] = React.useState(false); + const [hasError, setHasError] = React.useState(false); + const [showErrorTooltip, setShowErrorTooltip] = React.useState(false); const [pickerHours, pickerMinutes] = (value || "00:00").split(':').map(Number); const pickerRef = React.useRef(null); + const containerRef = React.useRef(null); React.useEffect(() => { setDisplayValue(value); @@ -16,19 +19,32 @@ const TimeInput = ({ value, onChange }) => { if (pickerRef.current && !pickerRef.current.contains(event.target)) { setIsPickerOpen(false); } + if (containerRef.current && !containerRef.current.contains(event.target)) { + setShowErrorTooltip(false); + } }; - if (isPickerOpen) { + if (isPickerOpen || showErrorTooltip) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, [isPickerOpen]); + }, [isPickerOpen, showErrorTooltip]); const handleBlur = (e) => { - let input = e.target.value.replace(/[^0-9]/g, ''); + const inputValue = e.target.value.trim(); + + // Check if field is empty + if (inputValue === '') { + setHasError(true); + // Small delay before hiding tooltip + setTimeout(() => setShowErrorTooltip(false), 100); + return; + } + + let input = inputValue.replace(/[^0-9]/g, ''); let hours = '00', minutes = '00'; if (input.length <= 2) { hours = input.padStart(2, '0'); @@ -44,11 +60,15 @@ const TimeInput = ({ value, onChange }) => { hours = Math.min(23, parseInt(hours, 10) || 0).toString().padStart(2, '0'); minutes = Math.min(59, parseInt(minutes, 10) || 0).toString().padStart(2, '0'); const formattedTime = `${hours}:${minutes}`; + setHasError(false); + setShowErrorTooltip(false); setDisplayValue(formattedTime); onChange(formattedTime); }; const handleChange = (e) => { + setHasError(false); // Clear error on input + setShowErrorTooltip(false); setDisplayValue(e.target.value); }; @@ -60,19 +80,42 @@ const TimeInput = ({ value, onChange }) => { newMinutes = val; } const formattedTime = `${String(newHours).padStart(2, '0')}:${String(newMinutes).padStart(2, '0')}`; + setHasError(false); // Clear error when using picker + setShowErrorTooltip(false); onChange(formattedTime); }; + const handleFocus = () => { + if (hasError) setShowErrorTooltip(true); + }; + + const handleMouseEnter = () => { + if (hasError) setShowErrorTooltip(true); + }; + + const handleMouseLeave = () => { + setShowErrorTooltip(false); + }; + return ( -
- +
+
+
+ +
{isPickerOpen && ( -
+
{value}
Hour:
@@ -120,6 +163,18 @@ const TimeInput = ({ value, onChange }) => {
)} + {hasError && showErrorTooltip && ( +
+
+ + + + +
+ {errorMessage} +
+ )} +
); }; diff --git a/src/constants/defaults.js b/src/constants/defaults.js index 2facd99..5692b18 100644 --- a/src/constants/defaults.js +++ b/src/constants/defaults.js @@ -12,7 +12,7 @@ export const getDefaultState = () => ({ { time: '06:30', dose: '25', label: 'morning' }, { time: '12:30', dose: '10', label: 'midday' }, { time: '17:00', dose: '10', label: 'afternoon' }, - { time: '21:00', dose: '10', label: 'evening' }, + { time: '22:00', dose: '10', label: 'evening' }, { time: '01:00', dose: '0', label: 'night' }, ], steadyStateConfig: { daysOnMedication: '7' }, diff --git a/src/locales/de.js b/src/locales/de.js index cb8ab89..8b18960 100644 --- a/src/locales/de.js +++ b/src/locales/de.js @@ -71,7 +71,11 @@ export const de = { // Footer disclaimer importantNote: "Wichtiger Hinweis", - disclaimer: "Dieses Tool dient ausschließlich zu Illustrations- und Informationszwecken. Es ist kein medizinisches Gerät und ersetzt nicht die Beratung durch einen Arzt oder Apotheker. Alle Berechnungen sind Simulationen, die auf allgemeinen pharmakokinetischen Modellen basieren und von individuellen Faktoren erheblich abweichen können. Bitte konsultiere deinen behandelnden Arzt, bevor du Anpassungen an deiner Medikation vornimmst." + disclaimer: "Dieses Tool dient ausschließlich zu Illustrations- und Informationszwecken. Es ist kein medizinisches Gerät und ersetzt nicht die Beratung durch einen Arzt oder Apotheker. Alle Berechnungen sind Simulationen, die auf allgemeinen pharmakokinetischen Modellen basieren und von individuellen Faktoren erheblich abweichen können. Bitte konsultiere deinen behandelnden Arzt, bevor du Anpassungen an deiner Medikation vornimmst.", + + // Field validation + fieldRequired: "Dieses Feld ist erforderlich", + timeRequired: "Zeitangabe ist erforderlich" }; export default de; diff --git a/src/locales/en.js b/src/locales/en.js index 339a74b..952f317 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -71,7 +71,11 @@ export const en = { // Footer disclaimer importantNote: "Important Notice", - disclaimer: "This tool is for illustration and information purposes only. It is not a medical device and does not replace consultation with a doctor or pharmacist. All calculations are simulations based on general pharmacokinetic models and may differ significantly from individual factors. Please consult your treating physician before making adjustments to your medication." + disclaimer: "This tool is for illustration and information purposes only. It is not a medical device and does not replace consultation with a doctor or pharmacist. All calculations are simulations based on general pharmacokinetic models and may differ significantly from individual factors. Please consult your treating physician before making adjustments to your medication.", + + // Field validation + fieldRequired: "This field is required", + timeRequired: "Time is required" }; export default en;