diff --git a/app/frontend/javascript/controllers/scholarship_preview_controller.js b/app/frontend/javascript/controllers/scholarship_preview_controller.js
index 5c937d58c7..7d35b7d732 100644
--- a/app/frontend/javascript/controllers/scholarship_preview_controller.js
+++ b/app/frontend/javascript/controllers/scholarship_preview_controller.js
@@ -3,9 +3,16 @@ import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="scholarship-preview"
// Live-previews the "Still owed" stat and the scholarship-amount allocation
// as the user edits the amount — before saving.
+//
+// Grant-funded scholarships have no event/registration to allocate against, so
+// instead the pending award is split live across "Allocated" (counts once the
+// recipient's tasks are completed) and "Amount due" (what's still owed them).
export default class extends Controller {
- static targets = ["amount", "owed", "amountBox", "allocationStrip", "allocatedHint"]
- static values = { eventCost: Number, otherAllocated: Number }
+ static targets = [
+ "amount", "owed", "amountBox", "allocationStrip", "allocatedHint",
+ "completed", "grantAmount", "grantAllocated", "grantDue"
+ ]
+ static values = { eventCost: Number, otherAllocated: Number, grantFunded: Boolean }
connect() {
this.update()
@@ -17,6 +24,7 @@ export default class extends Controller {
this.renderOwed(allocatedCents)
this.renderAllocation(allocatedCents)
+ this.renderGrantSplit(allocatedCents)
}
renderOwed(allocatedCents) {
@@ -53,6 +61,32 @@ export default class extends Controller {
}
}
+ // Grant-funded only: the award counts as allocated once tasks are completed;
+ // whatever isn't allocated is still due to the recipient.
+ renderGrantSplit(amountCents) {
+ if (!this.grantFundedValue) return
+
+ const completed = this.hasCompletedTarget && this.completedTarget.checked
+ const allocatedCents = completed ? amountCents : 0
+ const dueCents = Math.max(amountCents - allocatedCents, 0)
+
+ if (this.hasGrantAmountTarget) {
+ this.grantAmountTarget.textContent = this.formatDollars(amountCents)
+ }
+
+ if (this.hasGrantAllocatedTarget) {
+ this.grantAllocatedTarget.textContent = this.formatDollars(allocatedCents)
+ }
+
+ if (this.hasGrantDueTarget) {
+ const paid = dueCents <= 0
+ const cls = paid ? "border-green-200 bg-green-50 text-green-700" : "border-amber-200 bg-amber-50 text-amber-700"
+ const icon = paid ? "fa-circle-check" : "fa-circle-exclamation"
+ const label = paid ? "Paid" : `${this.formatDollars(dueCents)} due`
+ this.grantDueTarget.innerHTML = `${label}`
+ }
+ }
+
formatDollars(cents) {
const whole = cents % 100 === 0
return `$${(cents / 100).toLocaleString("en-US", { minimumFractionDigits: whole ? 0 : 2, maximumFractionDigits: 2 })}`
diff --git a/app/views/scholarships/_form.html.erb b/app/views/scholarships/_form.html.erb
index 2b2102909b..83b6f9e8f6 100644
--- a/app/views/scholarships/_form.html.erb
+++ b/app/views/scholarships/_form.html.erb
@@ -7,8 +7,9 @@
<%= render "shared/errors", resource: f.object if f.object.errors.any? %>
+ data-scholarship-preview-event-cost-value="<%= @allocatable.respond_to?(:event) ? @allocatable.event.cost_cents.to_i : 0 %>"
+ data-scholarship-preview-other-allocated-value="<%= @allocatable.respond_to?(:event) ? (@allocatable.allocations_sum.to_i - f.object.allocation&.amount.to_i) : 0 %>"
+ data-scholarship-preview-grant-funded-value="<%= grant_funded %>">
@@ -76,6 +77,37 @@
<% end %>
+ <%# Grant-funded scholarships have no event to owe against, so mirror the event
+ stat grid with the award split across "Allocated" (counts once tasks are
+ completed) and "Amount due" (what's still owed the recipient).
+ scholarship_preview_controller keeps these in sync as the amount and the
+ tasks toggle change. %>
+ <% if grant_funded %>
+ <% amount_cents = f.object.amount_cents.to_i %>
+ <% allocated_cents = f.object.tasks_completed? ? amount_cents : 0 %>
+ <% due_cents = [ amount_cents - allocated_cents, 0 ].max %>
+ <% paid = due_cents <= 0 %>
+
+ <% end %>
+
<%# With a registration, the amount + tasks-completed toggle live in the stat grid
above. Without one (grant-funded or standalone) they live here instead. %>
<% unless @allocatable.respond_to?(:event) %>
@@ -103,7 +135,7 @@