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.
157 lines
4.8 KiB
TypeScript
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;
|