Files
shopdb-flask/plugins/computers/frontend/views/ScopeNodeEditor.vue
benjamin.baker 01734e5449 harden CSV import reconciliation and preserve saved model selections
Default fuzzy reconcile threshold to 0 and refuse merges when digit runs differ so numbered labels like Bay 311 never bind to Bay 310. Keep edit forms from clearing saved model fields when category filtering hides untyped rows. Includes in-progress computers policy and endpoints command feature work.
2026-08-28 09:29:09 -04:00

107 lines
3.1 KiB
Vue

<template>
<div class="scope-node">
<div class="scope-node-row">
<select v-model="node.type" class="form-control scope-type" @change="onTypeChange">
<option value="true">Matches everything</option>
<option value="pctype">PC type is</option>
<option value="app">App is installed</option>
<option value="and">ALL of (AND)</option>
<option value="or">ANY of (OR)</option>
<option value="not">NOT</option>
</select>
<select v-if="node.type === 'pctype'" v-model.number="node.id" class="form-control">
<option :value="undefined" disabled>Select PC type...</option>
<option v-for="pt in pctypes" :key="pt.computertypeid" :value="pt.computertypeid">
{{ pt.computertype }}
</option>
</select>
<select v-if="node.type === 'app'" v-model.number="node.id" class="form-control">
<option :value="undefined" disabled>Select app...</option>
<option v-for="app in apps" :key="app.appid" :value="app.appid">
{{ app.appname }}
</option>
</select>
<button v-if="removable" type="button" class="btn btn-danger btn-sm" @click="$emit('remove')">
Remove
</button>
</div>
<div v-if="node.type === 'and' || node.type === 'or'" class="scope-children">
<ScopeNodeEditor
v-for="(child, index) in node.nodes"
:key="index"
:node="child"
:pctypes="pctypes"
:apps="apps"
:removable="true"
@remove="removeChild(index)"
/>
<button type="button" class="btn btn-secondary btn-sm" @click="addChild">+ Add condition</button>
</div>
<div v-if="node.type === 'not'" class="scope-children">
<ScopeNodeEditor
:node="node.node"
:pctypes="pctypes"
:apps="apps"
:removable="false"
/>
</div>
</div>
</template>
<script setup>
const props = defineProps({
node: { type: Object, required: true },
pctypes: { type: Array, default: () => [] },
apps: { type: Array, default: () => [] },
removable: { type: Boolean, default: false }
})
defineEmits(['remove'])
function onTypeChange() {
const node = props.node
// Reset payload keys so a switched node never carries stale fields from its
// previous type. Keep only what the new type needs.
delete node.id
delete node.nodes
delete node.node
if (node.type === 'and' || node.type === 'or') {
node.nodes = [{ type: 'true' }]
} else if (node.type === 'not') {
node.node = { type: 'true' }
}
}
function addChild() {
props.node.nodes.push({ type: 'true' })
}
function removeChild(index) {
props.node.nodes.splice(index, 1)
if (props.node.nodes.length === 0) {
props.node.nodes.push({ type: 'true' })
}
}
</script>
<script>
export default { name: 'ScopeNodeEditor' }
</script>
<style scoped>
.scope-node { margin: 4px 0; }
.scope-node-row { display: flex; gap: 8px; align-items: center; }
.scope-type { max-width: 220px; }
.scope-children {
margin-left: 24px;
padding-left: 12px;
border-left: 2px solid var(--border-color, #444);
margin-top: 6px;
}
</style>