Compare commits

..

8 Commits

7 changed files with 175 additions and 44 deletions

View File

@@ -315,6 +315,7 @@ const MedPlanAssistant = () => {
onSwitchProfile={switchProfile} onSwitchProfile={switchProfile}
onSaveProfile={saveProfile} onSaveProfile={saveProfile}
onSaveProfileAs={saveProfileAs} onSaveProfileAs={saveProfileAs}
onRenameProfile={updateProfileName}
onDeleteProfile={deleteProfile} onDeleteProfile={deleteProfile}
t={t} t={t}
/> />
@@ -333,7 +334,7 @@ const MedPlanAssistant = () => {
</div> </div>
{/* Right Column - Settings */} {/* Right Column - Settings */}
<div className="lg:col-span-1 space-y-6"> <div className="lg:col-span-1 space-y-4">
<Settings <Settings
pkParams={pkParams} pkParams={pkParams}
therapeuticRange={therapeuticRange} therapeuticRange={therapeuticRange}
@@ -351,6 +352,7 @@ const MedPlanAssistant = () => {
</div> </div>
{/* Footer */}
<footer className="mt-8 p-4 bg-muted rounded-lg text-sm border"> <footer className="mt-8 p-4 bg-muted rounded-lg text-sm border">
<div className="space-y-3"> <div className="space-y-3">
<div> <div>

View File

@@ -21,7 +21,7 @@ import {
SelectValue, SelectValue,
} from './ui/select'; } from './ui/select';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
import { Save, Trash2, Plus } from 'lucide-react'; import { Save, Trash2, Plus, Pencil } from 'lucide-react';
import { IconButtonWithTooltip } from './ui/icon-button-with-tooltip'; import { IconButtonWithTooltip } from './ui/icon-button-with-tooltip';
import { MAX_PROFILES, type ScheduleProfile } from '../constants/defaults'; import { MAX_PROFILES, type ScheduleProfile } from '../constants/defaults';
@@ -32,6 +32,7 @@ interface ProfileSelectorProps {
onSwitchProfile: (profileId: string) => void; onSwitchProfile: (profileId: string) => void;
onSaveProfile: () => void; onSaveProfile: () => void;
onSaveProfileAs: (name: string) => string | null; onSaveProfileAs: (name: string) => string | null;
onRenameProfile: (profileId: string, newName: string) => void;
onDeleteProfile: (profileId: string) => boolean; onDeleteProfile: (profileId: string) => boolean;
t: (key: string) => string; t: (key: string) => string;
} }
@@ -43,20 +44,29 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
onSwitchProfile, onSwitchProfile,
onSaveProfile, onSaveProfile,
onSaveProfileAs, onSaveProfileAs,
onRenameProfile,
onDeleteProfile, onDeleteProfile,
t, t,
}) => { }) => {
const [newProfileName, setNewProfileName] = useState(''); const [newProfileName, setNewProfileName] = useState('');
const [isSaveAsMode, setIsSaveAsMode] = useState(false); const [isSaveAsMode, setIsSaveAsMode] = useState(false);
const [isRenameMode, setIsRenameMode] = useState(false);
const [renameName, setRenameName] = useState('');
const activeProfile = profiles.find(p => p.id === activeProfileId); const activeProfile = profiles.find(p => p.id === activeProfileId);
const canDelete = profiles.length > 1; const canDelete = profiles.length > 1;
const canCreateNew = profiles.length < MAX_PROFILES; const canCreateNew = profiles.length < MAX_PROFILES;
// Sort profiles alphabetically (case-insensitive)
const sortedProfiles = [...profiles].sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
);
const handleSelectChange = (value: string) => { const handleSelectChange = (value: string) => {
if (value === '__new__') { if (value === '__new__') {
// Enter "save as" mode // Enter "save as" mode
setIsSaveAsMode(true); setIsSaveAsMode(true);
setIsRenameMode(false);
setNewProfileName(''); setNewProfileName('');
} else { } else {
// Confirm before switching if there are unsaved changes // Confirm before switching if there are unsaved changes
@@ -67,6 +77,7 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
} }
onSwitchProfile(value); onSwitchProfile(value);
setIsSaveAsMode(false); setIsSaveAsMode(false);
setIsRenameMode(false);
} }
}; };
@@ -114,6 +125,51 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
} }
}; };
const handleStartRename = () => {
if (activeProfile) {
setIsRenameMode(true);
setIsSaveAsMode(false);
setRenameName(activeProfile.name);
}
};
const handleRename = () => {
if (!renameName.trim() || !activeProfile) {
return;
}
const trimmedName = renameName.trim();
// Check if name is unchanged
if (trimmedName === activeProfile.name) {
setIsRenameMode(false);
return;
}
// Check for duplicate names (excluding current profile)
const isDuplicate = profiles.some(
p => p.id !== activeProfile.id && p.name.toLowerCase() === trimmedName.toLowerCase()
);
if (isDuplicate) {
alert(t('profileNameAlreadyExists') || 'A schedule with this name already exists');
return;
}
onRenameProfile(activeProfile.id, trimmedName);
setIsRenameMode(false);
setRenameName('');
};
const handleRenameKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleRename();
} else if (e.key === 'Escape') {
setIsRenameMode(false);
setRenameName('');
}
};
return ( return (
<Card className="mb-4"> <Card className="mb-4">
<CardContent className="pt-6"> <CardContent className="pt-6">
@@ -135,21 +191,32 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={t('profileSaveAsPlaceholder')} placeholder={t('profileSaveAsPlaceholder')}
autoFocus autoFocus
className="h-9 rounded-r-none border-r-0 w-[360px] bg-background" className="h-9 rounded-r-none border-r-0 w-[288px] bg-background"
/>
) : isRenameMode ? (
<Input
id="profile-selector"
type="text"
value={renameName}
onChange={(e) => setRenameName(e.target.value)}
onKeyDown={handleRenameKeyDown}
placeholder={t('profileRenamePlaceholder')}
autoFocus
className="h-9 rounded-r-none border-r-0 w-[288px] bg-background"
/> />
) : ( ) : (
<Select <Select
value={activeProfileId} value={activeProfileId}
onValueChange={handleSelectChange} onValueChange={handleSelectChange}
> >
<SelectTrigger id="profile-selector" className="h-9 rounded-r-none border-r-0 w-[360px] bg-background"> <SelectTrigger id="profile-selector" className="h-9 rounded-r-none border-r-0 w-[288px] bg-background">
<SelectValue> <SelectValue>
{activeProfile?.name} {activeProfile?.name}
{hasUnsavedChanges && ' *'} {hasUnsavedChanges && ' *'}
</SelectValue> </SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{profiles.map(profile => ( {sortedProfiles.map(profile => (
<SelectItem key={profile.id} value={profile.id}> <SelectItem key={profile.id} value={profile.id}>
{profile.name} {profile.name}
</SelectItem> </SelectItem>
@@ -178,10 +245,21 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
{/* Save button - integrated */} {/* Save button - integrated */}
<IconButtonWithTooltip <IconButtonWithTooltip
onClick={isSaveAsMode ? handleSaveAs : onSaveProfile} onClick={isSaveAsMode ? handleSaveAs : isRenameMode ? handleRename : onSaveProfile}
icon={<Save className="h-4 w-4" />} icon={<Save className="h-4 w-4" />}
tooltip={isSaveAsMode ? t('profileSaveAs') : t('profileSave')} tooltip={isSaveAsMode ? t('profileSaveAs') : isRenameMode ? t('profileRename') : t('profileSave')}
disabled={(isSaveAsMode && !newProfileName.trim()) || (!isSaveAsMode && !hasUnsavedChanges)} disabled={(isSaveAsMode && !newProfileName.trim()) || (isRenameMode && !renameName.trim()) || (!isSaveAsMode && !isRenameMode && !hasUnsavedChanges)}
variant="outline"
size="icon"
className="rounded-none border-r-0"
/>
{/* Rename button - integrated */}
<IconButtonWithTooltip
onClick={handleStartRename}
icon={<Pencil className="h-4 w-4" />}
tooltip={t('profileRename')}
disabled={isSaveAsMode || isRenameMode}
variant="outline" variant="outline"
size="icon" size="icon"
className="rounded-none border-r-0" className="rounded-none border-r-0"
@@ -192,7 +270,7 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
onClick={handleDelete} onClick={handleDelete}
icon={<Trash2 className="h-4 w-4" />} icon={<Trash2 className="h-4 w-4" />}
tooltip={canDelete ? t('profileDelete') : t('profileDeleteDisabled')} tooltip={canDelete ? t('profileDelete') : t('profileDeleteDisabled')}
disabled={!canDelete || isSaveAsMode} disabled={!canDelete || isSaveAsMode || isRenameMode}
variant="outline" variant="outline"
size="icon" size="icon"
className="rounded-l-none text-destructive hover:bg-destructive hover:text-destructive-foreground" className="rounded-l-none text-destructive hover:bg-destructive hover:text-destructive-foreground"
@@ -216,6 +294,24 @@ export const ProfileSelector: React.FC<ProfileSelectorProps> = ({
</button> </button>
</div> </div>
)} )}
{/* Helper text for rename mode */}
{isRenameMode && (
<div className="flex items-center gap-2">
<p className="text-xs text-muted-foreground flex-1">
{t('profileRenameHelp')}
</p>
<button
onClick={() => {
setIsRenameMode(false);
setRenameName('');
}}
className="text-xs text-muted-foreground hover:text-foreground underline"
>
{t('cancel')}
</button>
</div>
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -394,11 +394,12 @@ const Settings = ({
min={0} min={0}
max={500} max={500}
placeholder={t('min')} placeholder={t('min')}
required={true} required={false}
error={!!therapeuticRangeError || !therapeuticRange.min} error={!!therapeuticRangeError}
errorMessage={formatText(therapeuticRangeError || t('errorTherapeuticRangeMinRequired') || 'Minimum therapeutic range is required')} errorMessage={formatText(therapeuticRangeError)}
showResetButton={true} showResetButton={true}
defaultValue={defaultsForT.therapeuticRangeMin} defaultValue={defaultsForT.therapeuticRangeMin}
allowEmpty={true}
/> />
<span className="text-muted-foreground">-</span> <span className="text-muted-foreground">-</span>
<FormNumericInput <FormNumericInput
@@ -409,11 +410,12 @@ const Settings = ({
max={500} max={500}
placeholder={t('max')} placeholder={t('max')}
unit="ng/ml" unit="ng/ml"
required={true} required={false}
error={!!therapeuticRangeError || !therapeuticRange.max} error={!!therapeuticRangeError}
errorMessage={formatText(therapeuticRangeError || t('errorTherapeuticRangeMaxRequired') || 'Maximum therapeutic range is required')} errorMessage={formatText(therapeuticRangeError)}
showResetButton={true} showResetButton={true}
defaultValue={defaultsForT.therapeuticRangeMax} defaultValue={defaultsForT.therapeuticRangeMax}
allowEmpty={true}
/> />
</div> </div>
</div> </div>
@@ -517,7 +519,7 @@ const Settings = ({
onValueChange={value => onUpdateUiSetting('showDayTimeOnXAxis', value)} onValueChange={value => onUpdateUiSetting('showDayTimeOnXAxis', value)}
showResetButton={true} showResetButton={true}
defaultValue={defaultsForT.showDayTimeOnXAxis} defaultValue={defaultsForT.showDayTimeOnXAxis}
triggerClassName="w-[360px]" triggerClassName="w-[288px]"
> >
<SelectContent> <SelectContent>
<Tooltip> <Tooltip>
@@ -826,7 +828,7 @@ const Settings = ({
onValueChange={(value) => updateAdvanced('standardVd', 'preset', value as 'adult' | 'child' | 'custom' | 'weight-based')} onValueChange={(value) => updateAdvanced('standardVd', 'preset', value as 'adult' | 'child' | 'custom' | 'weight-based')}
showResetButton={true} showResetButton={true}
defaultValue={defaultsForT.standardVdPreset} defaultValue={defaultsForT.standardVdPreset}
triggerClassName="w-[360px]" triggerClassName="w-[288px]"
> >
<SelectContent> <SelectContent>
<SelectItem value="adult">{t('standardVdPresetAdult')}</SelectItem> <SelectItem value="adult">{t('standardVdPresetAdult')}</SelectItem>
@@ -961,7 +963,7 @@ const Settings = ({
} }
showResetButton={true} showResetButton={true}
defaultValue={defaultsForT.urinePh} defaultValue={defaultsForT.urinePh}
triggerClassName="w-[360px]" triggerClassName="w-[288px]"
> >
<SelectContent> <SelectContent>
<SelectItem value="normal">{t('urinePHModeNormal')}</SelectItem> <SelectItem value="normal">{t('urinePHModeNormal')}</SelectItem>
@@ -1002,7 +1004,7 @@ const Settings = ({
}} }}
showResetButton={true} showResetButton={true}
defaultValue={defaultsForT.ageGroup} defaultValue={defaultsForT.ageGroup}
triggerClassName="w-[360px]" triggerClassName="w-[288px]"
> >
<SelectContent> <SelectContent>
<SelectItem value="adult">{t('ageGroupAdult')}</SelectItem> <SelectItem value="adult">{t('ageGroupAdult')}</SelectItem>

View File

@@ -76,6 +76,11 @@ const SimulationChart = React.memo(({
const containerRef = React.useRef<HTMLDivElement>(null); const containerRef = React.useRef<HTMLDivElement>(null);
const { width: containerWidth } = useElementSize(containerRef, 150); const { width: containerWidth } = useElementSize(containerRef, 150);
// Guard against invalid dimensions during initial render
const yAxisWidth = 80;
const minContainerWidth = yAxisWidth + 100; // Minimum 100px for chart area
const safeContainerWidth = Math.max(containerWidth, minContainerWidth);
// Track current theme for chart styling // Track current theme for chart styling
const [isDarkTheme, setIsDarkTheme] = React.useState(false); const [isDarkTheme, setIsDarkTheme] = React.useState(false);
@@ -96,9 +101,8 @@ const SimulationChart = React.memo(({
return () => observer.disconnect(); return () => observer.disconnect();
}, []); }, []);
// Y-axis takes ~80px, scrollable area gets the rest // Calculate scrollable width using safe container width
const yAxisWidth = 80; const scrollableWidth = safeContainerWidth - yAxisWidth;
const scrollableWidth = containerWidth - yAxisWidth;
// Calculate chart width for scrollable area // Calculate chart width for scrollable area
const chartWidth = simDays <= dispDays const chartWidth = simDays <= dispDays
@@ -106,7 +110,7 @@ const SimulationChart = React.memo(({
: Math.ceil((scrollableWidth / dispDays) * simDays); : Math.ceil((scrollableWidth / dispDays) * simDays);
// Use shorter captions on narrow containers to reduce wrapping // Use shorter captions on narrow containers to reduce wrapping
const isCompactLabels = containerWidth < 640; // tweakable threshold for mobile const isCompactLabels = safeContainerWidth < 640; // tweakable threshold for mobile
// Precompute series labels with translations // Precompute series labels with translations
const seriesLabels = React.useMemo<Record<string, { full: string; short: string; display: string }>>(() => { const seriesLabels = React.useMemo<Record<string, { full: string; short: string; display: string }>>(() => {
@@ -145,9 +149,9 @@ const SimulationChart = React.memo(({
// Dynamically calculate tick interval based on available pixel width // Dynamically calculate tick interval based on available pixel width
const xTickInterval = React.useMemo(() => { const xTickInterval = React.useMemo(() => {
// Aim for ~46px per label to avoid overlaps on narrow screens // Aim for ~46px per label to avoid overlaps on narrow screens
//const MIN_PX_PER_TICK = 46; //const MIN_PX_PER_TICK = 46;
const MIN_PX_PER_TICK = 46; const MIN_PX_PER_TICK = 56; // increased to 56, partially too tight otherwise
const intervals = [1, 2, 3, 4, 6, 8, 12, 24]; const intervals = [1, 2, 3, 4, 6, 8, 12, 24];
const pxPerDay = scrollableWidth / Math.max(1, dispDays); const pxPerDay = scrollableWidth / Math.max(1, dispDays);
@@ -270,7 +274,7 @@ const SimulationChart = React.memo(({
// User set yAxisMax explicitly // User set yAxisMax explicitly
// Add padding to dataMax and use the higher of manual or (dataMax + padding) // Add padding to dataMax and use the higher of manual or (dataMax + padding)
const range = dataMax - dataMin; const range = dataMax - dataMin;
const padding = range * 0.1; const padding = range * 0.05;
const dataMaxWithPadding = dataMax + padding; const dataMaxWithPadding = dataMax + padding;
// Use manual max only if it's higher than dataMax + padding // Use manual max only if it's higher than dataMax + padding
domainMax = Math.max(numMax, dataMaxWithPadding); domainMax = Math.max(numMax, dataMaxWithPadding);
@@ -279,9 +283,9 @@ const SimulationChart = React.memo(({
domainMax = numMax; domainMax = numMax;
} }
} else if (dataMax !== -Infinity) { // data exists } else if (dataMax !== -Infinity) { // data exists
// Auto mode: add 10% padding above // Auto mode: add 5% padding above
const range = dataMax - dataMin; const range = dataMax - dataMin;
const padding = range * 0.1; const padding = range * 0.05;
domainMax = dataMax + padding; domainMax = dataMax + padding;
} else { // no data } else { // no data
domainMax = 100; domainMax = 100;
@@ -446,6 +450,15 @@ const SimulationChart = React.memo(({
); );
}, [seriesLabels]); }, [seriesLabels]);
// Don't render chart if dimensions are invalid (prevents crash during initialization)
if (chartWidth <= 0 || scrollableWidth <= 0) {
return (
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden items-center justify-center text-muted-foreground">
<p>{t('loadingChart', { defaultValue: 'Loading chart...' })}</p>
</div>
);
}
// Render the chart // Render the chart
return ( return (
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden"> <div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden">
@@ -531,7 +544,9 @@ const SimulationChart = React.memo(({
domain={yAxisDomain as any} domain={yAxisDomain as any}
axisLine={{ stroke: isDarkTheme ? '#ccc' : '#666' }} axisLine={{ stroke: isDarkTheme ? '#ccc' : '#666' }}
tick={<YAxisTick />} tick={<YAxisTick />}
allowDecimals={true} tickCount={20}
interval={1}
allowDecimals={false}
allowDataOverflow={false} allowDataOverflow={false}
/> />
<RechartsTooltip <RechartsTooltip
@@ -594,7 +609,7 @@ const SimulationChart = React.memo(({
style={{ stroke: isDarkTheme ? '#666' : '#ccc' }} style={{ stroke: isDarkTheme ? '#666' : '#ccc' }}
/> />
{showDayReferenceLines !== false && [...Array(dispDays + 1).keys()].map(day => { {showDayReferenceLines !== false && [...Array(simDays).keys()].map(day => {
// Determine whether to use compact day labels to avoid overlap on narrow screens // Determine whether to use compact day labels to avoid overlap on narrow screens
const pxPerDay = scrollableWidth / Math.max(1, dispDays); const pxPerDay = scrollableWidth / Math.max(1, dispDays);
let label = ""; let label = "";
@@ -625,20 +640,20 @@ const SimulationChart = React.memo(({
/> />
); );
})} })}
{showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && ( {showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && therapeuticRange.min && !isNaN(parseFloat(therapeuticRange.min)) && (
<ReferenceLine <ReferenceLine
y={parseFloat(therapeuticRange.min) || 0} y={parseFloat(therapeuticRange.min)}
label={{ value: t('refLineMin'), position: 'insideTopLeft' }} label={{ value: t('refLineMin'), position: 'insideBottomLeft', style: { fontSize: '0.75rem', fontStyle: 'italic', fill: CHART_COLORS.therapeuticMin } }}
stroke={CHART_COLORS.therapeuticMin} stroke={CHART_COLORS.therapeuticMin}
strokeDasharray="3 3" strokeDasharray="3 3"
xAxisId="hours" xAxisId="hours"
yAxisId="concentration" yAxisId="concentration"
/> />
)} )}
{showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && ( {showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && therapeuticRange.max && !isNaN(parseFloat(therapeuticRange.max)) && (
<ReferenceLine <ReferenceLine
y={parseFloat(therapeuticRange.max) || 0} y={parseFloat(therapeuticRange.max)}
label={{ value: t('refLineMax'), position: 'insideTopLeft' }} label={{ value: t('refLineMax'), position: 'insideTopLeft', style: { fontSize: '0.75rem', fontStyle: 'italic', fill: CHART_COLORS.therapeuticMax } }}
stroke={CHART_COLORS.therapeuticMax} stroke={CHART_COLORS.therapeuticMax}
strokeDasharray="3 3" strokeDasharray="3 3"
xAxisId="hours" xAxisId="hours"

View File

@@ -38,17 +38,25 @@ export function useElementSize<T extends HTMLElement>(
const element = ref.current; const element = ref.current;
if (!element) return; if (!element) return;
// Set initial size // Set initial size (guard against 0 dimensions)
setSize({ const initialWidth = element.clientWidth;
width: element.clientWidth, const initialHeight = element.clientHeight;
height: element.clientHeight,
}); if (initialWidth > 0 && initialHeight > 0) {
setSize({
width: initialWidth,
height: initialHeight,
});
}
// Use ResizeObserver for efficient element size tracking // Use ResizeObserver for efficient element size tracking
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) { for (const entry of entries) {
const { width, height } = entry.contentRect; const { width, height } = entry.contentRect;
setSize({ width, height }); // Guard against invalid dimensions
if (width > 0 && height > 0) {
setSize({ width, height });
}
} }
}); });

View File

@@ -37,11 +37,15 @@ export const de = {
profileSaveAsNewProfile: "Als neuen Zeitplan speichern", profileSaveAsNewProfile: "Als neuen Zeitplan speichern",
profileSave: "Änderungen im aktuellen Zeitplan speichern", profileSave: "Änderungen im aktuellen Zeitplan speichern",
profileSaveAs: "Neuen Zeitplan mit aktueller Konfiguration erstellen", profileSaveAs: "Neuen Zeitplan mit aktueller Konfiguration erstellen",
profileRename: "Diesen Zeitplan umbenennen",
profileRenameHelp: "Geben Sie einen neuen Namen für den Zeitplan ein und drücken Sie Enter oder klicken Sie auf Speichern",
profileRenamePlaceholder: "Neuer Name für den Zeitplan...",
profileDelete: "Diesen Zeitplan löschen", profileDelete: "Diesen Zeitplan löschen",
profileDeleteDisabled: "Der letzte Zeitplan kann nicht gelöscht werden", profileDeleteDisabled: "Der letzte Zeitplan kann nicht gelöscht werden",
profileDeleteConfirm: "Möchten Sie den Zeitplan '{name}' wirklich löschen?", profileDeleteConfirm: "Möchten Sie den Zeitplan '{name}' wirklich löschen?",
profileSaveAsPlaceholder: "Name für den neuen Zeitplan...", profileSaveAsPlaceholder: "Name für den neuen Zeitplan...",
profileSaveAsHelp: "Geben Sie einen Namen für den neuen Zeitplan ein und drücken Sie Enter oder klicken Sie auf Speichern", profileSaveAsHelp: "Geben Sie einen Namen für den neuen Zeitplan ein und drücken Sie Enter oder klicken Sie auf Speichern",
profileNameAlreadyExists: "Ein Zeitplan mit diesem Namen existiert bereits",
profileSwitchUnsavedConfirm: "Sie haben ungespeicherte Änderungen. Beim Wechseln des Zeitplans gehen diese verloren. Fortfahren?", profileSwitchUnsavedConfirm: "Sie haben ungespeicherte Änderungen. Beim Wechseln des Zeitplans gehen diese verloren. Fortfahren?",
profiles: "Zeitpläne", profiles: "Zeitpläne",
cancel: "Abbrechen", cancel: "Abbrechen",
@@ -114,7 +118,7 @@ export const de = {
xAxisFormat24hDesc: "Wiederholender 0-24h Zyklus", xAxisFormat24hDesc: "Wiederholender 0-24h Zyklus",
xAxisFormat12h: "Tageszeit (12h AM/PM)", xAxisFormat12h: "Tageszeit (12h AM/PM)",
xAxisFormat12hDesc: "Wiederholend 12h Zyklus im AM/PM Format", xAxisFormat12hDesc: "Wiederholend 12h Zyklus im AM/PM Format",
showTemplateDayInChart: "Basis-Zeitplan kontinuierlich anzeigen", showTemplateDayInChart: "Basis-Zeitplan zum Vergleich anzeigen",
showTemplateDayTooltip: "Führt die Simulation des Basis-Zeitplans auch dann fort, auch wenn für Tag 2+ abweichende Zeitpläne definiert sind. Die entsprechenden Plasmakonzentrationen werden, nur im Falle einer Abweichung vom Basis-Zeitplan, als zusätzliche gestrichelte Linien dargestellt.\\n\\n__Standard:__ **aktiviert**", showTemplateDayTooltip: "Führt die Simulation des Basis-Zeitplans auch dann fort, auch wenn für Tag 2+ abweichende Zeitpläne definiert sind. Die entsprechenden Plasmakonzentrationen werden, nur im Falle einer Abweichung vom Basis-Zeitplan, als zusätzliche gestrichelte Linien dargestellt.\\n\\n__Standard:__ **aktiviert**",
simulationSettings: "Simulations-Einstellungen", simulationSettings: "Simulations-Einstellungen",

View File

@@ -37,11 +37,15 @@ export const en = {
profileSaveAsNewProfile: "Save as new schedule", profileSaveAsNewProfile: "Save as new schedule",
profileSave: "Save changes to current schedule", profileSave: "Save changes to current schedule",
profileSaveAs: "Create new schedule with current configuration", profileSaveAs: "Create new schedule with current configuration",
profileRename: "Rename this schedule",
profileRenameHelp: "Enter a new name for the schedule and press Enter or click Save",
profileRenamePlaceholder: "New name for the schedule...",
profileDelete: "Delete this schedule", profileDelete: "Delete this schedule",
profileDeleteDisabled: "Cannot delete the last schedule", profileDeleteDisabled: "Cannot delete the last schedule",
profileDeleteConfirm: "Are you sure you want to delete the schedule '{name}'?", profileDeleteConfirm: "Are you sure you want to delete the schedule '{name}'?",
profileSaveAsPlaceholder: "Name for the new schedule...", profileSaveAsPlaceholder: "Name for the new schedule...",
profileSaveAsHelp: "Enter a name for the new schedule and press Enter or click Save", profileSaveAsHelp: "Enter a name for the new schedule and press Enter or click Save",
profileNameAlreadyExists: "A schedule with this name already exists",
profileSwitchUnsavedConfirm: "You have unsaved changes. Switching schedules will discard them. Continue?", profileSwitchUnsavedConfirm: "You have unsaved changes. Switching schedules will discard them. Continue?",
profiles: "schedules", profiles: "schedules",
cancel: "Cancel", cancel: "Cancel",
@@ -113,7 +117,7 @@ export const en = {
xAxisFormat24hDesc: "Repeating 0-24h cycle", xAxisFormat24hDesc: "Repeating 0-24h cycle",
xAxisFormat12h: "Time of Day (12h AM/PM)", xAxisFormat12h: "Time of Day (12h AM/PM)",
xAxisFormat12hDesc: "Repeating 12h cycle in AM/PM format", xAxisFormat12hDesc: "Repeating 12h cycle in AM/PM format",
showTemplateDayInChart: "Continuously Show Baseline Schedule", showTemplateDayInChart: "Show Baseline Schedule for Comparison",
showTemplateDayTooltip: "Continue simulating the baseline schedule even when deviations are defined for day 2+. Corresponding plasma concentrations will be shown as additional dashed lines, only if deviating from the baseline schedule.\\n\\n__Default:__ **enabled**", showTemplateDayTooltip: "Continue simulating the baseline schedule even when deviations are defined for day 2+. Corresponding plasma concentrations will be shown as additional dashed lines, only if deviating from the baseline schedule.\\n\\n__Default:__ **enabled**",
simulationSettings: "Simulation Settings", simulationSettings: "Simulation Settings",
showDayReferenceLines: "Show Day Separators", showDayReferenceLines: "Show Day Separators",