diff --git a/web/src/harvest.ts b/web/src/harvest.ts
index 80ef6aaf..f197301e 100644
--- a/web/src/harvest.ts
+++ b/web/src/harvest.ts
@@ -40,29 +40,49 @@ function renderHarvestables(capital_gains: CapitalGain[]) {
.append("div")
.attr("class", "card-header-icon")
.style("flex-grow", "1")
+ .style("cursor", "auto")
.append("div")
.each(function (cg) {
const self = d3.select(this);
- const [units, amount, taxableGain] = unitsRequired(cg, 100000);
- self.append("span").html("Redeeming ");
+ const [units, amount, taxableGain] = unitsRequiredFromGain(cg, 100000);
+ self.append("span").html("If you redeem ");
const unitsSpan = self.append("span").text(formatFloat(units));
- self.append("span").html(" units (amount ₹");
- const amountSpan = self.append("span").text(formatCurrency(amount));
- self.append("span").html(") will result in taxable gain ₹");
+ self.append("span").html(" units you will get ₹");
+ const amountInput = self
+ .append("input")
+ .attr("class", "input is-small adjustable-input")
+ .attr("type", "number")
+ .attr("value", round(amount))
+ .attr("step", "1000")
+ .on("input", (event) => {
+ const [units, amount, taxableGain] = unitsRequiredFromAmount(
+ cg,
+ parseInt(event.srcElement.value)
+ );
+
+ unitsSpan.text(formatFloat(units));
+ (taxableGainInput.node() as HTMLInputElement).value =
+ round(taxableGain).toString();
+ event.srcElement.value = round(amount);
+ });
self
+ .append("span")
+ .html(" and your taxable gain would be ₹");
+ const taxableGainInput = self
.append("input")
.attr("class", "input is-small adjustable-input")
.attr("type", "number")
.attr("value", round(taxableGain))
.attr("step", "1000")
.on("input", (event) => {
- const [units, amount, taxableGain] = unitsRequired(
+ const [units, amount, taxableGain] = unitsRequiredFromGain(
cg,
- event.srcElement.value
+ parseInt(event.srcElement.value)
);
unitsSpan.text(formatFloat(units));
event.srcElement.value = round(taxableGain);
- amountSpan.text(formatCurrency(amount));
+ (amountInput.node() as HTMLInputElement).value =
+ round(amount).toString();
});
});
@@ -137,7 +157,7 @@ function renderHarvestables(capital_gains: CapitalGain[]) {
.append("div")
.attr("class", "table-container")
.style("overflow-y", "auto")
- .style("max-height", "210px")
+ .style("max-height", "245px")
.append("table")
.attr("class", "table");
@@ -182,7 +202,7 @@ function renderHarvestables(capital_gains: CapitalGain[]) {
});
}
-function unitsRequired(
+function unitsRequiredFromGain(
cg: CapitalGain,
taxableGain: number
): [number, number, number] {
@@ -208,6 +228,30 @@ function unitsRequired(
return [units, amount, gain];
}
+function unitsRequiredFromAmount(
+ cg: CapitalGain,
+ expectedAmount: number
+): [number, number, number] {
+ let gain = 0;
+ let amount = 0;
+ let units = 0;
+ const available = _.clone(cg.harvestable.harvest_breakdown);
+ while (expectedAmount > amount && available.length > 0) {
+ const breakdown = available.shift();
+ if (breakdown.current_price < expectedAmount - amount) {
+ gain += breakdown.taxable_unrealized_gain;
+ units += breakdown.units;
+ amount += breakdown.current_price;
+ } else {
+ const u = (expectedAmount - amount) / cg.harvestable.current_unit_price;
+ units += u;
+ amount = expectedAmount;
+ gain += u * (breakdown.taxable_unrealized_gain / breakdown.units);
+ }
+ }
+ return [units, amount, gain];
+}
+
function renderSingleBar(cg: CapitalGain) {
const selection = d3.select(this);
const svg = selection.append("svg");
diff --git a/web/static/styles/custom.css b/web/static/styles/custom.css
index b9dd4de0..a6a733a1 100644
--- a/web/static/styles/custom.css
+++ b/web/static/styles/custom.css
@@ -96,13 +96,16 @@ body {
}
.adjustable-input {
+ border: none;
+ border-bottom: 1px solid #ccc;
+ box-shadow: none;
width: 80px;
- font-weight: bold;
- font-size: 0.75rem !important;
+ font-size: 1em !important;
margin-left: 5px;
padding-top: 0;
padding-bottom: 0;
padding-right: 0;
+ padding-left: 5px;
height: 1.3rem;
}