feat: implemented AgileCalculator component
parent
0a599cf9d4
commit
9c5a8fd2c4
@ -0,0 +1,150 @@
|
||||
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 calculatorQuantity = createMemo(() =>
|
||||
calculateAgileQuantity(
|
||||
agileCalculator.hoursPerPoint,
|
||||
new Big(agileCalculator.risk).div(100).toNumber(),
|
||||
agileCalculator.minPoints,
|
||||
agileCalculator.maxPoints
|
||||
)
|
||||
);
|
||||
|
||||
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="Stunden pro Story Point"
|
||||
min="0"
|
||||
type="number"
|
||||
value={agileCalculator.hoursPerPoint}
|
||||
onInput={(e) =>
|
||||
!Number.isNaN(e.currentTarget.valueAsNumber) &&
|
||||
setAgileCalculator("hoursPerPoint", 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="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="p-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="p-1">
|
||||
<div class="flex justify-between">
|
||||
<span>Gewichtete Story Points:</span>
|
||||
{new Big(calculatorQuantity())
|
||||
.div(agileCalculator.hoursPerPoint)
|
||||
.toNumber()}{" "}
|
||||
SP
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-1 font-mono text-sm text-right">
|
||||
<span title="Gewichtete Story Points" class="text-title-border">
|
||||
{new Big(calculatorQuantity())
|
||||
.div(agileCalculator.hoursPerPoint)
|
||||
.toNumber()}{" "}
|
||||
SP
|
||||
</span>{" "}
|
||||
*{" "}
|
||||
<span title="Stunden pro Story Point" class="text-title-border">
|
||||
{agileCalculator.hoursPerPoint} h
|
||||
</span>{" "}
|
||||
*{" "}
|
||||
<span title="Einzelpreis" class="text-title-border">
|
||||
{agileCalculator.singlePrice} CHF
|
||||
</span>{" "}
|
||||
=
|
||||
</div>
|
||||
<div class="p-1 font-bold">
|
||||
<div class="flex justify-between">
|
||||
<span>Gesamtpreis:</span>
|
||||
{formatAmount(
|
||||
calculatorQuantity() * agileCalculator.singlePrice
|
||||
)}{" "}
|
||||
CHF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgileCalculator;
|
Loading…
Reference in New Issue