Update data deletion now in data manager with customization, minor UI improvements, increased chart y-axis tick count (regression)
This commit is contained in:
28
src/App.tsx
28
src/App.tsx
@@ -24,6 +24,8 @@ import { Button } from './components/ui/button';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from './components/ui/tooltip';
|
||||
import { IconButtonWithTooltip } from './components/ui/icon-button-with-tooltip';
|
||||
import { PROJECT_REPOSITORY_URL, APP_VERSION } from './constants/defaults';
|
||||
import { deleteSelectedData } from './utils/exportImport';
|
||||
import type { ExportOptions } from './utils/exportImport';
|
||||
|
||||
// Custom Hooks
|
||||
import { useAppState } from './hooks/useAppState';
|
||||
@@ -74,7 +76,6 @@ const MedPlanAssistant = () => {
|
||||
updateState,
|
||||
updateNestedState,
|
||||
updateUiSetting,
|
||||
handleReset,
|
||||
addDay,
|
||||
removeDay,
|
||||
addDoseToDay,
|
||||
@@ -135,6 +136,29 @@ const MedPlanAssistant = () => {
|
||||
templateProfile
|
||||
} = useSimulation(appState);
|
||||
|
||||
// Handle data deletion
|
||||
const handleDeleteData = (options: ExportOptions) => {
|
||||
const newState = deleteSelectedData(appState, options);
|
||||
|
||||
// Apply all state updates
|
||||
Object.entries(newState).forEach(([key, value]) => {
|
||||
if (key === 'days') {
|
||||
updateState('days', value as any);
|
||||
} else if (key === 'pkParams') {
|
||||
updateState('pkParams', value as any);
|
||||
} else if (key === 'therapeuticRange') {
|
||||
updateState('therapeuticRange', value as any);
|
||||
} else if (key === 'doseIncrement') {
|
||||
updateState('doseIncrement', value as any);
|
||||
} else if (key === 'uiSettings') {
|
||||
// Update UI settings individually
|
||||
Object.entries(value as any).forEach(([uiKey, uiValue]) => {
|
||||
updateUiSetting(uiKey as any, uiValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="min-h-screen bg-background p-4 sm:p-6 lg:p-8">
|
||||
@@ -163,6 +187,7 @@ const MedPlanAssistant = () => {
|
||||
onUpdateTherapeuticRange={(key: any, value: any) => updateNestedState('therapeuticRange', key, value)}
|
||||
onUpdateUiSetting={(key: any, value: any) => updateUiSetting(key as any, value)}
|
||||
onImportDays={(importedDays: any) => updateState('days', importedDays)}
|
||||
onDeleteData={handleDeleteData}
|
||||
/>
|
||||
|
||||
<div className="max-w-7xl mx-auto" style={{
|
||||
@@ -287,7 +312,6 @@ const MedPlanAssistant = () => {
|
||||
onUpdatePkParams={(key: any, value: any) => updateNestedState('pkParams', key, value)}
|
||||
onUpdateTherapeuticRange={(key: any, value: any) => updateNestedState('therapeuticRange', key, value)}
|
||||
onUpdateUiSetting={updateUiSetting}
|
||||
onReset={handleReset}
|
||||
onImportDays={(importedDays: any) => updateState('days', importedDays)}
|
||||
onOpenDataManagement={() => setShowDataManagement(true)}
|
||||
t={t}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Switch } from './ui/switch';
|
||||
import { Separator } from './ui/separator';
|
||||
import { Textarea } from './ui/textarea';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -52,6 +53,7 @@ interface ExportImportOptions {
|
||||
includeSimulationSettings: boolean;
|
||||
includePharmacoSettings: boolean;
|
||||
includeAdvancedSettings: boolean;
|
||||
includeOtherData: boolean;
|
||||
}
|
||||
|
||||
interface DataManagementModalProps {
|
||||
@@ -69,6 +71,7 @@ interface DataManagementModalProps {
|
||||
onUpdateTherapeuticRange: (key: string, value: any) => void;
|
||||
onUpdateUiSetting: (key: string, value: any) => void;
|
||||
onImportDays?: (days: any) => void;
|
||||
onDeleteData?: (options: ExportImportOptions) => void;
|
||||
}
|
||||
|
||||
const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
@@ -84,6 +87,7 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
onUpdateTherapeuticRange,
|
||||
onUpdateUiSetting,
|
||||
onImportDays,
|
||||
onDeleteData,
|
||||
}) => {
|
||||
// Export/Import options
|
||||
const [exportOptions, setExportOptions] = useState<ExportImportOptions>({
|
||||
@@ -92,6 +96,7 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
includeSimulationSettings: true,
|
||||
includePharmacoSettings: true,
|
||||
includeAdvancedSettings: true,
|
||||
includeOtherData: false,
|
||||
});
|
||||
|
||||
const [importOptions, setImportOptions] = useState<ExportImportOptions>({
|
||||
@@ -100,6 +105,17 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
includeSimulationSettings: true,
|
||||
includePharmacoSettings: true,
|
||||
includeAdvancedSettings: true,
|
||||
includeOtherData: false,
|
||||
});
|
||||
|
||||
// Deletion options - defaults: all except otherData
|
||||
const [deletionOptions, setDeletionOptions] = useState<ExportImportOptions>({
|
||||
includeSchedules: true,
|
||||
includeDiagramSettings: true,
|
||||
includeSimulationSettings: true,
|
||||
includePharmacoSettings: true,
|
||||
includeAdvancedSettings: true,
|
||||
includeOtherData: true,
|
||||
});
|
||||
|
||||
// File upload state
|
||||
@@ -445,6 +461,40 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// Handle delete selected data
|
||||
const handleDeleteData = () => {
|
||||
const hasAnySelected = Object.values(deletionOptions).some(v => v);
|
||||
if (!hasAnySelected) {
|
||||
alert(t('deleteNoOptionsSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build confirmation message listing what will be deleted
|
||||
const categoriesToDelete = [];
|
||||
if (deletionOptions.includeSchedules) categoriesToDelete.push(t('exportOptionSchedules'));
|
||||
if (deletionOptions.includeDiagramSettings) categoriesToDelete.push(t('exportOptionDiagram'));
|
||||
if (deletionOptions.includeSimulationSettings) categoriesToDelete.push(t('exportOptionSimulation'));
|
||||
if (deletionOptions.includePharmacoSettings) categoriesToDelete.push(t('exportOptionPharmaco'));
|
||||
if (deletionOptions.includeAdvancedSettings) categoriesToDelete.push(t('exportOptionAdvanced'));
|
||||
if (deletionOptions.includeOtherData) categoriesToDelete.push(t('exportOptionOtherData'));
|
||||
|
||||
const confirmMessage =
|
||||
t('deleteDataConfirmTitle') + '\n\n' +
|
||||
categoriesToDelete.map(cat => `• ${cat}`).join('\n') + '\n\n' +
|
||||
t('deleteDataConfirmWarning');
|
||||
|
||||
if (!window.confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call deletion handler
|
||||
if (onDeleteData) {
|
||||
onDeleteData(deletionOptions);
|
||||
alert(t('deleteDataSuccess'));
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
||||
<div className="max-w-4xl w-full max-h-[90vh] overflow-y-auto bg-background rounded-lg shadow-xl">
|
||||
@@ -542,6 +592,31 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
{t('exportOptionAdvanced')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="export-other"
|
||||
checked={exportOptions.includeOtherData}
|
||||
onCheckedChange={checked =>
|
||||
setExportOptions({ ...exportOptions, includeOtherData: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="export-other" className="text-sm">
|
||||
{t('exportOptionOtherData')}
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs max-w-xs">{t('exportOptionOtherDataTooltip')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -647,6 +722,31 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
{t('exportOptionAdvanced')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="import-other"
|
||||
checked={importOptions.includeOtherData}
|
||||
onCheckedChange={checked =>
|
||||
setImportOptions({ ...importOptions, includeOtherData: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="import-other" className="text-sm">
|
||||
{t('exportOptionOtherData')}
|
||||
</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs max-w-xs">{t('exportOptionOtherDataTooltip')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -791,22 +891,138 @@ const DataManagementModal: React.FC<DataManagementModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
{/* Apply Import Button - directly below JSON editor without separator */}
|
||||
<Button
|
||||
onClick={handleImport}
|
||||
className="flex-1"
|
||||
className="w-full"
|
||||
size="lg"
|
||||
disabled={!jsonEditorContent && !selectedFile}
|
||||
>
|
||||
{t('importApplyButton')}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="outline" className="flex-1" size="lg">
|
||||
{t('closeDataManagement')}
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Delete Specific Data Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex i-tems-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Trash2 className="h-5 w-5 text-destructive" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-destructive">{t('deleteSpecificData')}</h3>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs max-w-xs">{t('deleteSpecificDataTooltip')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* Warning Message */}
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-md p-3 text-sm">
|
||||
<p className="text-yellow-800 dark:text-yellow-200">{t('deleteDataWarning')}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">{t('deleteSelectWhat')}</Label>
|
||||
<div className="space-y-2 pl-4 [&_button[role=switch][data-state=checked]]:bg-destructive [&_button[role=switch][data-state=checked]]:border-destructive">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="delete-schedules"
|
||||
checked={deletionOptions.includeSchedules}
|
||||
onCheckedChange={checked =>
|
||||
setDeletionOptions({ ...deletionOptions, includeSchedules: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="delete-schedules" className="text-sm">
|
||||
{t('exportOptionSchedules')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="delete-diagram"
|
||||
checked={deletionOptions.includeDiagramSettings}
|
||||
onCheckedChange={checked =>
|
||||
setDeletionOptions({ ...deletionOptions, includeDiagramSettings: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="delete-diagram" className="text-sm">
|
||||
{t('exportOptionDiagram')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="delete-simulation"
|
||||
checked={deletionOptions.includeSimulationSettings}
|
||||
onCheckedChange={checked =>
|
||||
setDeletionOptions({ ...deletionOptions, includeSimulationSettings: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="delete-simulation" className="text-sm">
|
||||
{t('exportOptionSimulation')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="delete-pharmaco"
|
||||
checked={deletionOptions.includePharmacoSettings}
|
||||
onCheckedChange={checked =>
|
||||
setDeletionOptions({ ...deletionOptions, includePharmacoSettings: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="delete-pharmaco" className="text-sm">
|
||||
{t('exportOptionPharmaco')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="delete-advanced"
|
||||
checked={deletionOptions.includeAdvancedSettings}
|
||||
onCheckedChange={checked =>
|
||||
setDeletionOptions({ ...deletionOptions, includeAdvancedSettings: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="delete-advanced" className="text-sm">
|
||||
{t('exportOptionAdvanced')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
id="delete-other"
|
||||
checked={deletionOptions.includeOtherData}
|
||||
onCheckedChange={checked =>
|
||||
setDeletionOptions({ ...deletionOptions, includeOtherData: checked })
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="delete-other" className="text-sm">
|
||||
{t('exportOptionOtherData')}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete Button */}
|
||||
<Button
|
||||
onClick={handleDeleteData}
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
{t('deleteDataButton')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Close Button */}
|
||||
<Button onClick={onClose} variant="outline" className="w-full" size="lg">
|
||||
{t('closeDataManagement')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,6 @@ const Settings = ({
|
||||
onUpdatePkParams,
|
||||
onUpdateTherapeuticRange,
|
||||
onUpdateUiSetting,
|
||||
onReset,
|
||||
onImportDays,
|
||||
onOpenDataManagement,
|
||||
t
|
||||
@@ -458,6 +457,8 @@ const Settings = ({
|
||||
unit={t('unitDays')}
|
||||
required={true}
|
||||
errorMessage={t('errorNumberRequired')}
|
||||
showResetButton={true}
|
||||
defaultValue="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -490,7 +491,8 @@ const Settings = ({
|
||||
max={500}
|
||||
placeholder={t('auto')}
|
||||
allowEmpty={true}
|
||||
clearButton={true}
|
||||
showResetButton={true}
|
||||
defaultValue=""
|
||||
warning={!!yAxisRangeError}
|
||||
warningMessage={yAxisRangeError}
|
||||
/>
|
||||
@@ -504,7 +506,8 @@ const Settings = ({
|
||||
placeholder={t('auto')}
|
||||
unit="ng/ml"
|
||||
allowEmpty={true}
|
||||
clearButton={true}
|
||||
showResetButton={true}
|
||||
defaultValue=""
|
||||
warning={!!yAxisRangeError}
|
||||
warningMessage={yAxisRangeError}
|
||||
/>
|
||||
@@ -598,6 +601,8 @@ const Settings = ({
|
||||
unit={t('unitDays')}
|
||||
required={true}
|
||||
errorMessage={t('errorNumberRequired')}
|
||||
showResetButton={true}
|
||||
defaultValue="5"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -649,6 +654,8 @@ const Settings = ({
|
||||
max={7}
|
||||
unit={t('unitDays')}
|
||||
required={true}
|
||||
showResetButton={true}
|
||||
defaultValue="7"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -699,6 +706,8 @@ const Settings = ({
|
||||
error={eliminationExtreme}
|
||||
warningMessage={t('warningEliminationOutOfRange')}
|
||||
errorMessage={t('errorEliminationHalfLifeRequired')}
|
||||
showResetButton={true}
|
||||
defaultValue="11"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -736,6 +745,8 @@ const Settings = ({
|
||||
warning={conversionWarning}
|
||||
warningMessage={t('warningConversionOutOfRange')}
|
||||
errorMessage={t('errorConversionHalfLifeRequired')}
|
||||
showResetButton={true}
|
||||
defaultValue="0.8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1144,16 +1155,6 @@ const Settings = ({
|
||||
>
|
||||
{t('openDataManagement')}
|
||||
</Button>
|
||||
|
||||
{/* Reset Button - Always Visible */}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
>
|
||||
{t('resetAllSettings')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -467,8 +467,8 @@ const SimulationChart = ({
|
||||
// FIXME
|
||||
//label={{ value: t('axisLabelConcentration'), angle: -90, position: 'insideLeft', style: { fontStyle: 'italic', color: '#666' } }}
|
||||
domain={yAxisDomain as any}
|
||||
tickCount={20}
|
||||
interval={1}
|
||||
tickCount={16}
|
||||
interval={0}
|
||||
allowDecimals={false}
|
||||
allowDataOverflow={false}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import * as React from "react"
|
||||
import { Minus, Plus, X } from "lucide-react"
|
||||
import { Minus, Plus, RotateCcw } from "lucide-react"
|
||||
import { Button } from "./button"
|
||||
import { IconButtonWithTooltip } from "./icon-button-with-tooltip"
|
||||
import { Input } from "./input"
|
||||
@@ -25,7 +25,8 @@ interface NumericInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
|
||||
unit?: string
|
||||
align?: 'left' | 'center' | 'right'
|
||||
allowEmpty?: boolean
|
||||
clearButton?: boolean
|
||||
showResetButton?: boolean
|
||||
defaultValue?: number | string
|
||||
error?: boolean
|
||||
warning?: boolean
|
||||
required?: boolean
|
||||
@@ -44,7 +45,8 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
||||
unit,
|
||||
align = 'right',
|
||||
allowEmpty = false,
|
||||
clearButton = false,
|
||||
showResetButton = false,
|
||||
defaultValue,
|
||||
error = false,
|
||||
warning = false,
|
||||
required = false,
|
||||
@@ -217,7 +219,7 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
||||
size="icon"
|
||||
className={cn(
|
||||
"h-9 w-9",
|
||||
clearButton && allowEmpty ? "rounded-l-none rounded-r-none border-x-0" : "rounded-l-none border-l-0",
|
||||
showResetButton ? "rounded-l-none rounded-r-none border-x-0" : "rounded-l-none border-l-0",
|
||||
hasError && "border-destructive",
|
||||
hasWarning && !hasError && "border-yellow-500"
|
||||
)}
|
||||
@@ -226,11 +228,11 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
{clearButton && allowEmpty && (
|
||||
{showResetButton && (
|
||||
<IconButtonWithTooltip
|
||||
type="button"
|
||||
icon={<X className="h-4 w-4" />}
|
||||
tooltip={t('buttonClear')}
|
||||
icon={<RotateCcw className="h-4 w-4" />}
|
||||
tooltip={t('buttonResetToDefault')}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className={cn(
|
||||
@@ -238,7 +240,7 @@ const FormNumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
||||
hasError && "border-destructive",
|
||||
hasWarning && !hasError && "border-yellow-500"
|
||||
)}
|
||||
onClick={() => onChange('')}
|
||||
onClick={() => onChange(String(defaultValue ?? ''))}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -151,15 +151,14 @@ export const getDefaultState = (): AppState => ({
|
||||
id: 'day-template',
|
||||
isTemplate: true,
|
||||
doses: [
|
||||
{ id: 'dose-1', time: '06:30', ldx: '20' },
|
||||
{ id: 'dose-2', time: '12:30', ldx: '10' },
|
||||
{ id: 'dose-3', time: '17:30', ldx: '10' },
|
||||
{ id: 'dose-4', time: '22:00', ldx: '7.5' },
|
||||
{ id: 'dose-1', time: '06:30', ldx: '25' },
|
||||
{ id: 'dose-2', time: '14:30', ldx: '15' },
|
||||
{ id: 'dose-4', time: '22:15', ldx: '15' },
|
||||
]
|
||||
}
|
||||
],
|
||||
steadyStateConfig: { daysOnMedication: '7' }, // kept for backwards compatibility, now sourced from pkParams.advanced
|
||||
therapeuticRange: { min: '10', max: '120' }, // min for adults and max for children for maximum range (users should personalize based on their response)
|
||||
therapeuticRange: { min: '', max: '' }, // users should personalize based on their response
|
||||
doseIncrement: '2.5',
|
||||
uiSettings: {
|
||||
showDayTimeOnXAxis: '24h',
|
||||
|
||||
@@ -311,13 +311,6 @@ export const useAppState = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (window.confirm("Bist du sicher, dass du alle Einstellungen auf die Standardwerte zurücksetzen möchtest? Dies kann nicht rückgängig gemacht werden.")) {
|
||||
window.localStorage.removeItem(LOCAL_STORAGE_KEY);
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
appState,
|
||||
isLoaded,
|
||||
@@ -331,7 +324,6 @@ export const useAppState = () => {
|
||||
removeDoseFromDay,
|
||||
updateDoseInDay,
|
||||
updateDoseFieldInDay,
|
||||
sortDosesInDay,
|
||||
handleReset
|
||||
sortDosesInDay
|
||||
};
|
||||
};
|
||||
|
||||
@@ -210,6 +210,8 @@ export const de = {
|
||||
exportOptionSimulation: "Simulations-Einstellungen (Dauer, Bereich, Diagrammansicht)",
|
||||
exportOptionPharmaco: "Pharmakokinetik-Einstellungen (Halbwertszeiten, therapeutischer Bereich)",
|
||||
exportOptionAdvanced: "Erweiterte Einstellungen (Gewicht, Nahrung, pH, Bioverfügbarkeit)",
|
||||
exportOptionOtherData: "Andere Daten (Design, eingeklappte Karten, Sprache, Haftungsausschluss)",
|
||||
exportOptionOtherDataTooltip: "UI-Präferenzen wie Design, eingeklappte Kartenstatus, Spracheinstellung und Haftungsausschluss-Bestätigung. Normalerweise nicht nötig beim Teilen von Plänen mit anderen.",
|
||||
exportButton: "Backup-Datei herunterladen",
|
||||
importButton: "Datei zum Importieren wählen",
|
||||
importApplyButton: "Import anwenden",
|
||||
@@ -254,12 +256,24 @@ export const de = {
|
||||
closeDataManagement: "Schließen",
|
||||
pasteContentTooLarge: "Inhalt zu groß (max. 5000 Zeichen)",
|
||||
|
||||
// Delete Data
|
||||
deleteSpecificData: "Spezifische Daten löschen",
|
||||
deleteSpecificDataTooltip: "Ausgewählte Datenkategorien dauerhaft von Ihrem Gerät löschen. Dieser Vorgang kann nicht rückgängig gemacht werden.",
|
||||
deleteSelectWhat: "Was möchtest du löschen:",
|
||||
deleteDataWarning: "⚠️ Warnung: Das Löschen ist dauerhaft und kann nicht rückgängig gemacht werden. Gelöschte Daten werden auf Standardwerte zurückgesetzt.",
|
||||
deleteDataButton: "Ausgewählte Daten löschen",
|
||||
deleteNoOptionsSelected: "Bitte wähle mindestens eine Kategorie zum Löschen aus.",
|
||||
deleteDataConfirmTitle: "Bist du sicher, dass du die folgenden Daten dauerhaft löschen möchtest?",
|
||||
deleteDataConfirmWarning: "Diese Aktion kann nicht rückgängig gemacht werden. Gelöschte Daten werden auf Werkseinstellungen zurückgesetzt.",
|
||||
deleteDataSuccess: "Ausgewählte Daten wurden erfolgreich gelöscht.",
|
||||
|
||||
// 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.",
|
||||
|
||||
// Number input field
|
||||
buttonClear: "Feld löschen",
|
||||
buttonResetToDefault: "Auf Standard zurücksetzen",
|
||||
|
||||
// Field validation - Errors
|
||||
errorNumberRequired: "⛔ Bitte gib eine gültige Zahl ein.",
|
||||
|
||||
@@ -208,6 +208,8 @@ export const en = {
|
||||
exportOptionSimulation: "Simulation Settings (Duration, range, chart view)",
|
||||
exportOptionPharmaco: "Pharmacokinetic Settings (Half-lives, therapeutic range)",
|
||||
exportOptionAdvanced: "Advanced Settings (Weight, food, pH, bioavailability)",
|
||||
exportOptionOtherData: "Other Data (Theme, collapsed cards, language, disclaimer)",
|
||||
exportOptionOtherDataTooltip: "UI preferences like theme, collapsed card states, language preference, and disclaimer acceptance. Typically not needed when sharing plans with others.",
|
||||
exportButton: "Download Backup File",
|
||||
importButton: "Choose File to Import",
|
||||
importApplyButton: "Apply Import",
|
||||
@@ -252,12 +254,24 @@ export const en = {
|
||||
closeDataManagement: "Close",
|
||||
pasteContentTooLarge: "Content too large (max. 5000 characters)",
|
||||
|
||||
// Delete Data
|
||||
deleteSpecificData: "Delete Specific Data",
|
||||
deleteSpecificDataTooltip: "Permanently delete selected data categories from your device. This operation cannot be undone.",
|
||||
deleteSelectWhat: "Select what to delete:",
|
||||
deleteDataWarning: "⚠️ Warning: Deletion is permanent and cannot be undone. Deleted data will be reset to default values.",
|
||||
deleteDataButton: "Delete Selected Data",
|
||||
deleteNoOptionsSelected: "Please select at least one category to delete.",
|
||||
deleteDataConfirmTitle: "Are you sure you want to permanently delete the following data?",
|
||||
deleteDataConfirmWarning: "This action cannot be undone. Deleted data will be reset to factory defaults.",
|
||||
deleteDataSuccess: "Selected data has been deleted successfully.",
|
||||
|
||||
// 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.",
|
||||
|
||||
// Number input field
|
||||
buttonClear: "Clear field",
|
||||
buttonResetToDefault: "Reset to default",
|
||||
|
||||
// Field validation - Errors
|
||||
errorNumberRequired: "⛔ Please enter a valid number.",
|
||||
|
||||
@@ -37,6 +37,13 @@ export interface ExportData {
|
||||
doseIncrement: AppState['doseIncrement'];
|
||||
};
|
||||
advancedSettings?: AppState['pkParams']['advanced'];
|
||||
otherData?: {
|
||||
theme?: AppState['uiSettings']['theme'];
|
||||
settingsCardStates?: any;
|
||||
dayScheduleCollapsedStates?: any;
|
||||
language?: string;
|
||||
disclaimerAccepted?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,6 +53,7 @@ export interface ExportOptions {
|
||||
includeSimulationSettings: boolean;
|
||||
includePharmacoSettings: boolean;
|
||||
includeAdvancedSettings: boolean;
|
||||
includeOtherData: boolean;
|
||||
}
|
||||
|
||||
export interface ImportValidationResult {
|
||||
@@ -111,6 +119,21 @@ export const exportSettings = (
|
||||
exportData.data.advancedSettings = appState.pkParams.advanced;
|
||||
}
|
||||
|
||||
if (options.includeOtherData) {
|
||||
const settingsCardStates = localStorage.getItem('settingsCardStates_v1');
|
||||
const dayScheduleCollapsedStates = localStorage.getItem('dayScheduleCollapsedDays_v1');
|
||||
const language = localStorage.getItem('medPlanAssistant_language');
|
||||
const disclaimerAccepted = localStorage.getItem('medPlanDisclaimerAccepted_v1');
|
||||
|
||||
exportData.data.otherData = {
|
||||
theme: appState.uiSettings.theme,
|
||||
settingsCardStates: settingsCardStates ? JSON.parse(settingsCardStates) : undefined,
|
||||
dayScheduleCollapsedStates: dayScheduleCollapsedStates ? JSON.parse(dayScheduleCollapsedStates) : undefined,
|
||||
language: language || undefined,
|
||||
disclaimerAccepted: disclaimerAccepted === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
return exportData;
|
||||
};
|
||||
|
||||
@@ -228,6 +251,17 @@ export const validateImportData = (data: any): ImportValidationResult => {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate other data
|
||||
if (importData.otherData !== undefined) {
|
||||
const validFields = ['theme', 'settingsCardStates', 'dayScheduleCollapsedStates', 'language', 'disclaimerAccepted'];
|
||||
const importedFields = Object.keys(importData.otherData);
|
||||
const unknownFields = importedFields.filter(f => !validFields.includes(f));
|
||||
if (unknownFields.length > 0) {
|
||||
result.warnings.push(`Other data: Unknown fields found (${unknownFields.join(', ')})`);
|
||||
result.hasUnknownFields = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -295,6 +329,131 @@ export const importSettings = (
|
||||
};
|
||||
}
|
||||
|
||||
if (options.includeOtherData && importData.otherData) {
|
||||
// Update theme in uiSettings
|
||||
if (importData.otherData.theme !== undefined) {
|
||||
if (!newState.uiSettings) {
|
||||
newState.uiSettings = { ...currentState.uiSettings };
|
||||
}
|
||||
newState.uiSettings.theme = importData.otherData.theme;
|
||||
}
|
||||
|
||||
// Update localStorage-only settings
|
||||
if (importData.otherData.settingsCardStates !== undefined) {
|
||||
localStorage.setItem('settingsCardStates_v1', JSON.stringify(importData.otherData.settingsCardStates));
|
||||
}
|
||||
if (importData.otherData.dayScheduleCollapsedStates !== undefined) {
|
||||
localStorage.setItem('dayScheduleCollapsedDays_v1', JSON.stringify(importData.otherData.dayScheduleCollapsedStates));
|
||||
}
|
||||
if (importData.otherData.language !== undefined) {
|
||||
localStorage.setItem('medPlanAssistant_language', importData.otherData.language);
|
||||
}
|
||||
if (importData.otherData.disclaimerAccepted !== undefined) {
|
||||
localStorage.setItem('medPlanDisclaimerAccepted_v1', importData.otherData.disclaimerAccepted ? 'true' : 'false');
|
||||
}
|
||||
}
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete selected data categories from localStorage and return updated state
|
||||
* @param currentState Current application state
|
||||
* @param options Which categories to delete
|
||||
* @returns Partial state with defaults for deleted categories
|
||||
*/
|
||||
export const deleteSelectedData = (
|
||||
currentState: AppState,
|
||||
options: ExportOptions
|
||||
): Partial<AppState> => {
|
||||
const defaults = getDefaultState();
|
||||
const newState: Partial<AppState> = {};
|
||||
|
||||
// Track if main localStorage should be removed
|
||||
let shouldRemoveMainStorage = false;
|
||||
|
||||
if (options.includeSchedules) {
|
||||
// Delete schedules - but always keep template day with at least one dose
|
||||
// Never allow complete deletion as this breaks the app
|
||||
const defaults = getDefaultState();
|
||||
newState.days = [
|
||||
{
|
||||
id: 'day-template',
|
||||
isTemplate: true,
|
||||
doses: [
|
||||
{ id: 'dose-default', time: '06:00', ldx: '70' }
|
||||
]
|
||||
}
|
||||
];
|
||||
shouldRemoveMainStorage = true;
|
||||
}
|
||||
|
||||
if (options.includeDiagramSettings) {
|
||||
if (!newState.uiSettings) {
|
||||
newState.uiSettings = { ...currentState.uiSettings };
|
||||
}
|
||||
// Reset diagram settings to defaults
|
||||
newState.uiSettings.showDayTimeOnXAxis = defaults.uiSettings.showDayTimeOnXAxis;
|
||||
newState.uiSettings.showTemplateDay = defaults.uiSettings.showTemplateDay;
|
||||
newState.uiSettings.showDayReferenceLines = defaults.uiSettings.showDayReferenceLines;
|
||||
newState.uiSettings.showTherapeuticRange = defaults.uiSettings.showTherapeuticRange;
|
||||
newState.uiSettings.stickyChart = defaults.uiSettings.stickyChart;
|
||||
shouldRemoveMainStorage = true;
|
||||
}
|
||||
|
||||
if (options.includeSimulationSettings) {
|
||||
if (!newState.uiSettings) {
|
||||
newState.uiSettings = { ...currentState.uiSettings };
|
||||
}
|
||||
// Reset simulation settings to defaults
|
||||
newState.uiSettings.simulationDays = defaults.uiSettings.simulationDays;
|
||||
newState.uiSettings.displayedDays = defaults.uiSettings.displayedDays;
|
||||
newState.uiSettings.yAxisMin = defaults.uiSettings.yAxisMin;
|
||||
newState.uiSettings.yAxisMax = defaults.uiSettings.yAxisMax;
|
||||
newState.uiSettings.chartView = defaults.uiSettings.chartView;
|
||||
newState.uiSettings.steadyStateDaysEnabled = defaults.uiSettings.steadyStateDaysEnabled;
|
||||
shouldRemoveMainStorage = true;
|
||||
}
|
||||
|
||||
if (options.includePharmacoSettings) {
|
||||
// Reset pharmacokinetic settings to defaults
|
||||
newState.pkParams = {
|
||||
...currentState.pkParams,
|
||||
ldx: defaults.pkParams.ldx,
|
||||
damph: defaults.pkParams.damph,
|
||||
};
|
||||
newState.therapeuticRange = defaults.therapeuticRange;
|
||||
newState.doseIncrement = defaults.doseIncrement;
|
||||
shouldRemoveMainStorage = true;
|
||||
}
|
||||
|
||||
if (options.includeAdvancedSettings) {
|
||||
if (!newState.pkParams) {
|
||||
newState.pkParams = { ...currentState.pkParams };
|
||||
}
|
||||
// Reset advanced settings to defaults
|
||||
newState.pkParams.advanced = defaults.pkParams.advanced;
|
||||
shouldRemoveMainStorage = true;
|
||||
}
|
||||
|
||||
if (options.includeOtherData) {
|
||||
// Reset theme to default
|
||||
if (!newState.uiSettings) {
|
||||
newState.uiSettings = { ...currentState.uiSettings };
|
||||
}
|
||||
newState.uiSettings.theme = defaults.uiSettings.theme;
|
||||
|
||||
// Remove UI state from localStorage
|
||||
localStorage.removeItem('settingsCardStates_v1');
|
||||
localStorage.removeItem('dayScheduleCollapsedDays_v1');
|
||||
localStorage.removeItem('medPlanAssistant_language');
|
||||
localStorage.removeItem('medPlanDisclaimerAccepted_v1');
|
||||
shouldRemoveMainStorage = true;
|
||||
}
|
||||
|
||||
// If any main state category was deleted, we'll trigger a save by returning the partial state
|
||||
// The useAppState hook will handle saving to localStorage
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user