Controls first, because that is what the page is opened for
The column opened with the charger picker and its address — the two things touched once and then never again — and put the start button below them. Order reversed: control, then connection, then readings, then information. The comments on both cards described the old order, so they move with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b48019b08f
commit
2a5d21d328
+104
-101
@@ -762,6 +762,110 @@ onMounted(async () => {
|
||||
<div v-show="chargerTab === 'home'" class="grid gap-6 lg:grid-cols-[1fr_360px] lg:items-start">
|
||||
<!-- Real OCPP control (Own/Proxy CSMS) — the real home-charger control -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Acting on the charger — first, because it is what the page is
|
||||
opened for. It appears only once there is a connection to act
|
||||
over, which the card below is where you set up. -->
|
||||
<div v-if="ctlActive && ctlConnected" class="dh-card p-4">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between gap-3 text-left"
|
||||
:aria-expanded="isOpen('control')"
|
||||
@click="toggleCard('control')"
|
||||
>
|
||||
<p class="text-sm font-semibold text-strong">{{ t("charging.control.title") }}</p>
|
||||
<svg
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
class="h-4 w-4 shrink-0 text-muted transition-transform" :class="isOpen('control') ? '' : '-rotate-90'"
|
||||
><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
|
||||
<div v-show="isOpen('control')">
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Which charger, and how to reach it. Below the controls: it is
|
||||
touched once and then left alone. -->
|
||||
<div v-if="ctlActive" class="dh-card p-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -862,107 +966,6 @@ onMounted(async () => {
|
||||
</div>
|
||||
</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">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between gap-3 text-left"
|
||||
:aria-expanded="isOpen('control')"
|
||||
@click="toggleCard('control')"
|
||||
>
|
||||
<p class="text-sm font-semibold text-strong">{{ t("charging.control.title") }}</p>
|
||||
<svg
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
class="h-4 w-4 shrink-0 text-muted transition-transform" :class="isOpen('control') ? '' : '-rotate-90'"
|
||||
><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
|
||||
<div v-show="isOpen('control')">
|
||||
|
||||
<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>
|
||||
</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. -->
|
||||
|
||||
Reference in New Issue
Block a user