FIX - allow configurable radio channel jobs (#46)

Mark locked-channel job maps mutable in the configurator schema and initialize new structured list rows from their schema template so required frequency ranges remain valid.
This commit is contained in:
Leon.Schmidt
2026-08-26 18:46:51 +02:00
committed by GitHub
parent a597e161ea
commit 693c9bd6f9
6 changed files with 97 additions and 5 deletions
@@ -380,10 +380,10 @@ function toggleOptionalString(event: Event): void {
function addListRow(): void {
const rows = Array.isArray(props.modelValue) ? [...props.modelValue] : []
const index = rows.length
const value = rows.length
? blankLike(rows[0])
: listTemplate.value
? blankFromConfiguratorStructure(listTemplate.value)
const value = listTemplate.value
? blankFromConfiguratorStructure(listTemplate.value)
: rows.length
? blankLike(rows[0])
: blankValue(newArrayKind.value)
rows.push(value)
emit('update:modelValue', rows)
@@ -440,6 +440,9 @@ describe('standalone admin panel contracts', () => {
expect(source).toContain("selectConfiguratorScope('media')")
expect(source).not.toContain('class="admin-panel-config-meta"')
expect(configuratorValueEditor).toContain('function addListRow()')
expect(configuratorValueEditor).toMatch(
/const value = listTemplate\.value\s+\? blankFromConfiguratorStructure\(listTemplate\.value\)\s+: rows\.length\s+\? blankLike\(rows\[0\]\)/,
)
expect(configuratorValueEditor).toContain('function addTableField()')
expect(configuratorValueEditor).toContain(
'const canExtendTable = computed(',
@@ -1,7 +1,10 @@
import { describe, expect, it } from 'vitest'
import type { AdminConfiguratorStructure } from '@/types/admin'
import { createMutableTableEntry } from '@/utils/adminConfiguratorDefaults'
import {
blankFromConfiguratorStructure,
createMutableTableEntry,
} from '@/utils/adminConfiguratorDefaults'
describe('admin configurator defaults', () => {
it('creates a usable company draft from its key and the server defaults', () => {
@@ -111,4 +114,33 @@ describe('admin configurator defaults', () => {
false,
)
})
it('preserves required nested list fields in schema-derived rows', () => {
const structure: AdminConfiguratorStructure = {
fields: {
jobs: {
fields: {
police: { kind: 'value', valueType: 'boolean' },
},
kind: 'table',
mutableKeys: true,
template: { kind: 'value', valueType: 'boolean' },
},
range: {
items: [
{ kind: 'value', valueType: 'number' },
{ kind: 'value', valueType: 'number' },
],
kind: 'list',
template: { kind: 'value', valueType: 'number' },
},
},
kind: 'table',
}
expect(blankFromConfiguratorStructure(structure)).toEqual({
jobs: { police: false },
range: [0, 0],
})
})
})
@@ -540,6 +540,25 @@ function buildStructure(value, scope, path) {
if (scope === 'config' && path === 'Phone.Keybind') {
return { kind: 'optionalString' }
}
if (
scope === 'config' &&
/^Radio\.LockedChannels\.\d+\.jobs$/.test(path) &&
value !== null &&
typeof value === 'object' &&
!Array.isArray(value)
) {
return {
fields: Object.fromEntries(
Object.entries(value).map(([key, child]) => [
key,
buildStructure(child, scope, `${path}.${key}`),
]),
),
kind: 'table',
mutableKeys: true,
template: { kind: 'value', valueType: 'boolean' },
}
}
if (
scope === 'config' &&
path === 'Companies.Definitions' &&
@@ -193,6 +193,29 @@ describe('admin configurator fixture', () => {
})
})
it('allows custom jobs in locked radio channel entries', () => {
const radio = loadConfiguratorSections()
.flatMap((section) => section.fields)
.find((field) => field.path === 'Radio')
const lockedChannels = radio?.structure?.fields?.LockedChannels
const jobs = lockedChannels?.items?.[0]?.fields?.jobs
expect(jobs).toMatchObject({
fields: {
ambulance: { kind: 'value', valueType: 'boolean' },
police: { kind: 'value', valueType: 'boolean' },
},
kind: 'table',
mutableKeys: true,
template: { kind: 'value', valueType: 'boolean' },
})
expect(lockedChannels?.template?.fields?.jobs).toMatchObject({
kind: 'table',
mutableKeys: true,
template: { kind: 'value', valueType: 'boolean' },
})
})
it('publishes fixed schemas for every empty configurable collection', () => {
const fields = loadConfiguratorSections().flatMap(
(section) => section.fields,
@@ -637,6 +637,21 @@ local function build_structure(value, scope, path)
if scope == "config" and path == "Phone.Keybind" then
return { kind = "optionalString" }
end
if scope == "config"
and path:match("^Radio%.LockedChannels%.%d+%.jobs$")
and value_type == "table"
then
local fields = {}
for key, child in pairs(value) do
fields[key] = build_structure(child, scope, path .. "." .. tostring(key))
end
return {
fields = fields,
kind = "table",
mutableKeys = true,
template = { kind = "value", valueType = "boolean" },
}
end
if scope == "config" and path == "Companies.Definitions" and value_type == "table" then
local keys = {}
for key in pairs(value) do