Integrations, sorted into the categories the panel already sorts them into
The API Server panel has grouped plugins by category since the plugin list was drawn — car manufacturers, EV chargers, notifications, each tab carrying its count. The Settings page had the same integrations in one flat stack, so the two screens described the same set of things in two different shapes. Now they agree: the same categories, the same counted tabs, the same accent border on the active one. The category ids come from the Category* constants both sides already read, so nothing here invents a third vocabulary. Only a category holding something gets a tab, which is the panel's rule too: two tabs today rather than six empty ones. Below one category the bar hides itself entirely — with nothing to switch between, a single tab is a label pretending to be a control. The active group falls back to the first rather than to a fixed id, because the three integration views load independently and a tab can be momentarily empty on first paint; falling back keeps the section populated instead of blanking it. The cards moved into a space-y-4 wrapper and lost their per-card mt-4, so the first card sits the same distance under the bar whichever tab is showing — previously Toyota was the only one that could ever be first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a3f69fa5ef
commit
06f91578da
@@ -251,6 +251,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"integrations": {
|
"integrations": {
|
||||||
|
"categories": { "vehicles": "Bilproducenter", "chargers": "EV-ladere" },
|
||||||
"title": "Integrationer",
|
"title": "Integrationer",
|
||||||
"subtitle": "Forbind eksterne tjenester til din konto.",
|
"subtitle": "Forbind eksterne tjenester til din konto.",
|
||||||
"toyota": "Toyota Connected (MyToyota)",
|
"toyota": "Toyota Connected (MyToyota)",
|
||||||
|
|||||||
@@ -250,6 +250,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"integrations": {
|
"integrations": {
|
||||||
|
"categories": { "vehicles": "Car manufacturers", "chargers": "EV chargers" },
|
||||||
"title": "Integrations",
|
"title": "Integrations",
|
||||||
"subtitle": "Connect external services to your account.",
|
"subtitle": "Connect external services to your account.",
|
||||||
"toyota": "Toyota Connected (MyToyota)",
|
"toyota": "Toyota Connected (MyToyota)",
|
||||||
|
|||||||
@@ -255,6 +255,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"integrations": {
|
"integrations": {
|
||||||
|
"categories": { "vehicles": "Producenci samochodów", "chargers": "Ładowarki EV" },
|
||||||
"title": "Integracje",
|
"title": "Integracje",
|
||||||
"subtitle": "Połącz zewnętrzne usługi ze swoim kontem.",
|
"subtitle": "Połącz zewnętrzne usługi ze swoim kontem.",
|
||||||
"toyota": "Toyota Connected (MyToyota)",
|
"toyota": "Toyota Connected (MyToyota)",
|
||||||
|
|||||||
@@ -424,6 +424,39 @@ async function testToyotaConnection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Integrations: category tabs ---
|
||||||
|
//
|
||||||
|
// The API Server panel groups plugins into the same categories (see
|
||||||
|
// panel/src/components/PluginsCard.vue, and the Category* constants in
|
||||||
|
// internal/plugins/plugin.go that both read from), so grouping them the same way
|
||||||
|
// here means someone who has seen one list can read the other. Only a category
|
||||||
|
// that actually has an integration gets a tab — two tabs today, not six empty
|
||||||
|
// ones — and a category whose integrations are all still loading has none, which
|
||||||
|
// is why the active group falls back to the first rather than to a fixed id.
|
||||||
|
const INTEGRATION_CATEGORIES = ["vehicles", "chargers"];
|
||||||
|
|
||||||
|
const integrationsByCategory = computed(() => ({
|
||||||
|
vehicles: [toyota.value && "toyota"].filter(Boolean),
|
||||||
|
chargers: [anker.value && "anker", greencell.value && "greencell"].filter(Boolean),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const integrationGroups = computed(() =>
|
||||||
|
INTEGRATION_CATEGORIES.filter((c) => integrationsByCategory.value[c].length).map((c) => ({
|
||||||
|
id: c,
|
||||||
|
label: t(`settings.integrations.categories.${c}`),
|
||||||
|
ids: integrationsByCategory.value[c],
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const integrationTab = ref("");
|
||||||
|
const activeIntegrationGroup = computed(
|
||||||
|
() => integrationGroups.value.find((g) => g.id === integrationTab.value) || integrationGroups.value[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
function showsIntegration(id) {
|
||||||
|
return !!activeIntegrationGroup.value?.ids.includes(id);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Integrations: Anker Solix (V1 Smart EV Charger) ---
|
// --- Integrations: Anker Solix (V1 Smart EV Charger) ---
|
||||||
//
|
//
|
||||||
// Same superadmin → org admin → user cascade as Toyota above; the fields are the
|
// Same superadmin → org admin → user cascade as Toyota above; the fields are the
|
||||||
@@ -1211,7 +1244,23 @@ onBeforeUnmount(() => {
|
|||||||
<h2 class="text-lg font-bold tracking-[-0.02em] text-strong">{{ t("settings.integrations.title") }}</h2>
|
<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>
|
<p class="mb-4 mt-1 text-sm text-muted">{{ t("settings.integrations.subtitle") }}</p>
|
||||||
|
|
||||||
<div v-if="toyota" class="rounded-control border border-subtle p-4">
|
<!-- Category tabs, mirroring the API Server panel's plugin list. -->
|
||||||
|
<nav v-if="integrationGroups.length > 1" class="mb-4 flex flex-wrap gap-1.5 border-b border-subtle pb-3">
|
||||||
|
<button
|
||||||
|
v-for="g in integrationGroups"
|
||||||
|
:key="g.id"
|
||||||
|
type="button"
|
||||||
|
class="dh-btn dh-btn-ghost !px-3 !py-1.5 text-sm"
|
||||||
|
:class="activeIntegrationGroup?.id === g.id ? 'border-accent text-brandtext' : ''"
|
||||||
|
@click="integrationTab = g.id"
|
||||||
|
>
|
||||||
|
{{ g.label }}
|
||||||
|
<span class="dh-badge dh-badge-neutral ml-2">{{ g.ids.length }}</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div v-if="toyota && showsIntegration('toyota')" class="rounded-control border border-subtle p-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full items-start justify-between gap-3 text-left"
|
class="flex w-full items-start justify-between gap-3 text-left"
|
||||||
@@ -1341,7 +1390,7 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Anker Solix (V1 Smart EV Charger) -->
|
<!-- Anker Solix (V1 Smart EV Charger) -->
|
||||||
<div v-if="anker" class="mt-4 rounded-control border border-subtle p-4">
|
<div v-if="anker && showsIntegration('anker')" class="rounded-control border border-subtle p-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full items-start justify-between gap-3 text-left"
|
class="flex w-full items-start justify-between gap-3 text-left"
|
||||||
@@ -1588,7 +1637,7 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Greencell (HabuDen EV charger, read over your own MQTT broker) -->
|
<!-- Greencell (HabuDen EV charger, read over your own MQTT broker) -->
|
||||||
<div v-if="greencell" class="mt-4 rounded-control border border-subtle p-4">
|
<div v-if="greencell && showsIntegration('greencell')" class="rounded-control border border-subtle p-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full items-start justify-between gap-3 text-left"
|
class="flex w-full items-start justify-between gap-3 text-left"
|
||||||
@@ -1792,6 +1841,7 @@ onBeforeUnmount(() => {
|
|||||||
<p v-if="greencellError" class="mt-2 text-sm text-danger">{{ greencellError }}</p>
|
<p v-if="greencellError" class="mt-2 text-sm text-danger">{{ greencellError }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user