feat(admin-web): show brand quota alongside question quota on brands page
Frontend CI / Frontend (push) Successful in 3m44s
Backend CI / Backend (push) Successful in 16m18s

Display used/max brand count next to the existing question quota in the
brands page header, laid out as a two-column grid that collapses to full
width on narrow viewports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-24 09:25:51 +08:00
parent 348f2e3451
commit 2133295ce2
+39 -7
View File
@@ -131,12 +131,19 @@ const orderedBrands = computed(() => {
})
const canSortBrands = computed(() => brandList.value.length > 1)
const hasBrandOrderChanges = computed(
() => !isSameBrandOrder(draftBrandIds.value, brandList.value.map((brand) => brand.id)),
() =>
!isSameBrandOrder(
draftBrandIds.value,
brandList.value.map((brand) => brand.id),
),
)
const currentQuestions = computed(() => questionsQuery.data.value?.items ?? [])
const questionTotal = computed(() => questionsQuery.data.value?.total ?? 0)
const maxBrands = computed(() => brandLibrarySummary.value?.max_brands ?? 0)
const usedBrands = computed(() => brandLibrarySummary.value?.used_brands ?? brandList.value.length)
const maxQuestions = computed(
() =>
brandLibrarySummary.value?.max_questions ??
@@ -638,9 +645,15 @@ async function invalidateBrandQueries(): Promise<void> {
<p class="eyebrow">{{ t('brands.eyebrow') }}</p>
<h2>{{ t('brands.title') }}</h2>
</div>
<div class="brand-page-head__quota">
<span>{{ t('brands.quota.questions') }}</span>
<strong>{{ usedQuestions }} / {{ maxQuestions || '--' }}</strong>
<div class="brand-page-head__quotas">
<div class="brand-page-head__quota">
<span>{{ t('brands.quota.brands') }}</span>
<strong>{{ usedBrands }} / {{ maxBrands || '--' }}</strong>
</div>
<div class="brand-page-head__quota">
<span>{{ t('brands.quota.questions') }}</span>
<strong>{{ usedQuestions }} / {{ maxQuestions || '--' }}</strong>
</div>
</div>
</section>
@@ -1020,11 +1033,21 @@ async function invalidateBrandQueries(): Promise<void> {
font-weight: 760;
}
.brand-page-head__quotas {
display: grid;
grid-template-columns: repeat(2, minmax(150px, 1fr));
align-items: stretch;
}
.brand-page-head__quota {
min-width: 132px;
padding-left: 18px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 0;
padding: 0 24px;
border-left: 1px solid #e2e8f0;
text-align: right;
text-align: center;
}
.brand-page-head__quota span {
@@ -1335,6 +1358,15 @@ async function invalidateBrandQueries(): Promise<void> {
justify-content: flex-start;
}
.brand-page-head__quotas {
width: 100%;
}
.brand-page-head__quota {
min-width: 0;
padding: 0 12px;
}
.brand-detail__actions {
justify-content: flex-start;
}