The card beside it had boxes, so this one gets boxes
Charger information was one long list of everything the service knows; the readings card next to it had been splitting its fields into a box per group all along. Same treatment here: Device, Status, Network and On the account, plus the service's own fields and the per-charger views, each in its own sunken section under a heading. Both clients, since the web and the phone draw the same card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a7f04cba0
commit
fb42791f8d
8 files changed
+234
-166
No files matched your search
@@ -310,6 +310,12 @@
|
|||||||
"relatedBy": "Kan nås via",
|
"relatedBy": "Kan nås via",
|
||||||
"timeZone": "Tidszone",
|
"timeZone": "Tidszone",
|
||||||
"linked": "Tilknyttet",
|
"linked": "Tilknyttet",
|
||||||
|
"groups": {
|
||||||
|
"device": "Enhed",
|
||||||
|
"status": "Status",
|
||||||
|
"network": "Netværk",
|
||||||
|
"account": "På kontoen"
|
||||||
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"station": "Stationspost",
|
"station": "Stationspost",
|
||||||
"totals": "Ladetotaler",
|
"totals": "Ladetotaler",
|
||||||
|
|||||||
@@ -310,6 +310,12 @@
|
|||||||
"relatedBy": "Reachable by",
|
"relatedBy": "Reachable by",
|
||||||
"timeZone": "Time zone",
|
"timeZone": "Time zone",
|
||||||
"linked": "Linked",
|
"linked": "Linked",
|
||||||
|
"groups": {
|
||||||
|
"device": "Device",
|
||||||
|
"status": "Status",
|
||||||
|
"network": "Network",
|
||||||
|
"account": "On the account"
|
||||||
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"station": "Station record",
|
"station": "Station record",
|
||||||
"totals": "Charging totals",
|
"totals": "Charging totals",
|
||||||
|
|||||||
@@ -312,6 +312,12 @@
|
|||||||
"relatedBy": "Dostępna przez",
|
"relatedBy": "Dostępna przez",
|
||||||
"timeZone": "Strefa czasowa",
|
"timeZone": "Strefa czasowa",
|
||||||
"linked": "Powiązano",
|
"linked": "Powiązano",
|
||||||
|
"groups": {
|
||||||
|
"device": "Urządzenie",
|
||||||
|
"status": "Status",
|
||||||
|
"network": "Sieć",
|
||||||
|
"account": "Na koncie"
|
||||||
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"station": "Rekord stacji",
|
"station": "Rekord stacji",
|
||||||
"totals": "Sumy ładowania",
|
"totals": "Sumy ładowania",
|
||||||
|
|||||||
@@ -2342,83 +2342,74 @@ class _HomeTabState extends State<_HomeTab> {
|
|||||||
children: [
|
children: [
|
||||||
if (charger != null) ...[
|
if (charger != null) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Container(
|
Row(children: [
|
||||||
padding: const EdgeInsets.all(12),
|
// The product shot the app shows for this model, when the account
|
||||||
decoration: BoxDecoration(
|
// sent one. Decorative: the name beside it says everything the
|
||||||
color: DriverVault.isDark(context) ? DriverVault.darkSunken : DriverVault.ink50,
|
// picture does.
|
||||||
borderRadius: BorderRadius.circular(DriverVault.radiusControl),
|
if ((_liveFor(charger)?.imageUrl ?? "").isNotEmpty) ...[
|
||||||
|
Image.network(_liveFor(charger)!.imageUrl,
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
errorBuilder: (_, __, ___) => const SizedBox.shrink()),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
Expanded(
|
||||||
|
child: Text(charger.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||||
),
|
),
|
||||||
child: Column(
|
// Reachability, said either way. A charger the service says nothing
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// about stays silent: unknown is not offline.
|
||||||
children: [
|
if (_liveFor(charger)?.online == true) ...[
|
||||||
Row(children: [
|
_StatusBadge(label: t("charging.info.online"), ok: true),
|
||||||
// The product shot the app shows for this model, when the
|
const SizedBox(width: 6),
|
||||||
// account sent one. Decorative: the name beside it says
|
] else if (_liveFor(charger)?.online == false) ...[
|
||||||
// everything the picture does.
|
_StatusBadge(label: t("charging.info.offline"), ok: false),
|
||||||
if ((_liveFor(charger)?.imageUrl ?? "").isNotEmpty) ...[
|
const SizedBox(width: 6),
|
||||||
Image.network(_liveFor(charger)!.imageUrl,
|
],
|
||||||
width: 28,
|
if (charger.provider.isNotEmpty)
|
||||||
height: 28,
|
_NeutralBadge(label: _providerLabel(charger.provider)),
|
||||||
fit: BoxFit.contain,
|
]),
|
||||||
errorBuilder: (_, __, ___) => const SizedBox.shrink()),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(width: 8),
|
// A box per group, the way the readings card draws its own: one list
|
||||||
],
|
// of everything the service knows is harder to find a field in than
|
||||||
Expanded(
|
// several short ones under headings.
|
||||||
child: Text(charger.name,
|
for (final (heading, rows) in _infoGroups(charger)) ...[
|
||||||
maxLines: 1,
|
_ReadingSection(heading: heading, child: _PairList(rows: rows, breakLong: true)),
|
||||||
overflow: TextOverflow.ellipsis,
|
const SizedBox(height: 8),
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
],
|
||||||
),
|
// The rest of what the service knows, in the service's own words. It
|
||||||
// Reachability, said either way. A charger the service says
|
// appears only when there is something in it, so a charger the cloud
|
||||||
// nothing about stays silent: unknown is not offline.
|
// says nothing more about stays quiet.
|
||||||
if (_liveFor(charger)?.online == true) ...[
|
if (_attrRows(charger).isNotEmpty) ...[
|
||||||
_StatusBadge(label: t("charging.info.online"), ok: true),
|
_ReadingSection(
|
||||||
const SizedBox(width: 6),
|
heading: t("charging.info.rawTitle"),
|
||||||
] else if (_liveFor(charger)?.online == false) ...[
|
child: Column(
|
||||||
_StatusBadge(label: t("charging.info.offline"), ok: false),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(width: 6),
|
children: [
|
||||||
],
|
|
||||||
if (charger.provider.isNotEmpty)
|
|
||||||
_NeutralBadge(label: _providerLabel(charger.provider)),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PairList(rows: _infoRows(charger), breakLong: true),
|
|
||||||
// The rest of what the service knows, in the service's own
|
|
||||||
// words. It appears only when there is something in it, so a
|
|
||||||
// charger the cloud says nothing more about stays quiet.
|
|
||||||
if (_attrRows(charger).isNotEmpty) ...[
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
t("charging.info.rawTitle"),
|
|
||||||
style: DriverVault.mono(context, size: 10, weight: FontWeight.w500, color: muted)
|
|
||||||
.copyWith(letterSpacing: 1.4),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
_PairList(rows: _attrRows(charger), breakLong: true),
|
_PairList(rows: _attrRows(charger), breakLong: true),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 6),
|
||||||
Text(t("charging.info.rawHint"), style: TextStyle(fontSize: 11, color: muted)),
|
Text(t("charging.info.rawHint"), style: TextStyle(fontSize: 11, color: muted)),
|
||||||
],
|
],
|
||||||
// The views that answer per charger rather than per account.
|
),
|
||||||
// Each says what it knows, or why it could not be read — an
|
|
||||||
// account that is not the charger's owner cannot read the
|
|
||||||
// cards, which is a fact about the account rather than a
|
|
||||||
// failure.
|
|
||||||
for (final view in _detailViews(charger)) ...[
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
t("charging.info.views.${view.id}"),
|
|
||||||
style: DriverVault.mono(context, size: 10, weight: FontWeight.w500, color: muted)
|
|
||||||
.copyWith(letterSpacing: 1.4),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
if (view.error.isNotEmpty)
|
|
||||||
Text(view.error, style: TextStyle(fontSize: 11, color: muted))
|
|
||||||
else
|
|
||||||
_PairList(rows: view.rows, breakLong: true),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
// The views that answer per charger rather than per account. Each says
|
||||||
|
// what it knows, or why it could not be read — an account that is not
|
||||||
|
// the charger's owner cannot read the cards, which is a fact about the
|
||||||
|
// account rather than a failure.
|
||||||
|
for (final view in _detailViews(charger)) ...[
|
||||||
|
_ReadingSection(
|
||||||
|
heading: t("charging.info.views.${view.id}"),
|
||||||
|
child: view.error.isNotEmpty
|
||||||
|
? Text(view.error, style: TextStyle(fontSize: 11, color: muted))
|
||||||
|
: _PairList(rows: view.rows, breakLong: true),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
if (_homeChargers.isEmpty)
|
if (_homeChargers.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
@@ -2437,49 +2428,65 @@ class _HomeTabState extends State<_HomeTab> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Everything the card can say about one charger. Every row is drawn every
|
/// Everything the card can say about one charger, in groups: what the box is,
|
||||||
/// time, a field nothing supplied included: which fields a charger has an
|
/// what it is doing, how it is connected, and how it sits on the account. Each
|
||||||
/// answer for is itself worth seeing, and a row that comes and goes with the
|
/// group gets a box of its own, the way the readings card draws its groups.
|
||||||
/// data makes two chargers impossible to read against each other. Nothing to
|
/// Every row is drawn every time, a field nothing supplied included: which
|
||||||
/// say is said with a dash.
|
/// fields a charger has an answer for is itself worth seeing, and a row that
|
||||||
List<(String, String)> _infoRows(HomeCharger c) {
|
/// comes and goes with the data makes two chargers impossible to read against
|
||||||
|
/// each other. Nothing to say is said with a dash.
|
||||||
|
List<(String, List<(String, String)>)> _infoGroups(HomeCharger c) {
|
||||||
final live = _liveFor(c);
|
final live = _liveFor(c);
|
||||||
final pairs = <(String, String)>[
|
final groups = <(String, List<(String, String)>)>[
|
||||||
("vendor", c.vendor),
|
("device", [
|
||||||
("model", c.model),
|
("vendor", c.vendor),
|
||||||
("firmware", live?.firmware ?? ""),
|
("model", c.model),
|
||||||
("serial", c.serial),
|
("firmware", live?.firmware ?? ""),
|
||||||
("site", c.siteName.isNotEmpty ? c.siteName : (live?.siteName ?? "")),
|
("serial", c.serial),
|
||||||
("siteId", live?.siteId ?? ""),
|
("power", c.powerKw > 0 ? "${c.powerKw} kW" : ""),
|
||||||
("sources", _sourcesLabel(live?.sources ?? const [])),
|
("connector", c.connector),
|
||||||
("power", c.powerKw > 0 ? "${c.powerKw} kW" : ""),
|
]),
|
||||||
("connector", c.connector),
|
("status", [
|
||||||
("state", _stateLabel(live?.status ?? "")),
|
("state", _stateLabel(live?.status ?? "")),
|
||||||
// Relayed as the service words it — the unit is upstream's, so putting one
|
// Relayed as the service words it — the unit is upstream's, so putting
|
||||||
// on it here would be inventing it.
|
// one on it here would be inventing it.
|
||||||
("chargePower", live?.power ?? ""),
|
("chargePower", live?.power ?? ""),
|
||||||
("ocpp", live?.ocppLabel ?? ""),
|
("ocpp", live?.ocppLabel ?? ""),
|
||||||
// The box on the wall, as opposed to the charging: the networks it is on,
|
]),
|
||||||
// where it thinks it is, and when the account first saw it.
|
// The box on the wall, as opposed to the charging: the networks it is on.
|
||||||
("wifiName", live?.wifiName ?? ""),
|
("network", [
|
||||||
("wifiMac", live?.wifiMac ?? ""),
|
("wifiName", live?.wifiName ?? ""),
|
||||||
("signal", live?.wifiRssi == null ? "" : "${live!.wifiRssi} dBm"),
|
("wifiMac", live?.wifiMac ?? ""),
|
||||||
("bleMac", live?.bleMac ?? ""),
|
("signal", live?.wifiRssi == null ? "" : "${live!.wifiRssi} dBm"),
|
||||||
("relatedBy", (live?.relatedBy ?? const []).join(" · ")),
|
("bleMac", live?.bleMac ?? ""),
|
||||||
("timeZone", live?.timeZone ?? ""),
|
("relatedBy", (live?.relatedBy ?? const []).join(" · ")),
|
||||||
(
|
]),
|
||||||
"linked",
|
// Where it thinks it is, and how the account came to know it.
|
||||||
live?.linkedAt == null
|
("account", [
|
||||||
? ""
|
("site", c.siteName.isNotEmpty ? c.siteName : (live?.siteName ?? "")),
|
||||||
: formatDateTime(
|
("siteId", live?.siteId ?? ""),
|
||||||
DateTime.fromMillisecondsSinceEpoch((live!.linkedAt! * 1000).round()))
|
("sources", _sourcesLabel(live?.sources ?? const [])),
|
||||||
),
|
("timeZone", live?.timeZone ?? ""),
|
||||||
("providerId", c.providerChargerId),
|
(
|
||||||
("added", c.created.isEmpty ? "" : formatDateTime(DateTime.tryParse(c.created)?.toLocal())),
|
"linked",
|
||||||
|
live?.linkedAt == null
|
||||||
|
? ""
|
||||||
|
: formatDateTime(
|
||||||
|
DateTime.fromMillisecondsSinceEpoch((live!.linkedAt! * 1000).round()))
|
||||||
|
),
|
||||||
|
("providerId", c.providerChargerId),
|
||||||
|
("added", c.created.isEmpty ? "" : formatDateTime(DateTime.tryParse(c.created)?.toLocal())),
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
return [
|
return [
|
||||||
for (final (key, value) in pairs)
|
for (final (id, pairs) in groups)
|
||||||
(t("charging.info.$key"), value.isEmpty ? "—" : value),
|
(
|
||||||
|
t("charging.info.groups.$id"),
|
||||||
|
[
|
||||||
|
for (final (key, value) in pairs)
|
||||||
|
(t("charging.info.$key"), value.isEmpty ? "—" : value),
|
||||||
|
],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,6 +270,12 @@
|
|||||||
"relatedBy": "Kan nås via",
|
"relatedBy": "Kan nås via",
|
||||||
"timeZone": "Tidszone",
|
"timeZone": "Tidszone",
|
||||||
"linked": "Tilknyttet",
|
"linked": "Tilknyttet",
|
||||||
|
"groups": {
|
||||||
|
"device": "Enhed",
|
||||||
|
"status": "Status",
|
||||||
|
"network": "Netværk",
|
||||||
|
"account": "På kontoen"
|
||||||
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"station": "Stationspost",
|
"station": "Stationspost",
|
||||||
"totals": "Ladetotaler",
|
"totals": "Ladetotaler",
|
||||||
|
|||||||
@@ -256,6 +256,12 @@
|
|||||||
"relatedBy": "Reachable by",
|
"relatedBy": "Reachable by",
|
||||||
"timeZone": "Time zone",
|
"timeZone": "Time zone",
|
||||||
"linked": "Linked",
|
"linked": "Linked",
|
||||||
|
"groups": {
|
||||||
|
"device": "Device",
|
||||||
|
"status": "Status",
|
||||||
|
"network": "Network",
|
||||||
|
"account": "On the account"
|
||||||
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"station": "Station record",
|
"station": "Station record",
|
||||||
"totals": "Charging totals",
|
"totals": "Charging totals",
|
||||||
|
|||||||
@@ -272,6 +272,12 @@
|
|||||||
"relatedBy": "Dostępna przez",
|
"relatedBy": "Dostępna przez",
|
||||||
"timeZone": "Strefa czasowa",
|
"timeZone": "Strefa czasowa",
|
||||||
"linked": "Powiązano",
|
"linked": "Powiązano",
|
||||||
|
"groups": {
|
||||||
|
"device": "Urządzenie",
|
||||||
|
"status": "Status",
|
||||||
|
"network": "Sieć",
|
||||||
|
"account": "Na koncie"
|
||||||
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"station": "Rekord stacji",
|
"station": "Rekord stacji",
|
||||||
"totals": "Sumy ładowania",
|
"totals": "Sumy ładowania",
|
||||||
|
|||||||
@@ -868,44 +868,59 @@ function homeChargerSubtitle(c) {
|
|||||||
return [c.serial, c.model, c.siteName].filter(Boolean).join(" · ");
|
return [c.serial, c.model, c.siteName].filter(Boolean).join(" · ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything the card can say about one charger, as label/value rows. Every row
|
// Everything the card can say about one charger, in groups: what the box is,
|
||||||
// is drawn every time, a field nothing supplied included: which fields a charger
|
// what it is doing, how it is connected, and how it sits on the account. Four
|
||||||
// has an answer for is itself worth seeing, and a row that comes and goes with
|
// short lists under headings, one box each the way the readings card draws its
|
||||||
// the data makes two chargers side by side impossible to read against each
|
// own, beat one long list nobody can find a field in. Every row is drawn every
|
||||||
// other. Nothing to say is said with a dash.
|
// time, a field nothing supplied included: which fields a charger has an answer
|
||||||
function chargerInfoRows(c) {
|
// for is itself worth seeing, and a row that comes and goes with the data makes
|
||||||
|
// two chargers side by side impossible to read against each other. Nothing to
|
||||||
|
// say is said with a dash.
|
||||||
|
function chargerInfoGroups(c) {
|
||||||
const live = liveFor(c) || {};
|
const live = liveFor(c) || {};
|
||||||
const rows = [
|
const groups = [
|
||||||
["vendor", c.vendor],
|
["device", [
|
||||||
["model", c.model],
|
["vendor", c.vendor],
|
||||||
["firmware", live.firmware],
|
["model", c.model],
|
||||||
["serial", c.serial],
|
["firmware", live.firmware],
|
||||||
["site", c.siteName || live.siteName],
|
["serial", c.serial],
|
||||||
["siteId", live.siteId],
|
["power", c.powerKw ? `${c.powerKw} kW` : ""],
|
||||||
["sources", sourcesLabel(live.sources)],
|
["connector", c.connector],
|
||||||
["power", c.powerKw ? `${c.powerKw} kW` : ""],
|
]],
|
||||||
["connector", c.connector],
|
["status", [
|
||||||
["state", chargerStateLabel(live.status)],
|
["state", chargerStateLabel(live.status)],
|
||||||
// Relayed as the service words it — the unit is upstream's, so putting one
|
// Relayed as the service words it — the unit is upstream's, so putting one
|
||||||
// on it here would be inventing it.
|
// on it here would be inventing it.
|
||||||
["chargePower", live.power],
|
["chargePower", live.power],
|
||||||
["ocpp", ocppStatusLabel(live)],
|
["ocpp", ocppStatusLabel(live)],
|
||||||
// The box on the wall, as opposed to the charging: the networks it is on,
|
]],
|
||||||
// where it thinks it is, and when the account first saw it.
|
// The box on the wall, as opposed to the charging: the networks it is on.
|
||||||
["wifiName", live.wifiName],
|
["network", [
|
||||||
["wifiMac", live.wifiMac],
|
["wifiName", live.wifiName],
|
||||||
["signal", isSet(live.wifiRssi) ? `${live.wifiRssi} dBm` : ""],
|
["wifiMac", live.wifiMac],
|
||||||
["bleMac", live.bleMac],
|
["signal", isSet(live.wifiRssi) ? `${live.wifiRssi} dBm` : ""],
|
||||||
["relatedBy", (live.relatedBy || []).join(" · ")],
|
["bleMac", live.bleMac],
|
||||||
["timeZone", live.timeZone],
|
["relatedBy", (live.relatedBy || []).join(" · ")],
|
||||||
["linked", live.linkedAt ? formatDateTime(new Date(live.linkedAt * 1000)) : ""],
|
]],
|
||||||
["providerId", c.providerChargerId],
|
// Where it thinks it is, and how the account came to know it.
|
||||||
["added", c.created ? formatDateTime(c.created) : ""],
|
["account", [
|
||||||
|
["site", c.siteName || live.siteName],
|
||||||
|
["siteId", live.siteId],
|
||||||
|
["sources", sourcesLabel(live.sources)],
|
||||||
|
["timeZone", live.timeZone],
|
||||||
|
["linked", live.linkedAt ? formatDateTime(new Date(live.linkedAt * 1000)) : ""],
|
||||||
|
["providerId", c.providerChargerId],
|
||||||
|
["added", c.created ? formatDateTime(c.created) : ""],
|
||||||
|
]],
|
||||||
];
|
];
|
||||||
return rows.map(([key, value]) => ({
|
return groups.map(([id, rows]) => ({
|
||||||
key,
|
id,
|
||||||
label: t(`charging.info.${key}`),
|
label: t(`charging.info.groups.${id}`),
|
||||||
value: value === "" || value == null ? "—" : value,
|
rows: rows.map(([key, value]) => ({
|
||||||
|
key,
|
||||||
|
label: t(`charging.info.${key}`),
|
||||||
|
value: value === "" || value == null ? "—" : value,
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1865,8 +1880,11 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="isOpen('info')">
|
<div v-show="isOpen('info')">
|
||||||
<!-- The charger picked in the list beside this card, and only it. -->
|
<!-- The charger picked in the list beside this card, and only it. Its
|
||||||
<div v-if="selectedHomeCharger" class="mt-3 rounded-control bg-sunken p-3">
|
fields are split into a box each, the way the readings card draws
|
||||||
|
its groups: one list of everything the service knows is harder to
|
||||||
|
find a field in than several short ones under headings. -->
|
||||||
|
<div v-if="selectedHomeCharger" class="mt-3 flex flex-col gap-2">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<div class="flex min-w-0 items-center gap-2">
|
<div class="flex min-w-0 items-center gap-2">
|
||||||
<!-- The product shot the app shows for this model, when the
|
<!-- The product shot the app shows for this model, when the
|
||||||
@@ -1894,41 +1912,48 @@ onMounted(async () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dl class="mt-2 grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
|
<section
|
||||||
<template v-for="row in chargerInfoRows(selectedHomeCharger)" :key="row.key">
|
v-for="group in chargerInfoGroups(selectedHomeCharger)"
|
||||||
<dt class="text-[11px] text-muted">{{ row.label }}</dt>
|
:key="group.id"
|
||||||
<dd class="data break-all text-[11px] text-body">{{ row.value }}</dd>
|
class="rounded-control bg-sunken p-3"
|
||||||
</template>
|
>
|
||||||
</dl>
|
<h4 class="eyebrow">{{ group.label }}</h4>
|
||||||
|
<dl class="mt-2 grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
|
||||||
|
<template v-for="row in group.rows" :key="row.key">
|
||||||
|
<dt class="text-[11px] text-muted">{{ row.label }}</dt>
|
||||||
|
<dd class="data break-all text-[11px] text-body">{{ row.value }}</dd>
|
||||||
|
</template>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- The rest of what the service knows, in the service's own words.
|
<!-- The rest of what the service knows, in the service's own words.
|
||||||
It only appears when there is something in it, which is also how
|
It only appears when there is something in it, which is also how
|
||||||
a charger the cloud says nothing more about stays quiet. -->
|
a charger the cloud says nothing more about stays quiet. -->
|
||||||
<template v-if="chargerAttrRows(selectedHomeCharger).length">
|
<section v-if="chargerAttrRows(selectedHomeCharger).length" class="rounded-control bg-sunken p-3">
|
||||||
<p class="eyebrow mt-3">{{ t("charging.info.rawTitle") }}</p>
|
<h4 class="eyebrow">{{ t("charging.info.rawTitle") }}</h4>
|
||||||
<dl class="mt-1 grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
|
<dl class="mt-2 grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
|
||||||
<template v-for="row in chargerAttrRows(selectedHomeCharger)" :key="row.key">
|
<template v-for="row in chargerAttrRows(selectedHomeCharger)" :key="row.key">
|
||||||
<dt class="data break-all text-[11px] text-muted">{{ row.key }}</dt>
|
<dt class="data break-all text-[11px] text-muted">{{ row.key }}</dt>
|
||||||
<dd class="data break-all text-[11px] text-body">{{ row.value }}</dd>
|
<dd class="data break-all text-[11px] text-body">{{ row.value }}</dd>
|
||||||
</template>
|
</template>
|
||||||
</dl>
|
</dl>
|
||||||
<p class="mt-1 text-[11px] text-muted">{{ t("charging.info.rawHint") }}</p>
|
<p class="mt-2 text-[11px] text-muted">{{ t("charging.info.rawHint") }}</p>
|
||||||
</template>
|
</section>
|
||||||
|
|
||||||
<!-- The views that answer per charger rather than per account. Each
|
<!-- The views that answer per charger rather than per account. Each
|
||||||
says what it knows, or why it could not be read — an account
|
says what it knows, or why it could not be read — an account
|
||||||
that is not the charger's owner cannot read the cards, which is
|
that is not the charger's owner cannot read the cards, which is
|
||||||
a fact about the account rather than a failure. -->
|
a fact about the account rather than a failure. -->
|
||||||
<template v-for="view in chargerDetailViews" :key="view.id">
|
<section v-for="view in chargerDetailViews" :key="view.id" class="rounded-control bg-sunken p-3">
|
||||||
<p class="eyebrow mt-3">{{ t(`charging.info.views.${view.id}`) }}</p>
|
<h4 class="eyebrow">{{ t(`charging.info.views.${view.id}`) }}</h4>
|
||||||
<p v-if="view.error" class="mt-1 text-[11px] text-muted">{{ view.error }}</p>
|
<p v-if="view.error" class="mt-2 text-[11px] text-muted">{{ view.error }}</p>
|
||||||
<dl v-else class="mt-1 grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
|
<dl v-else class="mt-2 grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
|
||||||
<template v-for="row in view.rows" :key="row.key">
|
<template v-for="row in view.rows" :key="row.key">
|
||||||
<dt class="data break-all text-[11px] text-muted">{{ row.key }}</dt>
|
<dt class="data break-all text-[11px] text-muted">{{ row.key }}</dt>
|
||||||
<dd class="data break-all text-[11px] text-body">{{ row.value }}</dd>
|
<dd class="data break-all text-[11px] text-body">{{ row.value }}</dd>
|
||||||
</template>
|
</template>
|
||||||
</dl>
|
</dl>
|
||||||
</template>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="homeChargers.length === 0" class="mt-2 text-xs text-muted">
|
<p v-if="homeChargers.length === 0" class="mt-2 text-xs text-muted">
|
||||||
|
|||||||
Reference in new issue
Block a user