Files
bookorbit/client/src/features/settings/metadata-preferences/components/FieldRulesTable.vue
T
neon 21b68685ad fix(settings): stop truncating the genres merge strategy control
Make the field rules table one grid with each row as a subgrid, so the
merge and status columns size to the widest control instead of fixed
272px and 116px tracks. The four-option genres control and translated
labels now fit, and the global scope drops the empty status gap.
2026-10-08 17:55:12 +00:00

126 lines
4.9 KiB
Vue

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { SearchX } from '@lucide/vue'
import type { FieldPreference, MetadataField, ProviderStatus } from '@bookorbit/types'
import { Button } from '@/components/ui/button'
import FieldRuleRow from './FieldRuleRow.vue'
import { FIELD_GROUPS } from '../lib/field-rules'
const { t } = useI18n()
const props = defineProps<{
fields: Record<MetadataField, FieldPreference>
statuses: ProviderStatus[]
visibleFields: Set<MetadataField>
overriddenFields: Set<MetadataField>
unsavedFields: Set<MetadataField>
isGlobalScope: boolean
scopeName: string
saving?: boolean
}>()
const emit = defineEmits<{
change: [field: MetadataField, pref: FieldPreference]
revert: [field: MetadataField]
clearFilters: []
}>()
/** One row open at a time: two open editors on one screen make the priority orders easy to confuse. */
const expandedField = ref<MetadataField | null>(null)
// A row hidden by a filter must not stay open behind it.
watch(
() => props.visibleFields,
(visible) => {
if (expandedField.value && !visible.has(expandedField.value)) expandedField.value = null
},
)
const groups = computed(() =>
FIELD_GROUPS.map((group) => {
const fields = group.fields.filter((field) => props.visibleFields.has(field))
return {
id: group.id,
label: t(`settings.metadata.fieldRules.groups.${group.id}`),
fields,
enabled: fields.filter((field) => props.fields[field]?.enabled).length,
}
}).filter((group) => group.fields.length > 0),
)
const isEmpty = computed(() => groups.value.length === 0)
function setExpanded(field: MetadataField, expanded: boolean) {
expandedField.value = expanded ? field : null
}
function onChange(field: MetadataField, pref: FieldPreference) {
emit('change', field, pref)
}
function onRevert(field: MetadataField) {
emit('revert', field)
}
function clearFilters() {
emit('clearFilters')
}
</script>
<template>
<!-- One grid for the whole table; the header, groups and rows are subgrids of it. The merge and
status tracks are auto, so they fit the widest control in any row (the four-option genres
control, translated labels) and the header stays over its column. Subgrid padding counts
toward the edge tracks, hence 24px more on the first track than its content needs. -->
<div class="md:grid md:grid-cols-[minmax(184px,224px)_minmax(0,1fr)_auto_auto] md:gap-x-4">
<!-- Column header. Hidden on mobile, where the row is a card rather than a table line. -->
<div
class="hidden items-center border-b border-border bg-muted/25 px-6 py-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground md:col-span-full md:grid md:grid-cols-subgrid"
aria-hidden="true"
>
<div>{{ t('settings.metadata.fieldRules.table.field') }}</div>
<div>{{ t('settings.metadata.fieldRules.table.activeProviders') }}</div>
<div>{{ t('settings.metadata.fieldRules.table.mergeStrategy') }}</div>
<!-- Global rules are never overrides, so the column carries only the expander there. -->
<div class="text-right">{{ isGlobalScope ? '' : t('settings.metadata.fieldRules.table.status') }}</div>
</div>
<div v-if="isEmpty" class="flex flex-col items-center gap-3 px-6 py-12 text-center md:col-span-full">
<SearchX :size="22" class="text-muted-foreground" aria-hidden="true" />
<p class="text-sm text-muted-foreground">{{ t('settings.metadata.fieldRules.table.noMatches') }}</p>
<Button variant="outline" size="sm" type="button" @click="clearFilters">
{{ t('settings.metadata.fieldRules.toolbar.clearFilters') }}
</Button>
</div>
<template v-else>
<section v-for="group in groups" :key="group.id" class="md:col-span-full md:grid md:grid-cols-subgrid">
<h3
class="flex items-center gap-2 border-b border-border/60 bg-muted/25 px-4 py-1.5 text-[10px] font-bold uppercase tracking-[0.1em] text-muted-foreground md:col-span-full md:px-6"
>
{{ group.label }}
<span class="ml-auto text-[10px] font-medium normal-case tracking-normal tabular-nums">
{{ t('settings.metadata.fieldRules.groupSummary.enabledOf', { fields: group.fields.length, enabled: group.enabled }) }}
</span>
</h3>
<FieldRuleRow
v-for="field in group.fields"
:key="field"
:field="field"
:preference="fields[field]"
:statuses="statuses"
:expanded="expandedField === field"
:saving="saving"
:unsaved="unsavedFields.has(field)"
:inherited="isGlobalScope ? undefined : !overriddenFields.has(field)"
:scope-name="scopeName"
@change="onChange"
@revert="onRevert"
@update:expanded="(value: boolean) => setExpanded(field, value)"
/>
</section>
</template>
</div>
</template>