You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rappli/src/components/AgileCalculator.tsx

157 lines
4.8 KiB
TypeScript

import Big from "big.js";
import { Component, createMemo } from "solid-js";
import { createStore } from "solid-js/store";
import { TextInput } from "./Form";
import { calculateAgileQuantity } from "./Positions";
import { formatAmount } from "./SwissInvoice";
const AgileCalculator: Component = () => {
const [agileCalculator, setAgileCalculator] = createStore({
minPoints: 0,
maxPoints: 10,
risk: 70,
singlePrice: 10.0,
hoursPerPoint: 1,
});
const hours = createMemo(() =>
calculateAgileQuantity(
agileCalculator.hoursPerPoint,
new Big(agileCalculator.risk).div(100).toNumber(),
agileCalculator.minPoints,
agileCalculator.maxPoints
)
);
const quantity = createMemo(() =>
agileCalculator.hoursPerPoint > 0
? new Big(hours()).div(agileCalculator.hoursPerPoint).toNumber()
: hours()
);
return (
<div class="border border-slate-500/40 flex flex-col gap-2 p-2 rounded mb-3">
<h3 class="mt-0">Rechnungsbeispiel</h3>
<TextInput
labelMinWidth="300px"
label="Risiko Faktor"
suffix="%"
step="10"
min="0"
max="100"
type="number"
value={agileCalculator.risk}
onInput={(e) =>
!Number.isNaN(e.currentTarget.valueAsNumber) &&
setAgileCalculator("risk", e.currentTarget.valueAsNumber)
}
/>
<TextInput
labelMinWidth="300px"
label="Story Points Minimum"
min="0"
type="number"
value={agileCalculator.minPoints}
onInput={(e) =>
!Number.isNaN(e.currentTarget.valueAsNumber) &&
setAgileCalculator("minPoints", e.currentTarget.valueAsNumber)
}
/>
<TextInput
labelMinWidth="300px"
label="Story Points Maximum"
min="0"
type="number"
value={agileCalculator.maxPoints}
onInput={(e) =>
!Number.isNaN(e.currentTarget.valueAsNumber) &&
setAgileCalculator("maxPoints", e.currentTarget.valueAsNumber)
}
/>
<TextInput
labelMinWidth="300px"
label="Stunden pro Story Point"
suffix="h"
min="0"
type="number"
value={agileCalculator.hoursPerPoint}
onInput={(e) =>
!Number.isNaN(e.currentTarget.valueAsNumber) &&
setAgileCalculator("hoursPerPoint", e.currentTarget.valueAsNumber)
}
/>
<TextInput
labelMinWidth="300px"
label="Einzelpreis"
step="0.01"
suffix="CHF"
type="number"
value={agileCalculator.singlePrice}
onInput={(e) =>
!Number.isNaN(e.currentTarget.valueAsNumber) &&
setAgileCalculator("singlePrice", e.currentTarget.valueAsNumber)
}
/>
<div class="items-center grid grid-cols-[300px_1fr] gap-2">
<div class="px-1 font-mono text-sm text-right">
{"("}
<span title="Nichtrisiko Anteil" class="text-title-border">
{new Big(-100).plus(agileCalculator.risk).abs().toNumber()}%
</span>{" "}
*{" "}
<span title="Story Points Minimum" class="text-title-border">
{agileCalculator.minPoints} SP
</span>
{")"} + {"("}
<span title="Risiko Anteil" class="text-title-border">
{agileCalculator.risk}%
</span>{" "}
*{" "}
<span title="Story Points Maximum" class="text-title-border">
{agileCalculator.maxPoints > agileCalculator.minPoints
? agileCalculator.maxPoints
: agileCalculator.minPoints}{" "}
SP
</span>
{")"} =
</div>
<div class="px-1 flex justify-between">
<span>Gewichtete Story Points:</span>
{quantity()} SP
</div>
<div class="px-1 font-mono text-sm text-right">
<span title="Gewichtete Story Points" class="text-title-border">
{quantity()} SP
</span>{" "}
*{" "}
<span title="Stunden pro Story Point" class="text-title-border">
{agileCalculator.hoursPerPoint} h
</span>{" "}
=
</div>
<div class="px-1 flex justify-between">
<span>Menge:</span>
{hours()} h
</div>
<div class="px-1 font-mono text-sm text-right">
<span title="Menge" class="text-title-border">
{hours()} h
</span>{" "}
*{" "}
<span title="Einzelpreis" class="text-title-border">
{agileCalculator.singlePrice} CHF
</span>{" "}
=
</div>
<div class="px-1 font-bold flex justify-between">
<span>Gesamtpreis:</span>
{formatAmount(
new Big(hours()).mul(agileCalculator.singlePrice).toNumber()
)}{" "}
CHF
</div>
</div>
</div>
);
};
export default AgileCalculator;