Add localization support and docs
This commit is contained in:
@@ -8,11 +8,12 @@ const DeviationList = ({
|
||||
simulationDays,
|
||||
onAddDeviation,
|
||||
onRemoveDeviation,
|
||||
onDeviationChange
|
||||
onDeviationChange,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-amber-50 p-5 rounded-lg shadow-sm border border-amber-200">
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-700">Abweichungen vom Plan</h2>
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-700">{t.deviationsFromPlan}</h2>
|
||||
{deviations.map((dev, index) => (
|
||||
<div key={index} className="flex items-center space-x-2 mb-2 p-2 bg-white rounded flex-wrap">
|
||||
<select
|
||||
@@ -21,7 +22,7 @@ const DeviationList = ({
|
||||
className="p-2 border rounded-md text-sm"
|
||||
>
|
||||
{[...Array(parseInt(simulationDays, 10) || 1).keys()].map(day => (
|
||||
<option key={day} value={day}>Tag {day + 1}</option>
|
||||
<option key={day} value={day}>{t.day} {day + 1}</option>
|
||||
))}
|
||||
</select>
|
||||
<TimeInput
|
||||
@@ -34,7 +35,7 @@ const DeviationList = ({
|
||||
onChange={newDose => onDeviationChange(index, 'dose', newDose)}
|
||||
increment={doseIncrement}
|
||||
min={0}
|
||||
unit="mg"
|
||||
unit={t.mg}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@@ -43,7 +44,7 @@ const DeviationList = ({
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div className="flex items-center mt-1" title="Mark this if it was an extra dose instead of a replacement for a planned one.">
|
||||
<div className="flex items-center mt-1" title={t.additionalTooltip}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`add_dose_${index}`}
|
||||
@@ -52,7 +53,7 @@ const DeviationList = ({
|
||||
className="h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-500"
|
||||
/>
|
||||
<label htmlFor={`add_dose_${index}`} className="ml-2 text-xs text-gray-600">
|
||||
Zusätzlich
|
||||
{t.additional}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,10 +62,8 @@ const DeviationList = ({
|
||||
onClick={onAddDeviation}
|
||||
className="mt-2 w-full bg-amber-500 text-white py-2 rounded-md hover:bg-amber-600 text-sm"
|
||||
>
|
||||
Abweichung hinzufügen
|
||||
{t.addDeviation}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviationList;
|
||||
};export default DeviationList;
|
||||
|
||||
@@ -2,10 +2,10 @@ import React from 'react';
|
||||
import TimeInput from './TimeInput.js';
|
||||
import NumericInput from './NumericInput.js';
|
||||
|
||||
const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses }) => {
|
||||
const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses, t }) => {
|
||||
return (
|
||||
<div className="bg-white p-5 rounded-lg shadow-sm border">
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-700">Mein Plan</h2>
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-700">{t.myPlan}</h2>
|
||||
{doses.map((dose, index) => (
|
||||
<div key={index} className="flex items-center space-x-3 mb-3">
|
||||
<TimeInput
|
||||
@@ -18,10 +18,10 @@ const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses }) => {
|
||||
onChange={newDose => onUpdateDoses(doses.map((d, i) => i === index ? {...d, dose: newDose} : d))}
|
||||
increment={doseIncrement}
|
||||
min={0}
|
||||
unit="mg"
|
||||
unit={t.mg}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-gray-600 text-sm flex-1">{dose.label}</span>
|
||||
<span className="text-gray-600 text-sm flex-1">{t[dose.label] || dose.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
19
src/components/LanguageSelector.js
Normal file
19
src/components/LanguageSelector.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
const LanguageSelector = ({ currentLanguage, onLanguageChange, t }) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<label className="text-sm font-medium text-gray-600">{t.language}:</label>
|
||||
<select
|
||||
value={currentLanguage}
|
||||
onChange={(e) => onLanguageChange(e.target.value)}
|
||||
className="p-1 border rounded text-sm bg-white"
|
||||
>
|
||||
<option value="en">{t.english}</option>
|
||||
<option value="de">{t.german}</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSelector;
|
||||
@@ -8,13 +8,14 @@ const Settings = ({
|
||||
onUpdatePkParams,
|
||||
onUpdateTherapeuticRange,
|
||||
onUpdateUiSetting,
|
||||
onReset
|
||||
onReset,
|
||||
t
|
||||
}) => {
|
||||
const { showDayTimeXAxis, yAxisMin, yAxisMax, simulationDays, displayedDays } = uiSettings;
|
||||
|
||||
return (
|
||||
<div className="bg-white p-5 rounded-lg shadow-sm border">
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-700">Erweiterte Einstellungen</h2>
|
||||
<h2 className="text-xl font-semibold mb-4 text-gray-700">{t.advancedSettings}</h2>
|
||||
<div className="space-y-4 text-sm">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
@@ -25,11 +26,11 @@ const Settings = ({
|
||||
className="h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-500"
|
||||
/>
|
||||
<label htmlFor="showDayTimeXAxis" className="ml-3 block font-medium text-gray-600">
|
||||
24h-Zeitachse anzeigen
|
||||
{t.show24hTimeAxis}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium text-gray-600 pt-2">Simulationsdauer</label>
|
||||
<label className="block font-medium text-gray-600 pt-2">{t.simulationDuration}</label>
|
||||
<div className="w-40">
|
||||
<NumericInput
|
||||
value={simulationDays}
|
||||
@@ -37,11 +38,11 @@ const Settings = ({
|
||||
increment={'1'}
|
||||
min={2}
|
||||
max={7}
|
||||
unit="Tage"
|
||||
unit={t.days}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium text-gray-600">Angezeigte Tage</label>
|
||||
<label className="block font-medium text-gray-600">{t.displayedDays}</label>
|
||||
<div className="w-40">
|
||||
<NumericInput
|
||||
value={displayedDays}
|
||||
@@ -49,11 +50,11 @@ const Settings = ({
|
||||
increment={'1'}
|
||||
min={1}
|
||||
max={parseInt(simulationDays, 10) || 1}
|
||||
unit="Tage"
|
||||
unit={t.days}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium text-gray-600 pt-2">Y-Achsen-Bereich</label>
|
||||
<label className="block font-medium text-gray-600 pt-2">{t.yAxisRange}</label>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<div className="w-32">
|
||||
<NumericInput
|
||||
@@ -61,7 +62,7 @@ const Settings = ({
|
||||
onChange={val => onUpdateUiSetting('yAxisMin', val)}
|
||||
increment={'5'}
|
||||
min={0}
|
||||
placeholder="Auto"
|
||||
placeholder={t.auto}
|
||||
unit="ng/ml"
|
||||
/>
|
||||
</div>
|
||||
@@ -72,13 +73,13 @@ const Settings = ({
|
||||
onChange={val => onUpdateUiSetting('yAxisMax', val)}
|
||||
increment={'5'}
|
||||
min={0}
|
||||
placeholder="Auto"
|
||||
placeholder={t.auto}
|
||||
unit="ng/ml"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium text-gray-600">Therapeutischer Bereich</label>
|
||||
<label className="block font-medium text-gray-600">{t.therapeuticRange}</label>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<div className="w-32">
|
||||
<NumericInput
|
||||
@@ -86,7 +87,7 @@ const Settings = ({
|
||||
onChange={val => onUpdateTherapeuticRange('min', val)}
|
||||
increment={'0.5'}
|
||||
min={0}
|
||||
placeholder="Min"
|
||||
placeholder={t.min}
|
||||
unit="ng/ml"
|
||||
/>
|
||||
</div>
|
||||
@@ -97,15 +98,15 @@ const Settings = ({
|
||||
onChange={val => onUpdateTherapeuticRange('max', val)}
|
||||
increment={'0.5'}
|
||||
min={0}
|
||||
placeholder="Max"
|
||||
placeholder={t.max}
|
||||
unit="ng/ml"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">d-Amphetamin Parameter</h3>
|
||||
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">{t.dAmphetamineParameters}</h3>
|
||||
<div className="w-40">
|
||||
<label className="block font-medium text-gray-600">Halbwertszeit</label>
|
||||
<label className="block font-medium text-gray-600">{t.halfLife}</label>
|
||||
<NumericInput
|
||||
value={pkParams.damph.halfLife}
|
||||
onChange={val => onUpdatePkParams('damph', { halfLife: val })}
|
||||
@@ -115,9 +116,9 @@ const Settings = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">Lisdexamfetamin Parameter</h3>
|
||||
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">{t.lisdexamfetamineParameters}</h3>
|
||||
<div className="w-40">
|
||||
<label className="block font-medium text-gray-600">Umwandlungs-Halbwertszeit</label>
|
||||
<label className="block font-medium text-gray-600">{t.conversionHalfLife}</label>
|
||||
<NumericInput
|
||||
value={pkParams.ldx.halfLife}
|
||||
onChange={val => onUpdatePkParams('ldx', { halfLife: val })}
|
||||
@@ -127,13 +128,13 @@ const Settings = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="w-40">
|
||||
<label className="block font-medium text-gray-600">Absorptionsrate</label>
|
||||
<label className="block font-medium text-gray-600">{t.absorptionRate}</label>
|
||||
<NumericInput
|
||||
value={pkParams.ldx.absorptionRate}
|
||||
onChange={val => onUpdatePkParams('ldx', { absorptionRate: val })}
|
||||
increment={'0.1'}
|
||||
min={0.1}
|
||||
unit="(schneller >)"
|
||||
unit={t.faster}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -142,7 +143,7 @@ const Settings = ({
|
||||
onClick={onReset}
|
||||
className="w-full bg-red-600 text-white py-2 rounded-md hover:bg-red-700 text-sm"
|
||||
>
|
||||
Alle Einstellungen zurücksetzen
|
||||
{t.resetAllSettings}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,8 @@ const SimulationChart = ({
|
||||
simulationDays,
|
||||
displayedDays,
|
||||
yAxisMin,
|
||||
yAxisMax
|
||||
yAxisMax,
|
||||
t
|
||||
}) => {
|
||||
const totalHours = (parseInt(simulationDays, 10) || 3) * 24;
|
||||
const chartTicks = Array.from({length: Math.floor(totalHours / 6) + 1}, (_, i) => i * 6);
|
||||
@@ -36,7 +37,7 @@ const SimulationChart = ({
|
||||
type="number"
|
||||
domain={[0, totalHours]}
|
||||
ticks={chartTicks}
|
||||
tickFormatter={(h) => `${h}h`}
|
||||
tickFormatter={(h) => `${h}${t.hour}`}
|
||||
xAxisId="continuous"
|
||||
/>
|
||||
{showDayTimeXAxis && (
|
||||
@@ -45,26 +46,26 @@ const SimulationChart = ({
|
||||
type="number"
|
||||
domain={[0, totalHours]}
|
||||
ticks={chartTicks}
|
||||
tickFormatter={(h) => `${h % 24}h`}
|
||||
tickFormatter={(h) => `${h % 24}${t.hour}`}
|
||||
xAxisId="daytime"
|
||||
orientation="top"
|
||||
/>
|
||||
)}
|
||||
<YAxis
|
||||
label={{ value: 'Konzentration (ng/ml)', angle: -90, position: 'insideLeft', offset: -10 }}
|
||||
label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
|
||||
domain={chartDomain}
|
||||
allowDecimals={false}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value, name) => [`${value.toFixed(1)} ng/ml`, name]}
|
||||
labelFormatter={(label) => `Stunde: ${label}h`}
|
||||
formatter={(value, name) => [`${value.toFixed(1)} ${t.ngml}`, name]}
|
||||
labelFormatter={(label) => `${t.hour.replace('h', 'Hour')}: ${label}${t.hour}`}
|
||||
/>
|
||||
<Legend verticalAlign="top" height={36} />
|
||||
|
||||
{(chartView === 'damph' || chartView === 'both') && (
|
||||
<ReferenceLine
|
||||
y={parseFloat(therapeuticRange.min) || 0}
|
||||
label={{ value: 'Min', position: 'insideTopLeft' }}
|
||||
label={{ value: t.min, position: 'insideTopLeft' }}
|
||||
stroke="green"
|
||||
strokeDasharray="3 3"
|
||||
xAxisId="continuous"
|
||||
@@ -73,7 +74,7 @@ const SimulationChart = ({
|
||||
{(chartView === 'damph' || chartView === 'both') && (
|
||||
<ReferenceLine
|
||||
y={parseFloat(therapeuticRange.max) || 0}
|
||||
label={{ value: 'Max', position: 'insideTopLeft' }}
|
||||
label={{ value: t.max, position: 'insideTopLeft' }}
|
||||
stroke="red"
|
||||
strokeDasharray="3 3"
|
||||
xAxisId="continuous"
|
||||
@@ -97,7 +98,7 @@ const SimulationChart = ({
|
||||
type="monotone"
|
||||
data={idealProfile}
|
||||
dataKey="damph"
|
||||
name="d-Amphetamin (Ideal)"
|
||||
name={`${t.dAmphetamine} (Ideal)`}
|
||||
stroke="#3b82f6"
|
||||
strokeWidth={2.5}
|
||||
dot={false}
|
||||
@@ -109,7 +110,7 @@ const SimulationChart = ({
|
||||
type="monotone"
|
||||
data={idealProfile}
|
||||
dataKey="ldx"
|
||||
name="Lisdexamfetamin (Ideal)"
|
||||
name={`${t.lisdexamfetamine} (Ideal)`}
|
||||
stroke="#8b5cf6"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
@@ -123,7 +124,7 @@ const SimulationChart = ({
|
||||
type="monotone"
|
||||
data={deviatedProfile}
|
||||
dataKey="damph"
|
||||
name="d-Amphetamin (Abweichung)"
|
||||
name={`${t.dAmphetamine} (Deviation)`}
|
||||
stroke="#f59e0b"
|
||||
strokeWidth={2}
|
||||
strokeDasharray="5 5"
|
||||
@@ -136,7 +137,7 @@ const SimulationChart = ({
|
||||
type="monotone"
|
||||
data={deviatedProfile}
|
||||
dataKey="ldx"
|
||||
name="Lisdexamfetamin (Abweichung)"
|
||||
name={`${t.lisdexamfetamine} (Deviation)`}
|
||||
stroke="#f97316"
|
||||
strokeWidth={1.5}
|
||||
strokeDasharray="5 5"
|
||||
@@ -150,7 +151,7 @@ const SimulationChart = ({
|
||||
type="monotone"
|
||||
data={correctedProfile}
|
||||
dataKey="damph"
|
||||
name="d-Amphetamin (Korrektur)"
|
||||
name={`${t.dAmphetamine} (Correction)`}
|
||||
stroke="#10b981"
|
||||
strokeWidth={2.5}
|
||||
strokeDasharray="3 7"
|
||||
@@ -163,7 +164,7 @@ const SimulationChart = ({
|
||||
type="monotone"
|
||||
data={correctedProfile}
|
||||
dataKey="ldx"
|
||||
name="Lisdexamfetamin (Korrektur)"
|
||||
name={`${t.lisdexamfetamine} (Correction)`}
|
||||
stroke="#059669"
|
||||
strokeWidth={2}
|
||||
strokeDasharray="3 7"
|
||||
@@ -176,6 +177,4 @@ const SimulationChart = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimulationChart;
|
||||
};export default SimulationChart;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
const SuggestionPanel = ({ suggestion, onApplySuggestion }) => {
|
||||
const SuggestionPanel = ({ suggestion, onApplySuggestion, t }) => {
|
||||
if (!suggestion) return null;
|
||||
|
||||
return (
|
||||
<div className="bg-sky-100 border-l-4 border-sky-500 p-4 rounded-r-lg shadow-md">
|
||||
<h3 className="font-bold text-lg mb-2">Was wäre wenn?</h3>
|
||||
<h3 className="font-bold text-lg mb-2">{t.whatIf}</h3>
|
||||
{suggestion.dose ? (
|
||||
<>
|
||||
<p className="text-sm text-sky-800 mb-3">
|
||||
Vorschlag: <span className="font-bold">{suggestion.dose}mg</span> (statt {suggestion.originalDose}mg) um <span className="font-bold">{suggestion.time}</span>.
|
||||
{t.suggestion}: <span className="font-bold">{suggestion.dose}{t.mg}</span> ({t.instead} {suggestion.originalDose}{t.mg}) {t.at} <span className="font-bold">{suggestion.time}</span>.
|
||||
</p>
|
||||
<button
|
||||
onClick={onApplySuggestion}
|
||||
className="w-full bg-sky-600 text-white py-2 rounded-md hover:bg-sky-700 text-sm"
|
||||
>
|
||||
Vorschlag als Abweichung übernehmen
|
||||
{t.applySuggestion}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
@@ -23,6 +23,4 @@ const SuggestionPanel = ({ suggestion, onApplySuggestion }) => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SuggestionPanel;
|
||||
};export default SuggestionPanel;
|
||||
|
||||
@@ -67,7 +67,7 @@ const TimeInput = ({ value, onChange }) => {
|
||||
<div className="absolute top-full mt-2 z-10 bg-white p-4 rounded-lg shadow-xl border w-64">
|
||||
<div className="text-center text-lg font-bold mb-3">{value}</div>
|
||||
<div>
|
||||
<div className="mb-2"><span className="font-semibold">Stunde:</span></div>
|
||||
<div className="mb-2"><span className="font-semibold">Hour:</span></div>
|
||||
<div className="grid grid-cols-6 gap-1">
|
||||
{[...Array(24).keys()].map(h => (
|
||||
<button
|
||||
@@ -98,7 +98,7 @@ const TimeInput = ({ value, onChange }) => {
|
||||
onClick={() => setIsPickerOpen(false)}
|
||||
className="mt-4 w-full bg-gray-600 text-white py-1 rounded-md text-sm"
|
||||
>
|
||||
Schließen
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user