forge/arma/client/addons/cad/ui/src/dispatcher.html
Jacob Schmidt ff7ff0c4e5 Implement org credit line debt and bank repayment flow (#2)
## Summary

This finishes the org credit line workflow so it behaves like reserved treasury-backed credit instead of a simple member allowance.

## What changed

- reserve org funds immediately when a credit line is assigned
- track credit lines with:
  - approved amount
  - available amount
  - outstanding principal
  - interest rate
  - amount due
- consume reserved credit during store checkout without charging org funds a second time
- add credit line repayment through the bank app
- sync richer credit line state into org and bank payloads/UI
- keep legacy `amount` compatibility mapped to available credit for older consumers

## User-facing behavior

- assigning a credit line now reduces available org funds immediately
- spending on `credit_line` reduces available credit and creates debt with interest
- the bank app now shows outstanding credit debt and allows repayment from personal bank funds
- the org treasury view now shows reserved credit and outstanding due totals

## Validation

- `cargo fmt`
- `npm run build:webui`
- `cargo test -p forge-services --quiet`
- `cargo test -p forge-server --quiet`

## Follow-up checks

- validate in-game that assigning a credit line reduces org funds immediately
- validate store checkout with `credit_line` updates available credit and debt correctly
- validate bank repayment decreases player bank balance, increases org funds, and reduces amount due

Co-authored-by: Jacob Schmidt <innovativestudios@outlook.com>
Reviewed-on: #2
2026-04-02 16:50:38 -05:00

373 lines
16 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div class="dispatch-shell">
<header class="dispatch-header">
<div>
<p class="dispatch-kicker">Dispatch Dashboard</p>
<h2>Operational Board</h2>
</div>
</header>
<div id="dispatcherStatusMessage" class="dispatch-status"></div>
<div
id="dispatcherDangerAlert"
class="dispatch-danger-alert is-hidden"
></div>
<div
id="dispatcherRequestAlert"
class="dispatch-warning-alert is-hidden"
></div>
<section class="dispatch-metrics">
<div class="metric-card">
<span class="metric-label">Open Contracts</span>
<strong id="metricOpenContracts">0</strong>
</div>
<div class="metric-card">
<span class="metric-label">Assigned Contracts</span>
<strong id="metricAssignedContracts">0</strong>
</div>
<div class="metric-card">
<span class="metric-label">Active Groups</span>
<strong id="metricActiveGroups">0</strong>
</div>
<div id="metricOpenRequestsCard" class="metric-card">
<span class="metric-label">Open Requests</span>
<strong id="metricOpenRequests">0</strong>
</div>
<div id="metricDangerGroupsCard" class="metric-card">
<span class="metric-label">Groups In Danger</span>
<strong id="metricDangerGroups">0</strong>
</div>
</section>
<div class="dispatch-grid">
<section class="dispatch-panel dispatch-panel-open">
<div class="dispatch-panel-header">
<h3>Available Contracts</h3>
<button
id="dispatcherCreateOrderBtn"
type="button"
class="dispatch-icon-btn"
aria-label="Create dispatch order"
title="Create dispatch order"
>
+
</button>
</div>
<div
id="dispatcherOpenContracts"
class="dispatch-list"
></div>
</section>
<section class="dispatch-panel dispatch-panel-assigned">
<div class="dispatch-panel-header">
<h3>Assigned Contracts</h3>
</div>
<div
id="dispatcherAssignedContracts"
class="dispatch-list"
></div>
</section>
<section class="dispatch-panel dispatch-panel-groups">
<div class="dispatch-panel-header">
<h3>Group Board</h3>
</div>
<div id="dispatcherGroups" class="dispatch-list"></div>
</section>
<section class="dispatch-panel dispatch-panel-activity">
<div class="dispatch-panel-header">
<h3>Requests & Activity</h3>
</div>
<div id="dispatcherActivity" class="dispatch-list"></div>
</section>
</div>
<div id="dispatcherGroupModal" class="dispatch-modal is-hidden">
<div class="dispatch-modal-backdrop"></div>
<div
class="dispatch-modal-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="dispatcherGroupModalTitle"
>
<div class="dispatch-modal-header">
<div>
<p class="dispatch-kicker">Group Editor</p>
<h3 id="dispatcherGroupModalTitle">Manage Group</h3>
</div>
<button
id="dispatcherGroupModalCloseBtn"
class="dispatch-icon-btn"
type="button"
aria-label="Close group editor"
>
x
</button>
</div>
<div class="dispatch-modal-body">
<div class="dispatch-meta-grid">
<div>
<span class="metric-label">Callsign</span>
<strong id="dispatcherModalGroupCallsign"
>-</strong
>
</div>
<div>
<span class="metric-label">Leader</span>
<strong id="dispatcherModalGroupLeader"
>-</strong
>
</div>
<div>
<span class="metric-label">Current Task</span>
<strong id="dispatcherModalGroupTask"
>None</strong
>
</div>
<div>
<span class="metric-label">Org</span>
<strong id="dispatcherModalGroupOrg"
>default</strong
>
</div>
</div>
<div class="dispatch-modal-fields">
<label class="dispatch-field">
<span>Role</span>
<select
id="dispatcherModalRoleSelect"
class="dispatch-select"
></select>
</label>
<label class="dispatch-field">
<span>Status</span>
<select
id="dispatcherModalStatusSelect"
class="dispatch-select"
></select>
</label>
</div>
</div>
<div class="dispatch-modal-actions">
<button
id="dispatcherGroupModalSaveBtn"
type="button"
class="dispatch-btn"
>
Save Changes
</button>
</div>
</div>
</div>
<div id="dispatcherOrderModal" class="dispatch-modal is-hidden">
<div class="dispatch-modal-backdrop"></div>
<div
class="dispatch-modal-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="dispatcherOrderModalTitle"
>
<div class="dispatch-modal-header">
<div>
<p class="dispatch-kicker">Dispatch Order</p>
<h3 id="dispatcherOrderModalTitle">
Create Support Order
</h3>
</div>
<button
id="dispatcherOrderModalCloseBtn"
class="dispatch-icon-btn"
type="button"
aria-label="Close dispatch order editor"
>
x
</button>
</div>
<div class="dispatch-modal-body">
<div class="dispatch-modal-fields">
<label class="dispatch-field">
<span>Assignee Group</span>
<select
id="dispatcherOrderAssigneeSelect"
class="dispatch-select"
></select>
</label>
<label class="dispatch-field">
<span>Target Group</span>
<select
id="dispatcherOrderTargetSelect"
class="dispatch-select"
></select>
</label>
<label class="dispatch-field">
<span>Priority</span>
<select
id="dispatcherOrderPrioritySelect"
class="dispatch-select"
>
<option value="routine">routine</option>
<option value="priority" selected>
priority
</option>
<option value="emergency">emergency</option>
</select>
</label>
<label class="dispatch-field">
<span>Order Note</span>
<textarea
id="dispatcherOrderNoteInput"
class="dispatch-textarea"
rows="4"
placeholder="Optional order note for the assigned group."
></textarea>
</label>
</div>
</div>
<div class="dispatch-modal-actions">
<button
id="dispatcherOrderModalSaveBtn"
type="button"
class="dispatch-btn"
>
Create Order
</button>
</div>
</div>
</div>
<div id="dispatcherRequestModal" class="dispatch-modal is-hidden">
<div class="dispatch-modal-backdrop"></div>
<div
class="dispatch-modal-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="dispatcherRequestModalTitle"
>
<div class="dispatch-modal-header">
<div>
<p class="dispatch-kicker">Support Request</p>
<h3 id="dispatcherRequestModalTitle">
Request Details
</h3>
</div>
<button
id="dispatcherRequestModalCloseBtn"
class="dispatch-icon-btn"
type="button"
aria-label="Close support request details"
>
x
</button>
</div>
<div class="dispatch-modal-body">
<div class="dispatch-meta-grid">
<div>
<span class="metric-label">Title</span>
<strong id="dispatcherRequestTitle"
>Support Request</strong
>
</div>
<div>
<span class="metric-label">Priority</span>
<strong id="dispatcherRequestPriority"
>priority</strong
>
</div>
<div>
<span class="metric-label">Group</span>
<strong id="dispatcherRequestGroup"
>Unknown</strong
>
</div>
<div>
<span class="metric-label">Type</span>
<strong id="dispatcherRequestType"
>request</strong
>
</div>
</div>
<div class="dispatch-field">
<span>Summary</span>
<div
id="dispatcherRequestSummary"
class="dispatch-detail-block"
></div>
</div>
<div class="dispatch-field">
<span>Submitted Fields</span>
<div
id="dispatcherRequestFields"
class="dispatch-detail-list"
></div>
</div>
</div>
<div class="dispatch-modal-actions">
<button
id="dispatcherRequestConvertBtn"
type="button"
class="dispatch-btn dispatch-btn-secondary"
>
Convert to Order
</button>
<button
id="dispatcherRequestModalDoneBtn"
type="button"
class="dispatch-btn"
>
Done
</button>
</div>
</div>
</div>
</div>
<script>
window.MapLoader = {
loadCSS(path) {
return A3API.RequestFile(path).then((css) => {
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
});
},
loadJS(path) {
return A3API.RequestFile(path).then((js) => {
eval(js);
});
},
loadAll(resources) {
return resources.reduce((promise, resource) => {
return promise.then(() => {
if (resource.endsWith(".css")) {
return this.loadCSS(resource);
}
if (resource.endsWith(".js")) {
return this.loadJS(resource);
}
return Promise.resolve();
});
}, Promise.resolve());
},
};
MapLoader.loadAll([
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-dispatcher.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-dispatcher.js",
]).catch((err) => console.error("[DISPATCHER] Load error:", err));
</script>
</body>
</html>