const { useDeferredValue, useEffect, useRef, useState, startTransition } = React;
const NAV_ITEMS = [
{ key: "dashboard", label: "Dashboard" },
{ key: "preset", label: "Preset Batteries" },
{ key: "custom", label: "Custom Batteries" },
{ key: "add", label: "Add Battery" },
{ key: "import", label: "Import Files" },
];
const DEFAULT_FORM = {
name: "",
battery_type: "Li-ion",
num_cells: 48,
base_voltage: 4.1,
base_soh: 95,
base_temp: 25,
degradation_rate: 0.03,
fault_probability: 0.1,
capacity_ah: 100,
max_charge_rate: 50,
max_discharge_rate: 100,
operating_temp_min: -10,
operating_temp_max: 60,
description: "Custom battery configuration",
};
function createOrUpdateChart(chartRef, canvasRef, config) {
if (!canvasRef.current) {
return;
}
if (!chartRef.current) {
chartRef.current = new Chart(canvasRef.current.getContext("2d"), config);
return;
}
chartRef.current.data = config.data;
chartRef.current.options = config.options;
chartRef.current.update("none");
}
function destroyChart(chartRef) {
if (chartRef.current) {
chartRef.current.destroy();
chartRef.current = null;
}
}
function formatMetric(value, suffix, digits) {
if (value === null || value === undefined || Number.isNaN(Number(value))) {
return "--";
}
return Number(value).toFixed(digits) + suffix;
}
function selectionKey(selection) {
return selection ? selection.source + ":" + selection.id : "";
}
function selectPreferredBattery(catalog, preferred) {
const combined = catalog.predefined.concat(catalog.custom);
if (!combined.length) {
return null;
}
if (!preferred) {
return combined[0];
}
return combined.find(function findMatch(item) {
return item.id === preferred.id && item.source === preferred.source;
}) || combined[0];
}
function Sidebar({ activeSection, onSectionChange }) {
return (
);
}
function BatteryGrid({ items, onDelete, selected, title, onSelect }) {
return (
{title}
{items.length}
{items.map(function renderBattery(item) {
const active = selected && item.id === selected.id && item.source === selected.source;
return (
);
})}
);
}
function BatteryForm({ form, notice, onChange, onSubmit, submitting }) {
return (
Add Battery
Minimal form
{notice ? {notice}
: null}
);
}
function UploadPanel({ notice, onUpload, uploading }) {
const inputRef = useRef(null);
return (
Import Files
CSV / XLSX / JSON / TXT
{notice ? {notice}
: null}
);
}
function ConfirmDialog({ onCancel, onConfirm, open, title }) {
if (!open) {
return null;
}
return (
{title}
This action will permanently remove the custom battery.
);
}
function MetricCard({ tone, label, value }) {
return (
);
}
function MonitoringCharts({ liveData }) {
const historyCanvasRef = useRef(null);
const forecastCanvasRef = useRef(null);
const historyChartRef = useRef(null);
const forecastChartRef = useRef(null);
const deferredLiveData = useDeferredValue(liveData);
useEffect(function cleanupCharts() {
return function () {
destroyChart(historyChartRef);
destroyChart(forecastChartRef);
};
}, []);
useEffect(function syncCharts() {
if (!deferredLiveData) {
return;
}
const history = deferredLiveData.long_term_forecast.history || [];
const projection = deferredLiveData.long_term_forecast.projection || [];
const historyGradient = historyCanvasRef.current.getContext("2d").createLinearGradient(0, 0, 0, 220);
historyGradient.addColorStop(0, "rgba(59, 130, 246, 0.22)");
historyGradient.addColorStop(1, "rgba(59, 130, 246, 0.02)");
const forecastGradient = forecastCanvasRef.current.getContext("2d").createLinearGradient(0, 0, 0, 220);
forecastGradient.addColorStop(0, "rgba(34, 197, 94, 0.18)");
forecastGradient.addColorStop(1, "rgba(234, 179, 8, 0.05)");
const sharedOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { enabled: true, displayColors: false } },
scales: {
x: {
display: true,
ticks: {
color: "#94a3b8",
maxTicksLimit: 5,
font: { size: 11 },
},
grid: {
color: "rgba(148, 163, 184, 0.18)",
drawBorder: false,
},
border: { display: false },
},
y: {
display: true,
ticks: {
color: "#94a3b8",
maxTicksLimit: 4,
font: { size: 11 },
},
grid: {
color: "rgba(148, 163, 184, 0.18)",
drawBorder: false,
},
border: { display: false },
},
},
elements: { point: { radius: 0, hoverRadius: 4 } },
};
createOrUpdateChart(historyChartRef, historyCanvasRef, {
type: "line",
data: {
labels: history.map(function (point) {
return new Date(point.timestamp * 1000).toLocaleDateString();
}),
datasets: [
{
data: history.map(function (point) {
return point.soh;
}),
borderColor: "#2f6db6",
backgroundColor: historyGradient,
borderWidth: 2.5,
fill: true,
tension: 0.4,
},
],
},
options: sharedOptions,
});
const lastHistory = history[history.length - 1];
createOrUpdateChart(forecastChartRef, forecastCanvasRef, {
type: "line",
data: {
labels: lastHistory
? [new Date(lastHistory.timestamp * 1000).toLocaleDateString()].concat(
projection.map(function (point) {
return new Date(point.x * 1000).toLocaleDateString();
})
)
: [],
datasets: [
{
data: lastHistory ? [lastHistory.soh].concat(new Array(projection.length).fill(null)) : [],
borderColor: "#22a05d",
backgroundColor: forecastGradient,
borderWidth: 2.2,
fill: true,
tension: 0.25,
},
{
data: lastHistory
? [lastHistory.soh].concat(
projection.map(function (point) {
return point.y;
})
)
: [],
borderColor: "#d89b3d",
borderDash: [5, 5],
borderWidth: 2,
fill: false,
tension: 0.25,
},
],
},
options: sharedOptions,
});
}, [deferredLiveData]);
return (
24-Month Forecast
{liveData ? liveData.long_term_forecast.text : "--"}
);
}
function CellGrid({ cells }) {
return (
Cell Detail
{cells.length} cells
{cells.map(function renderCell(cell) {
return (
Cell {cell.id}
{formatMetric(cell.voltage, "V", 3)}
{formatMetric(cell.temperature, "°C", 1)}
);
})}
);
}
function DashboardView({ onDelete, selected, liveData, onRefresh }) {
if (!selected) {
return (
No battery selected
Select a preset or custom battery to load the dashboard.
);
}
const summary = liveData && liveData.pack_summary ? liveData.pack_summary : null;
const metrics = liveData && liveData.model_performance ? liveData.model_performance : { mae: "--", r2_score: "--" };
const alertText = summary && summary.alert !== "None" ? summary.alert : "Healthy";
return (
);
}
function Workspace({
activeSection,
catalog,
error,
form,
liveData,
message,
onChange,
onDelete,
onRefresh,
onSectionChange,
onSelect,
onSubmit,
onUpload,
selected,
submitting,
uploading,
}) {
const sectionNotice = error || message;
return (
{activeSection === "dashboard" ? : null}
{activeSection === "preset" ? (
) : null}
{activeSection === "custom" ? (
) : null}
{activeSection === "add" ? : null}
{activeSection === "import" ? : null}
);
}
function IntelliBMSApp() {
const [activeSection, setActiveSection] = useState("dashboard");
const [catalog, setCatalog] = useState({ predefined: [], custom: [] });
const [selected, setSelected] = useState(null);
const [liveData, setLiveData] = useState(null);
const [submitting, setSubmitting] = useState(false);
const [uploading, setUploading] = useState(false);
const [message, setMessage] = useState("");
const [error, setError] = useState("");
const [form, setForm] = useState(DEFAULT_FORM);
const [pendingDeleteId, setPendingDeleteId] = useState(null);
function changeSection(section) {
setMessage("");
setError("");
setActiveSection(section);
}
async function loadCatalog(preferred) {
try {
const payload = await window.IntelliBMSApi.getBatteryCatalog();
startTransition(function () {
setCatalog(payload);
setSelected(function (previous) {
return selectPreferredBattery(payload, preferred || previous);
});
});
} catch (requestError) {
setError(requestError.message);
}
}
async function loadLiveData(selection) {
if (!selection) {
return;
}
try {
const payload = await window.IntelliBMSApi.getLiveData(selection);
startTransition(function () {
setLiveData(payload);
});
} catch (requestError) {
setError(requestError.message);
}
}
useEffect(function () {
loadCatalog();
}, []);
useEffect(function () {
if (!selected) {
setLiveData(null);
return;
}
loadLiveData(selected);
const intervalId = window.setInterval(function () {
loadLiveData(selected);
}, 3000);
return function () {
window.clearInterval(intervalId);
};
}, [selectionKey(selected)]);
function updateForm(field, value) {
setForm(function (previous) {
return { ...previous, [field]: value };
});
}
async function handleSubmit(event) {
event.preventDefault();
setSubmitting(true);
setError("");
setMessage("");
try {
const createdBattery = await window.IntelliBMSApi.createBattery(form);
setForm(DEFAULT_FORM);
setMessage("");
await loadCatalog({ id: createdBattery.id, source: "custom" });
changeSection("dashboard");
} catch (requestError) {
setError(requestError.message);
} finally {
setSubmitting(false);
}
}
async function handleUpload(files) {
setUploading(true);
setError("");
setMessage("");
try {
const response = await window.IntelliBMSApi.uploadBatteryFiles(files);
setMessage("");
await loadCatalog({ id: response.battery_id, source: "custom" });
changeSection("dashboard");
} catch (requestError) {
setError(requestError.message);
} finally {
setUploading(false);
}
}
async function handleDelete(batteryId) {
if (!batteryId) {
return;
}
setError("");
setMessage("");
try {
await window.IntelliBMSApi.deleteBattery(batteryId);
setMessage("");
setPendingDeleteId(null);
await loadCatalog();
} catch (requestError) {
setError(requestError.message);
}
}
return (
);
}
window.IntelliBMSApp = IntelliBMSApp;