// AccountsScreen — connected data sources + chart of accounts
(function () {
const fmt = (n, ccy) => {
if (n == null || isNaN(n)) return "—";
const abs = Math.abs(n);
const isInt = ccy === "KRW" || ccy === "JPY" || Math.abs(abs - Math.round(abs)) < 0.005;
return (n < 0 ? "− " : "") + abs.toLocaleString(undefined, {
minimumFractionDigits: isInt ? 0 : 2,
maximumFractionDigits: isInt ? 0 : 2,
});
};
const entityShort = { personal: "P/", tfox: "9T/", finacode: "FC/" };
const entityClass = { personal: "personal", tfox: "tfox", finacode: "finacode" };
// ----- Status pill --------------------------------------------------
function StatusPill({ status }) {
const map = {
ok: { lbl: "OK", cls: "ok" },
warn: { lbl: "WARN", cls: "warn" },
fail: { lbl: "FAILED", cls: "fail" },
idle: { lbl: "IDLE", cls: "idle" },
};
const m = map[status] || map.idle;
return {m.lbl};
}
function MethodTag({ method }) {
const map = {
"api": "API",
"csv-poll": "CSV · POLL",
"csv-drop": "CSV · DROP",
"manual": "MANUAL",
};
return {map[method] || method.toUpperCase()};
}
// ----- Connected source card ---------------------------------------
function SourceCard({ src, selected, onClick }) {
return (
);
}
// ----- Chart of accounts tree --------------------------------------
// accountsByEntity rows have full path; we render them grouped per entity, indented by depth.
function ChartOfAccounts({ entity }) {
const data = window.AKEFIN_DATA.accountsByEntity;
const sets = entity === "all"
? [["Personal", data.personal, "personal"], ["9TFox", data.tfox, "tfox"], ["Finacode", data.finacode, "finacode"]]
: [[entity === "tfox" ? "9TFox" : entity[0].toUpperCase() + entity.slice(1), data[entity], entity]];
return (
{sets.map(([title, rows, cls]) => (
{entityShort[cls]}
{title}
{rows.length} accounts
{rows.map((r) => {
const depth = (r.path.match(/:/g) || []).length;
const leaf = r.path.split(":").pop();
return (
{r.kind === "branch" ? "▸" : "·"}
{leaf}
{r.path}
{r.balance >= 0 ? "+ " : "− "}{fmt(Math.abs(r.balance), r.ccy)}
{r.ccy}
);
})}
))}
);
}
// ----- Detail panel for a selected source --------------------------
function SourceDetail({ src }) {
if (!src) {
return (
SELECT A SOURCE
Click a connected account to inspect sync settings, recent imports, and rotation.
);
}
return (
{src.kind.toUpperCase()} · {src.country}
{src.name}
{src.maskedNo}
BALANCE
{fmt(src.balance, src.ccy)} {src.ccy}
TX · 30D
{src.txCount30d}
LAST SYNC
{src.lastSync.split(" ")[1]}
{src.lastSync.split(" ")[0]}
SYNC
METHOD
ENTITY{entityShort[src.entity]} {src.entity === "tfox" ? "9TFox" : src.entity[0].toUpperCase() + src.entity.slice(1)}
SCHEDULE{src.method === "api" ? "every 4 hours" : src.method === "csv-poll" ? "every 12 hours" : "on demand"}
CCY{src.ccy}
RECENT IMPORTS
{src.lastSync.split(" ")[1]}
Last successful sync · {src.txCount30d} rows in 30d
{src.status === "fail" && (
{src.lastSync.split(" ")[1]}
CSV parse failed · encoding mismatch
)}
{src.status === "warn" && (
{src.lastSync.split(" ")[1]}
11 rows queued for review · 3 failed
)}
);
}
// ----- Top-level screen --------------------------------------------
function AccountsScreen({ entity }) {
const [tab, setTab] = React.useState("sources"); // sources | coa
const sources = window.AKEFIN_DATA.connectedAccounts;
const visible = sources.filter(s => entity === "all" || s.entity === entity);
const [selectedId, setSelectedId] = React.useState(visible[0]?.id);
React.useEffect(() => {
if (visible.length && !visible.find(s => s.id === selectedId)) {
setSelectedId(visible[0].id);
}
}, [entity]);
const selected = visible.find(s => s.id === selectedId);
// Totals (in KRW equivalent)
const totalKrw = visible.reduce((a, s) => a + window.AKEFIN_DATA.convert(s.balance, s.ccy, "KRW"), 0);
const okCount = visible.filter(s => s.status === "ok").length;
const warnCount = visible.filter(s => s.status === "warn" || s.status === "fail").length;
return (
ACCOUNTS · {entity === "all" ? "ALL ENTITIES" : entity.toUpperCase()}
Connected sources
{visible.length} sources · {okCount} healthy · {warnCount} need attention · combined ≈ {fmt(totalKrw, "KRW")} KRW
{tab === "sources" && (
{visible.map(s => (
setSelectedId(s.id)} />
))}
)}
{tab === "coa" &&
}
);
}
Object.assign(window, { AccountsScreen });
})();