Unify category autocomplete fields
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s

This commit is contained in:
Shinuwa 2026-09-04 14:09:08 +02:00
parent e3d823d0b1
commit 055fb78d60
21 changed files with 739 additions and 168 deletions

View file

@ -104,12 +104,24 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
const [occurrenceDrafts, setOccurrenceDrafts] = useState({ perHour: null, perDay: null, perMonth: null });
const rateDraft = useDraftForm({ label: "", rate: RNG_SIMULATOR_DEFAULT_RATE });
const activeRate = data.rates.find((rate) => rate.id === data.activeRateId) || data.rates[0] || null;
const summary = useMemo(() => activeRate ? calculateRngSummary({ rate: activeRate.rate, ...data.settings }) : null, [activeRate, data.settings]);
const summary = useMemo(() => activeRate ? calculateRngSummary({
rate: activeRate.rate,
multiplier: activeRate.multiplier,
simulationDraws: data.settings.simulationDraws,
perHour: activeRate.perHour,
perDay: activeRate.perDay,
perMonth: activeRate.perMonth
}) : null, [activeRate, data.settings.simulationDraws]);
useEffect(() => {
setHistory(readSimulationHistory(historyStorageKey));
}, [historyStorageKey]);
useEffect(() => {
setMultiplierDraft(null);
setOccurrenceDrafts({ perHour: null, perDay: null, perMonth: null });
}, [activeRate?.id]);
function save(nextData) {
context.setModuleData(toolboxId, moduleId, nextData, "rngSimulator");
}
@ -118,21 +130,28 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
const settings = { ...data.settings };
if (field === "simulationDraws") {
settings[field] = clampInteger(value, RNG_SIMULATOR_LIMITS.simulationDrawsMin, RNG_SIMULATOR_LIMITS.simulationDrawsMax, data.settings.simulationDraws);
} else if (field === "multiplier") {
settings[field] = clampDecimal(value, RNG_SIMULATOR_LIMITS.multiplierMin, RNG_SIMULATOR_LIMITS.multiplierMax, 0);
} else {
settings[field] = clampDecimal(value, RNG_SIMULATOR_LIMITS.occurrenceRateMin, RNG_SIMULATOR_LIMITS.occurrenceRateMax, data.settings[field]);
}
save({ ...data, settings });
}
function updateActiveRateSettings(field, value) {
if (!activeRate) return;
const nextValue = field === "multiplier"
? clampDecimal(value, RNG_SIMULATOR_LIMITS.multiplierMin, RNG_SIMULATOR_LIMITS.multiplierMax, 0)
: clampDecimal(value, RNG_SIMULATOR_LIMITS.occurrenceRateMin, RNG_SIMULATOR_LIMITS.occurrenceRateMax, activeRate[field]);
save({
...data,
rates: data.rates.map((rate) => rate.id === activeRate.id ? { ...rate, [field]: nextValue } : rate)
});
}
function updateMultiplier(value) {
setMultiplierDraft(value);
if (String(value).trim() === "") {
updateSettings("multiplier", 0);
updateActiveRateSettings("multiplier", 0);
return;
}
updateSettings("multiplier", value);
updateActiveRateSettings("multiplier", value);
}
function blurMultiplier() {
@ -141,7 +160,7 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
const parsed = Number(raw.replace(",", "."));
if (!raw || !Number.isFinite(parsed)) {
setMultiplierDraft(null);
updateSettings("multiplier", 0);
updateActiveRateSettings("multiplier", 0);
return;
}
setMultiplierDraft(null);
@ -149,16 +168,16 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
function clearMultiplier() {
setMultiplierDraft(null);
updateSettings("multiplier", 0);
updateActiveRateSettings("multiplier", 0);
}
function updateOccurrence(field, value) {
setOccurrenceDrafts((drafts) => ({ ...drafts, [field]: value }));
if (String(value).trim() === "") {
updateSettings(field, 0);
updateActiveRateSettings(field, 0);
return;
}
updateSettings(field, value);
updateActiveRateSettings(field, value);
}
function blurOccurrence(field) {
@ -166,7 +185,7 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
if (value === null) return;
const raw = value.trim();
const parsed = Number(raw.replace(",", "."));
if (!raw || !Number.isFinite(parsed)) updateSettings(field, 0);
if (!raw || !Number.isFinite(parsed)) updateActiveRateSettings(field, 0);
setOccurrenceDrafts((drafts) => ({ ...drafts, [field]: null }));
}
@ -207,12 +226,12 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
function runSimulation() {
if (!activeRate) return;
const result = simulateRngDraws(activeRate.rate, data.settings.simulationDraws, Math.random, data.settings.multiplier);
const result = simulateRngDraws(activeRate.rate, data.settings.simulationDraws, Math.random, activeRate.multiplier);
const entry = {
id: context.uid("rng"),
label: activeRate.label,
rate: activeRate.rate,
multiplier: data.settings.multiplier,
multiplier: activeRate.multiplier,
...result
};
const nextHistory = addRngSimulationHistory(history, entry);
@ -302,7 +321,7 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
<div className="rng-simulator-field rng-simulator-multiplier-field">
<span>{textContent.multiplierLabel || "Multiplicateur"}</span>
<div className="rng-simulator-percent-input">
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={multiplierDraft ?? String(data.settings.multiplier ?? 0)} onChange={(event) => updateMultiplier(event.target.value)} onBlur={blurMultiplier} aria-label={textContent.multiplierLabel || "Multiplicateur"} />
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={multiplierDraft ?? String(activeRate.multiplier ?? 0)} onChange={(event) => updateMultiplier(event.target.value)} onBlur={blurMultiplier} aria-label={textContent.multiplierLabel || "Multiplicateur"} />
<span>%</span>
<button className="nds-icon-button" data-size="xs" type="button" onClick={clearMultiplier} aria-label={textContent.clearMultiplierButton || "Réinitialiser le multiplicateur"} title={textContent.clearMultiplierButton || "Réinitialiser le multiplicateur"}>
<Icon name="rubber" />
@ -320,15 +339,15 @@ export function RngSimulatorModule({ toolboxId, moduleId, context, textExchangeO
<div className="rng-simulator-settings rng-simulator-three-columns">
<label className="rng-simulator-field">
<span>{textContent.perHourLabel || "Par heure"}</span>
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={occurrenceDrafts.perHour ?? String(data.settings.perHour ?? 0)} onChange={(event) => updateOccurrence("perHour", event.target.value)} onBlur={() => blurOccurrence("perHour")} />
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={occurrenceDrafts.perHour ?? String(activeRate.perHour ?? 0)} onChange={(event) => updateOccurrence("perHour", event.target.value)} onBlur={() => blurOccurrence("perHour")} />
</label>
<label className="rng-simulator-field">
<span>{textContent.perDayLabel || "Par jour"}</span>
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={occurrenceDrafts.perDay ?? String(data.settings.perDay ?? 0)} onChange={(event) => updateOccurrence("perDay", event.target.value)} onBlur={() => blurOccurrence("perDay")} />
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={occurrenceDrafts.perDay ?? String(activeRate.perDay ?? 0)} onChange={(event) => updateOccurrence("perDay", event.target.value)} onBlur={() => blurOccurrence("perDay")} />
</label>
<label className="rng-simulator-field">
<span>{textContent.perMonthLabel || "Par mois"}</span>
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={occurrenceDrafts.perMonth ?? String(data.settings.perMonth ?? 0)} onChange={(event) => updateOccurrence("perMonth", event.target.value)} onBlur={() => blurOccurrence("perMonth")} />
<input className="nds-input" data-size="sm" type="text" inputMode="decimal" value={occurrenceDrafts.perMonth ?? String(activeRate.perMonth ?? 0)} onChange={(event) => updateOccurrence("perMonth", event.target.value)} onBlur={() => blurOccurrence("perMonth")} />
</label>
</div>
<div className="rng-simulator-time-grid">