Split Web App Settings into tabs; allow unset plugin Control mode
Web App: Settings is now two tabs — Personal settings (account, appearance, profile, privacy, advanced/danger) and Integrations (Toyota, Anker Solix). Panels use v-show so loaded state and in-flight edits survive a tab switch. Adds settings.tabs.* labels (en/pl/da). API Server panel: non-required select config fields now render a leading "Not set" option, so a superadmin can leave the Anker Solix Control mode (and Toyota Brand) unset at the global layer. Previously every option was a real value, forcing a pick that won the cascade and locked organizations and users out of choosing their own mode. The server side already treated an empty value as "abstain"; this closes the UI gap. Adds plugins.notSet label (en/pl/da) and rebuilds the embedded panel dist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
318e9c1670
commit
8656aaaee3
@@ -134,6 +134,10 @@
|
||||
"eyebrow": "Konto",
|
||||
"title": "Indstillinger",
|
||||
"subtitle": "Administrer din konto, udseende og dine data.",
|
||||
"tabs": {
|
||||
"personal": "Personlige indstillinger",
|
||||
"integrations": "Integrationer"
|
||||
},
|
||||
|
||||
"account": {
|
||||
"title": "Konto",
|
||||
|
||||
@@ -152,6 +152,10 @@
|
||||
"eyebrow": "Account",
|
||||
"title": "Settings",
|
||||
"subtitle": "Manage your account, appearance, and data.",
|
||||
"tabs": {
|
||||
"personal": "Personal settings",
|
||||
"integrations": "Integrations"
|
||||
},
|
||||
|
||||
"account": {
|
||||
"title": "Account",
|
||||
|
||||
@@ -138,6 +138,10 @@
|
||||
"eyebrow": "Konto",
|
||||
"title": "Ustawienia",
|
||||
"subtitle": "Zarządzaj kontem, wyglądem i danymi.",
|
||||
"tabs": {
|
||||
"personal": "Ustawienia osobiste",
|
||||
"integrations": "Integracje"
|
||||
},
|
||||
|
||||
"account": {
|
||||
"title": "Konto",
|
||||
|
||||
@@ -13,6 +13,11 @@ const loading = ref(true);
|
||||
const loadError = ref("");
|
||||
const profile = ref(null);
|
||||
|
||||
// Settings is split into two tabs: personal account settings and external
|
||||
// integrations. The panels stay mounted (v-show) so their loaded state and
|
||||
// in-flight edits survive a tab switch.
|
||||
const activeTab = ref("personal"); // "personal" | "integrations"
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
loadError.value = "";
|
||||
@@ -722,7 +727,24 @@ onBeforeUnmount(() => {
|
||||
<p v-if="loadError" class="mb-4 rounded-control bg-danger-soft px-4 py-3 text-sm font-medium text-danger">{{ loadError }}</p>
|
||||
<p v-if="loading" class="text-muted">{{ t("common.loading") }}</p>
|
||||
|
||||
<div v-else-if="profile" class="space-y-6">
|
||||
<div v-else-if="profile">
|
||||
<!-- Tabs: personal settings vs. integrations -->
|
||||
<div class="mb-6 flex gap-2 border-b border-subtle">
|
||||
<button
|
||||
v-for="tab in ['personal', 'integrations']"
|
||||
:key="tab"
|
||||
class="-mb-px border-b-2 px-1 pb-3 text-sm font-semibold transition-colors"
|
||||
:class="activeTab === tab
|
||||
? 'border-accent text-strong'
|
||||
: 'border-transparent text-muted hover:text-body'"
|
||||
@click="activeTab = tab"
|
||||
>
|
||||
{{ t(`settings.tabs.${tab}`) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Personal settings -->
|
||||
<div v-show="activeTab === 'personal'" class="space-y-6">
|
||||
<!-- Account -->
|
||||
<section class="dh-card p-6">
|
||||
<h2 class="mb-4 text-lg font-bold tracking-[-0.02em] text-strong">{{ t("settings.account.title") }}</h2>
|
||||
@@ -891,7 +913,10 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Integrations -->
|
||||
<div v-show="activeTab === 'integrations'" class="space-y-6">
|
||||
<section v-if="toyota || anker" class="dh-card p-6">
|
||||
<h2 class="text-lg font-bold tracking-[-0.02em] text-strong">{{ t("settings.integrations.title") }}</h2>
|
||||
<p class="mb-4 mt-1 text-sm text-muted">{{ t("settings.integrations.subtitle") }}</p>
|
||||
@@ -1197,7 +1222,10 @@ onBeforeUnmount(() => {
|
||||
<p v-if="ankerError" class="mt-2 text-sm text-danger">{{ ankerError }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Personal settings (continued) -->
|
||||
<div v-show="activeTab === 'personal'" class="space-y-6">
|
||||
<!-- Privacy & Security -->
|
||||
<section class="dh-card p-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
@@ -1279,6 +1307,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<p v-if="deleteError" class="mt-3 text-sm font-medium text-danger">{{ deleteError }}</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user