Setting up the connection is not the same as using it

The control card opened with the parts that get touched once — which charger,
and where it lives on the network — and only then reached what gets used every
day. Split in two: a connection card holding the picker, the address and the
connected badge, then a control card holding the tiles and the buttons.

The not-connected hint and the error line move up with the connection, since
that is what they are about, and the control card simply does not appear until
there is a connection to control over. That also retires the inner template
that repeated the card's own condition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-09-01 20:12:05 +02:00
co-authored by Claude Opus 5
parent 5547daa689
commit bcc44da735
4 changed files with 95 additions and 86 deletions
+1
View File
@@ -78,6 +78,7 @@
},
"control": {
"connectionTitle": "Laderforbindelse",
"title": "Laderstyring",
"connected": "Forbundet",
"disconnected": "Offline",
+1
View File
@@ -64,6 +64,7 @@
"idleHint": "Plug in at a station to start a session."
},
"control": {
"connectionTitle": "Charger connection",
"title": "Charger control",
"connected": "Connected",
"disconnected": "Offline",
+1
View File
@@ -80,6 +80,7 @@
},
"control": {
"connectionTitle": "Połączenie z ładowarką",
"title": "Sterowanie ładowarką",
"connected": "Połączono",
"disconnected": "Offline",
+92 -86
View File
@@ -729,7 +729,7 @@ onMounted(async () => {
<div class="flex flex-col gap-4">
<div v-if="ctlActive" class="dh-card p-4">
<div class="flex items-center justify-between">
<p class="text-sm font-semibold text-strong">{{ t("charging.control.title") }}</p>
<p class="text-sm font-semibold text-strong">{{ t("charging.control.connectionTitle") }}</p>
<span class="dh-badge" :class="ctlConnected ? 'dh-badge-success' : 'dh-badge-warning'">
{{ ctlConnected ? t("charging.control.connected") : t("charging.control.disconnected") }}
</span>
@@ -802,96 +802,102 @@ onMounted(async () => {
</div>
</div>
<template v-if="ctlConnected">
<div class="mt-3 grid grid-cols-2 gap-2">
<div class="rounded-control bg-sunken px-3 py-2">
<div class="data text-sm font-semibold text-strong">{{ ctlStatusLabel }}</div>
<div class="text-[11px] text-muted">{{ t("charging.control.status") }}</div>
</div>
<div class="rounded-control bg-sunken px-3 py-2">
<div class="data text-sm font-semibold text-strong">{{ ctlMeterKwh }} kWh</div>
<div class="text-[11px] text-muted">{{ t("charging.control.meter") }}</div>
</div>
</div>
<div class="mt-3 flex gap-2">
<button class="dh-btn dh-btn-primary grow" :disabled="ctlBusy === 'start'" @click="doAction('start')">
{{ t("charging.control.start") }}
</button>
<button class="dh-btn dh-btn-ghost grow" :disabled="ctlBusy === 'stop'" @click="doAction('stop')">
{{ t("charging.control.stop") }}
</button>
</div>
<div class="mt-3">
<label class="dh-label flex justify-between">
<span>{{ t("charging.control.limit") }}</span><span class="data text-body">{{ limitAmps }} A</span>
</label>
<input v-model.number="limitAmps" type="range" min="6" max="32" step="1" class="w-full accent-[var(--accent)]" />
<div class="mt-2 flex gap-2">
<button class="dh-btn dh-btn-ghost grow" :disabled="ctlBusy === 'limit'" @click="doAction('limit', { amps: limitAmps })">
{{ t("charging.control.applyLimit") }}
</button>
<!-- Clearing a limit is an OCPP command; the register map takes an
explicit ceiling, so in Modbus mode there is nothing to clear to. -->
<button
v-if="!ctlIsModbus"
class="dh-btn dh-btn-ghost grow"
:disabled="ctlBusy === 'clear-limit'"
@click="doAction('clear-limit')"
>
{{ t("charging.control.clearLimit") }}
</button>
</div>
</div>
<!-- Boost is a Modbus command, and lasts for the current session only. -->
<button
v-if="ctlIsModbus"
class="dh-btn dh-btn-ghost mt-3 w-full"
:disabled="ctlBusy === 'boost'"
@click="doAction('boost', { on: true })"
>
{{ t("charging.control.boost") }}
</button>
<!-- Reset reboots the charger over OCPP; the register map has no
equivalent, so the button is not offered on the local path. -->
<button
v-if="!ctlIsModbus && !resetPrompt"
class="dh-btn dh-btn-ghost mt-3 w-full"
:disabled="ctlBusy === 'reset'"
@click="askReset"
>
{{ t("charging.control.reset") }}
</button>
<!-- Step-up: destructive reset requires re-entering the password. -->
<div v-else-if="resetPrompt" class="mt-3 rounded-control border border-danger/40 bg-danger-soft p-3">
<p class="text-xs font-medium text-danger">{{ t("charging.control.resetConfirm") }}</p>
<input
v-model="resetPassword"
type="password"
autocomplete="current-password"
class="dh-input mt-2"
:placeholder="t('charging.control.resetPassword')"
@keyup.enter="confirmReset"
/>
<div class="mt-2 flex gap-2">
<button class="dh-btn dh-btn-ghost grow" @click="cancelReset">{{ t("common.cancel") }}</button>
<button class="dh-btn dh-btn-danger grow" :disabled="!resetPassword || ctlBusy === 'reset'" @click="confirmReset">
{{ t("charging.control.reset") }}
</button>
</div>
</div>
</template>
<!-- Not connected. In Modbus mode the server has already tried to reach
the charger and says what it found, which beats a generic hint. -->
<p v-else class="mt-3 text-xs text-muted">
<!-- Why there is nothing to control yet. In Modbus mode the server has
already tried to reach the charger and says what it found, which
beats a generic hint. -->
<p v-if="!ctlConnected" class="mt-3 text-xs text-muted">
{{ ctl?.detail || t(ctlIsModbus ? "charging.control.modbusHint" : "charging.control.connectHint") }}
</p>
<p v-if="ctlError" class="mt-2 text-sm text-danger">{{ ctlError }}</p>
</div>
<!-- Acting on the charger, once there is a connection to act over. The
card above is where that connection is set up. -->
<div v-if="ctlActive && ctlConnected" class="dh-card p-4">
<p class="text-sm font-semibold text-strong">{{ t("charging.control.title") }}</p>
<div class="mt-3 grid grid-cols-2 gap-2">
<div class="rounded-control bg-sunken px-3 py-2">
<div class="data text-sm font-semibold text-strong">{{ ctlStatusLabel }}</div>
<div class="text-[11px] text-muted">{{ t("charging.control.status") }}</div>
</div>
<div class="rounded-control bg-sunken px-3 py-2">
<div class="data text-sm font-semibold text-strong">{{ ctlMeterKwh }} kWh</div>
<div class="text-[11px] text-muted">{{ t("charging.control.meter") }}</div>
</div>
</div>
<div class="mt-3 flex gap-2">
<button class="dh-btn dh-btn-primary grow" :disabled="ctlBusy === 'start'" @click="doAction('start')">
{{ t("charging.control.start") }}
</button>
<button class="dh-btn dh-btn-ghost grow" :disabled="ctlBusy === 'stop'" @click="doAction('stop')">
{{ t("charging.control.stop") }}
</button>
</div>
<div class="mt-3">
<label class="dh-label flex justify-between">
<span>{{ t("charging.control.limit") }}</span><span class="data text-body">{{ limitAmps }} A</span>
</label>
<input v-model.number="limitAmps" type="range" min="6" max="32" step="1" class="w-full accent-[var(--accent)]" />
<div class="mt-2 flex gap-2">
<button class="dh-btn dh-btn-ghost grow" :disabled="ctlBusy === 'limit'" @click="doAction('limit', { amps: limitAmps })">
{{ t("charging.control.applyLimit") }}
</button>
<!-- Clearing a limit is an OCPP command; the register map takes an
explicit ceiling, so in Modbus mode there is nothing to clear to. -->
<button
v-if="!ctlIsModbus"
class="dh-btn dh-btn-ghost grow"
:disabled="ctlBusy === 'clear-limit'"
@click="doAction('clear-limit')"
>
{{ t("charging.control.clearLimit") }}
</button>
</div>
</div>
<!-- Boost is a Modbus command, and lasts for the current session only. -->
<button
v-if="ctlIsModbus"
class="dh-btn dh-btn-ghost mt-3 w-full"
:disabled="ctlBusy === 'boost'"
@click="doAction('boost', { on: true })"
>
{{ t("charging.control.boost") }}
</button>
<!-- Reset reboots the charger over OCPP; the register map has no
equivalent, so the button is not offered on the local path. -->
<button
v-if="!ctlIsModbus && !resetPrompt"
class="dh-btn dh-btn-ghost mt-3 w-full"
:disabled="ctlBusy === 'reset'"
@click="askReset"
>
{{ t("charging.control.reset") }}
</button>
<!-- Step-up: destructive reset requires re-entering the password. -->
<div v-else-if="resetPrompt" class="mt-3 rounded-control border border-danger/40 bg-danger-soft p-3">
<p class="text-xs font-medium text-danger">{{ t("charging.control.resetConfirm") }}</p>
<input
v-model="resetPassword"
type="password"
autocomplete="current-password"
class="dh-input mt-2"
:placeholder="t('charging.control.resetPassword')"
@keyup.enter="confirmReset"
/>
<div class="mt-2 flex gap-2">
<button class="dh-btn dh-btn-ghost grow" @click="cancelReset">{{ t("common.cancel") }}</button>
<button class="dh-btn dh-btn-danger grow" :disabled="!resetPassword || ctlBusy === 'reset'" @click="confirmReset">
{{ t("charging.control.reset") }}
</button>
</div>
</div>
</div>
<!-- What the charger reports over Modbus. Its own card rather than a
tail on the control one: control is for acting on the charger, and
this is a long read that pushed the buttons off the screen. -->