From 3e7281e4db9d1532fc9f566f18e89450992ee51c Mon Sep 17 00:00:00 2001
From: Andreas Weyer
Date: Sat, 17 Jan 2026 13:06:56 +0000
Subject: [PATCH] Update consolidated and improved tooltips
---
src/App.tsx | 39 +++--
src/components/day-schedule.tsx | 162 +++++++++---------
src/components/settings.tsx | 40 +----
src/components/simulation-chart.tsx | 57 +++---
src/components/ui/form-numeric-input.tsx | 10 +-
.../ui/icon-button-with-tooltip.tsx | 49 ++++++
6 files changed, 180 insertions(+), 177 deletions(-)
create mode 100644 src/components/ui/icon-button-with-tooltip.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 74b93be..624a7a8 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -19,6 +19,8 @@ import Settings from './components/settings';
import LanguageSelector from './components/language-selector';
import DisclaimerModal from './components/disclaimer-modal';
import { Button } from './components/ui/button';
+import { TooltipProvider } from './components/ui/tooltip';
+import { IconButtonWithTooltip } from './components/ui/icon-button-with-tooltip';
import { PROJECT_REPOSITORY_URL, APP_VERSION } from './constants/defaults';
// Custom Hooks
@@ -100,19 +102,20 @@ const MedPlanAssistant = () => {
} = useSimulation(appState);
return (
-
- {/* Disclaimer Modal */}
-
+
+
+ {/* Disclaimer Modal */}
+
-
+
-
+
);
};
diff --git a/src/components/day-schedule.tsx b/src/components/day-schedule.tsx
index 5b94897..126cb81 100644
--- a/src/components/day-schedule.tsx
+++ b/src/components/day-schedule.tsx
@@ -14,7 +14,8 @@ import { Card, CardContent } from './ui/card';
import { Badge } from './ui/badge';
import { FormTimeInput } from './ui/form-time-input';
import { FormNumericInput } from './ui/form-numeric-input';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
+import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
+import { IconButtonWithTooltip } from './ui/icon-button-with-tooltip';
import CollapsibleCardHeader from './ui/collapsible-card-header';
import { Plus, Copy, Trash2, ArrowDownAZ, TrendingUp, TrendingDown } from 'lucide-react';
import type { DayGroup } from '../constants/defaults';
@@ -116,25 +117,23 @@ const DaySchedule: React.FC = ({
rightSection={
<>
{canAddDay && (
-
+ />
)}
{!day.isTemplate && (
-
+ />
)}
>
}
@@ -143,58 +142,54 @@ const DaySchedule: React.FC = ({
{t('day')} {dayIndex + 1}
{!day.isTemplate && doseCountDiff !== 0 ? (
-
-
-
-
-
+ {doseCountDiff > 0 ? : }
+ {day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
+
+
+
+
+
+ {doseCountDiff > 0 ? '+' : ''}{doseCountDiff} {Math.abs(doseCountDiff) === 1 ? t('dose') : t('doses')} {t('comparedToRegularPlan')}
+
+
+
) : (
{day.doses.length} {day.doses.length === 1 ? t('dose') : t('doses')}
)}
{!day.isTemplate && Math.abs(totalMgDiff) > 0.1 ? (
-
-
-
-
+
+
+ 0 ? 'bg-blue-50' : 'bg-orange-50'}`}
>
- 0 ? 'bg-blue-50' : 'bg-orange-50'}`}
- >
- {totalMgDiff > 0 ? : }
- {day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
-
-
-
-
-
- {totalMgDiff > 0 ? '+' : ''}{totalMgDiff.toFixed(1)} mg {t('comparedToRegularPlan')}
-
-
-
-
+ {totalMgDiff > 0 ? : }
+ {day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
+
+
+
+
+
+ {totalMgDiff > 0 ? '+' : ''}{totalMgDiff.toFixed(1)} mg {t('comparedToRegularPlan')}
+
+
+
) : (
{day.doses.reduce((sum, dose) => sum + (parseFloat(dose.ldx) || 0), 0).toFixed(1)} mg
@@ -207,31 +202,29 @@ const DaySchedule: React.FC = ({
{t('time')}
-
-
-
- !isDaySorted(day) && onSortDoses(day.id)}
- disabled={isDaySorted(day)}
- >
-
-
-
-
-
- {isDaySorted(day) ? t('sortByTimeSorted') : t('sortByTimeNeeded')}
-
-
-
-
+
+
+ !isDaySorted(day) && onSortDoses(day.id)}
+ disabled={isDaySorted(day)}
+ >
+
+
+
+
+
+ {isDaySorted(day) ? t('sortByTimeSorted') : t('sortByTimeNeeded')}
+
+
+
{t('ldx')} (mg)
@@ -267,16 +260,15 @@ const DaySchedule: React.FC
= ({
errorMessage={t('errorNumberRequired')}
warningMessage={t('warningZeroDose')}
/>
- onRemoveDose(day.id, dose.id)}
+ icon={}
+ tooltip={t('removeDose')}
size="sm"
variant="ghost"
disabled={day.isTemplate && day.doses.length === 1}
className="h-9 w-9 p-0"
- title={t('removeDose')}
- >
-
-
+ />
);
})}
diff --git a/src/components/settings.tsx b/src/components/settings.tsx
index f8f9aec..198be09 100644
--- a/src/components/settings.tsx
+++ b/src/components/settings.tsx
@@ -15,7 +15,7 @@ import { Label } from './ui/label';
import { Switch } from './ui/switch';
import { Separator } from './ui/separator';
import { Button } from './ui/button';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
+import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { FormNumericInput } from './ui/form-numeric-input';
import CollapsibleCardHeader from './ui/collapsible-card-header';
@@ -269,7 +269,6 @@ const Settings = ({
-
setOpenTooltipId(open ? 'showTemplateDay' : null)}>
{tWithDefaults(t, 'showTemplateDayTooltip', defaultsForT)}
-
@@ -300,7 +298,6 @@ const Settings = ({
-
setOpenTooltipId(open ? 'showDayReferenceLines' : null)}>
{tWithDefaults(t, 'showDayReferenceLinesTooltip', defaultsForT)}
-
@@ -331,7 +327,6 @@ const Settings = ({
-
setOpenTooltipId(open ? 'showTherapeuticRangeLines' : null)}>
{tWithDefaults(t, 'showTherapeuticRangeLinesTooltip', defaultsForT)}
-
{showTherapeuticRange && (
@@ -356,7 +350,6 @@ const Settings = ({
-
setOpenTooltipId(open ? 'therapeuticRange' : null)}>
{tWithDefaults(t, 'therapeuticRangeTooltip', defaultsForT)}
-
-
setOpenTooltipId(open ? 'displayedDays' : null)}>
{tWithDefaults(t, 'displayedDaysTooltip', defaultsForT)}
-
-
setOpenTooltipId(open ? 'yAxisRange' : null)}>
{tWithDefaults(t, 'yAxisRangeTooltip', defaultsForT)}
-
-
-
)}
@@ -546,7 +532,6 @@ const Settings = ({
-
setOpenTooltipId(open ? 'simulationDuration' : null)}>
{tWithDefaults(t, 'simulationDurationTooltip', defaultsForT)}
-
{t('steadyStateDays')}
-
setOpenTooltipId(open ? 'steadyStateDays' : null)}>
{tWithDefaults(t, 'steadyStateDaysTooltip', defaultsForT)}
-
{steadyStateDaysEnabled && (
@@ -648,7 +630,6 @@ const Settings = ({
-
setOpenTooltipId(open ? 'halfLife' : null)}>
{renderTooltipWithLinks(tWithDefaults(t, 'halfLifeTooltip', defaultsForT))}
-
-
setOpenTooltipId(open ? 'conversionHalfLife' : null)}>
{tWithDefaults(t, 'conversionHalfLifeTooltip', defaultsForT)}
-
-
setOpenTooltipId(open ? 'absorptionHalfLife' : null)}>
{tWithDefaults(t, 'absorptionHalfLifeTooltip', defaultsForT)}
-
{t('weightBasedVdScaling')}
-
setOpenTooltipId(open ? 'weightBasedVd' : null)}>
{tWithDefaults(t, 'weightBasedVdTooltip', defaultsForT)}
-
{pkParams.advanced.weightBasedVd.enabled && (
-
setOpenTooltipId(open ? 'bodyWeight' : null)}>
{renderTooltipWithLinks(tWithDefaults(t, 'bodyWeightTooltip', defaultsForT))}
-
{t('foodEffectEnabled')}
-
setOpenTooltipId(open ? 'foodEffect' : null)}>
{tWithDefaults(t, 'foodEffectTooltip', defaultsForT)}
-
{pkParams.advanced.foodEffect.enabled && (
-
setOpenTooltipId(open ? 'tmaxDelay' : null)}>
{renderTooltipWithLinks(tWithDefaults(t, 'tmaxDelayTooltip', defaultsForT))}
-
{t('urinePHTendency')}
-
setOpenTooltipId(open ? 'urinePH' : null)}>
{tWithDefaults(t, 'urinePHTooltip', defaultsForT)}
-
{pkParams.advanced.urinePh.enabled && (
-
setOpenTooltipId(open ? 'urinePHValue' : null)}>
{tWithDefaults(t, 'urinePHValueTooltip', defaultsForT)}
-
-
setOpenTooltipId(open ? 'oralBioavailability' : null)}>
{renderTooltipWithLinks(tWithDefaults(t, 'oralBioavailabilityTooltip', defaultsForT))}
-
{
? scrollableWidth
: Math.ceil((scrollableWidth / dispDays) * simDays);
+ // Render legend with tooltips for full names (custom legend renderer)
const renderLegend = React.useCallback((props: any) => {
const { payload } = props;
if (!payload) return null;
return (
-
-
- {payload.map((item: any) => {
- const labelInfo = seriesLabels[item.dataKey] || { display: item.value, full: item.value };
- const opacity = item.payload?.opacity ?? 1;
+
+ {payload.map((item: any) => {
+ const labelInfo = seriesLabels[item.dataKey] || { display: item.value, full: item.value };
+ const opacity = item.payload?.opacity ?? 1;
- return (
- -
-
-
-
-
- {labelInfo.display}
-
-
-
- {labelInfo.full}
-
-
-
- );
- })}
-
-
+ return (
+
+
+
+
+
+ {labelInfo.display}
+
+
+
+ {labelInfo.full}
+
+
+
+ );
+ })}
+
);
}, [seriesLabels]);
diff --git a/src/components/ui/form-numeric-input.tsx b/src/components/ui/form-numeric-input.tsx
index fd25fd8..714e27b 100644
--- a/src/components/ui/form-numeric-input.tsx
+++ b/src/components/ui/form-numeric-input.tsx
@@ -11,6 +11,7 @@
import * as React from "react"
import { Minus, Plus, X } from "lucide-react"
import { Button } from "./button"
+import { IconButtonWithTooltip } from "./icon-button-with-tooltip"
import { Input } from "./input"
import { cn } from "../../lib/utils"
import { useTranslation } from "react-i18next"
@@ -199,8 +200,10 @@ const FormNumericInput = React.forwardRef(
{clearButton && allowEmpty && (
- }
+ tooltip={t('buttonClear')}
variant="outline"
size="icon"
className={cn(
@@ -210,10 +213,7 @@ const FormNumericInput = React.forwardRef(
)}
onClick={() => onChange('')}
tabIndex={-1}
- title={ t('buttonClear') }
- >
-
-
+ />
)}
{unit &&
{unit}}
diff --git a/src/components/ui/icon-button-with-tooltip.tsx b/src/components/ui/icon-button-with-tooltip.tsx
new file mode 100644
index 0000000..8551a1e
--- /dev/null
+++ b/src/components/ui/icon-button-with-tooltip.tsx
@@ -0,0 +1,49 @@
+/**
+ * IconButtonWithTooltip
+ *
+ * A reusable button component that combines an icon button with a tooltip.
+ * Provides consistent tooltip behavior across the app for icon-only action buttons.
+ *
+ * @author Andreas Weyer
+ * @license MIT
+ */
+
+import React from 'react';
+import { Button, ButtonProps } from './button';
+import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
+
+interface IconButtonWithTooltipProps extends Omit
{
+ /** The icon element to display in the button */
+ icon: React.ReactNode;
+ /** The tooltip text to show on hover */
+ tooltip: string;
+ /** Optional side for tooltip positioning */
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
+}
+
+export const IconButtonWithTooltip: React.FC = ({
+ icon,
+ tooltip,
+ tooltipSide = 'top',
+ disabled,
+ ...buttonProps
+}) => {
+ return (
+
+
+
+ {icon}
+
+
+ {!disabled && (
+
+ {tooltip}
+
+ )}
+
+ );
+};