Feature work from the 2026-07 session: Settings IA - Replace the flat 27-card settings hub with a persistent two-pane shell (SettingsLayout.vue): grouped, searchable left rail + content pane. - Nest all settings/* routes under the shell via router post-processing; shared nav catalog in settingsNav.js. Group by asset class (PCs, Printers, Equipment, Network) so per-type settings stop scattering. Custom fields (core) - customfields + customfieldvalues tables (migration 7d14), CRUD API at /api/customfields, per-asset value get/save. - Settings management page + reusable CustomFieldsSection (detail) and CustomFieldsInputs (form) wired into all four asset types. Warranty (new plugin) - plugins/warranty: warranties + warrantyassets (migration 7d15), derived coverage status, provider abstraction (manual now; Dell/Lenovo/HP stubs). - API CRUD + per-asset panel + report buckets; WarrantyPanel on all four detail pages; Warranties management page; Warranty report + Reports card. - Seed warranty.* permissions. Printer drivers - printerdrivers table (migration 7d13) linked to printer models; drivers now surface on the matching printer's detail page. Other - PCDetail rebalanced (Network + Status + Warranty + custom fields on the right). - Rename PCs list "Features" column to "Remote Access"; fix badge hover underline. - Drop equipment islocationonly field. - Centralize asset-type label/route maps into utils/assetTypes.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
83 lines
2.8 KiB
Vue
83 lines
2.8 KiB
Vue
<template>
|
|
<div v-if="fields.length" class="custom-fields">
|
|
<div class="form-group" v-for="f in fields" :key="f.fieldid">
|
|
<label>{{ f.label }}</label>
|
|
|
|
<select v-if="f.datatype === 'select'" v-model="local[f.fieldid]" class="form-control">
|
|
<option value="">-- none --</option>
|
|
<option v-for="opt in f.options" :key="opt" :value="opt">{{ opt }}</option>
|
|
</select>
|
|
|
|
<label v-else-if="f.datatype === 'boolean'" class="checkbox-label">
|
|
<input type="checkbox"
|
|
:checked="local[f.fieldid] === 'true'"
|
|
@change="local[f.fieldid] = $event.target.checked ? 'true' : 'false'" />
|
|
Yes
|
|
</label>
|
|
|
|
<input v-else-if="f.datatype === 'date'" type="date" v-model="local[f.fieldid]" class="form-control" />
|
|
<input v-else-if="f.datatype === 'number'" type="number" v-model="local[f.fieldid]" class="form-control" />
|
|
<input v-else type="text" v-model="local[f.fieldid]" class="form-control" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, watch, onMounted } from 'vue'
|
|
import { customFieldsApi } from '../api'
|
|
|
|
const props = defineProps({
|
|
// Asset type whose fields to render (needed even for a brand-new asset).
|
|
assettypeid: { type: [Number, String], default: null },
|
|
// Existing asset id, if editing - used to preload stored values.
|
|
assetid: { type: [Number, String], default: null },
|
|
})
|
|
|
|
const fields = ref([])
|
|
const local = reactive({})
|
|
|
|
async function load() {
|
|
fields.value = []
|
|
Object.keys(local).forEach(k => delete local[k])
|
|
if (!props.assettypeid) return
|
|
try {
|
|
// Definitions for this asset type (form-visible, active).
|
|
const defsResponse = await customFieldsApi.list({ assettypeid: props.assettypeid })
|
|
const defs = (defsResponse.data.data || []).filter(f => f.showonform)
|
|
fields.value = defs
|
|
|
|
// Seed blanks, then overlay stored values if editing.
|
|
for (const f of defs) local[f.fieldid] = f.datatype === 'boolean' ? 'false' : ''
|
|
if (props.assetid) {
|
|
const valResponse = await customFieldsApi.forAsset(props.assetid)
|
|
for (const f of (valResponse.data.data || [])) {
|
|
if (f.value != null) local[f.fieldid] = String(f.value)
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading custom field defs:', err)
|
|
}
|
|
}
|
|
|
|
// Persist current values against an asset id (parent calls after asset save).
|
|
async function save(assetid) {
|
|
if (!assetid || !fields.value.length) return
|
|
const values = {}
|
|
for (const f of fields.value) values[f.fieldid] = local[f.fieldid]
|
|
await customFieldsApi.saveForAsset(assetid, values)
|
|
}
|
|
|
|
onMounted(load)
|
|
watch(() => [props.assettypeid, props.assetid], load)
|
|
|
|
defineExpose({ save, hasFields: () => fields.value.length > 0 })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.custom-fields {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
</style>
|