Relocate warranty, measuringtools, network, printers, usb, notifications, computers, and slides into plugins/<name>/frontend/. Each plugin's views are pulled from wherever they lived (own dir, plus the shared views/settings/, views/reports/, views/print/ dirs, and top-level views) into the plugin's frontend/views/, and its route file becomes the self-contained routes.js. Handled the messy cases: - computers: name mismatch (its views live in views/pcs/) - moved by following the route file's own imports, so the dir name did not matter. Its OS/access- protocol/PC-type settings views move with it (only computers.js routed them). - network: NetworkHub's sibling sub-views (NetworkDevicesList, SubnetsBrowse, not directly routed) moved too so its `./` imports resolve. - printers: the qrLogo helper is SHARED with core AssetLabel, so it stays in views/print/ and PrinterQR imports it via @/views/print/qrLogo. - slides: route file is toplevel-only (TVDashboard); SlideManager stays core (core.js routes /settings/slides). frontend/src/views/ now holds only core views; frontend/src/router/routes/ holds only core.js. All 13 plugins are self-contained under plugins/<name>/frontend/. Verified live: Network (hub + moved sub-views), Computers (name mismatch), GE-Enforce (helper), printedparts all render from their staged frontends. Build + 58 vitest + naming green.
296 lines
11 KiB
Vue
296 lines
11 KiB
Vue
<template>
|
|
<div>
|
|
<div class="page-header">
|
|
<h2>{{ isEdit ? 'Edit Measuring Tool' : 'New Measuring Tool' }}</h2>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div v-if="loading" class="loading">Loading...</div>
|
|
|
|
<form v-else @submit.prevent="saveTool">
|
|
<!-- Identity Section -->
|
|
<h3 class="form-section-title">Identity</h3>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="assetnumber">Asset Number (Gage Tag) *</label>
|
|
<input id="assetnumber" v-model="form.assetnumber" type="text" class="form-control" required />
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="name">Name / Alias</label>
|
|
<input id="name" v-model="form.name" type="text" class="form-control" />
|
|
<small class="form-help">Layperson-friendly label</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="serialnumber">Serial Number (vendor)</label>
|
|
<input id="serialnumber" v-model="form.serialnumber" type="text" class="form-control" />
|
|
</div>
|
|
<div class="form-group" v-if="isEnabled('gaugelabreference', 'measuring_tool')">
|
|
<label for="gaugelabreference">Gauge Lab Reference</label>
|
|
<input id="gaugelabreference" v-model="form.gaugelabreference" type="text" class="form-control" />
|
|
<small class="form-help">Authoritative gauge lab asset reference (if tracked)</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row" v-if="isEnabled('maintenancereference', 'measuring_tool')">
|
|
<div class="form-group">
|
|
<label for="maintenancereference">Maintenance Reference</label>
|
|
<input id="maintenancereference" v-model="form.maintenancereference" type="text" class="form-control" />
|
|
<small class="form-help">Maintenance system asset reference (if tracked)</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="measuringtooltypeid">Type</label>
|
|
<select id="measuringtooltypeid" v-model="form.measuringtooltypeid" class="form-control">
|
|
<option value="">Select type...</option>
|
|
<option v-for="t in types" :key="t.measuringtooltypeid" :value="t.measuringtooltypeid">
|
|
{{ t.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="statusid">Status</label>
|
|
<select id="statusid" v-model="form.statusid" class="form-control">
|
|
<option value="">Select status...</option>
|
|
<option v-for="s in statuses" :key="s.statusid" :value="s.statusid">{{ s.status }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Calibration Section -->
|
|
<h3 class="form-section-title">Calibration</h3>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="lastcalibrationdate">Last Calibration</label>
|
|
<input id="lastcalibrationdate" v-model="form.lastcalibrationdate" type="date" class="form-control" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="nextcalibrationdate">Next Calibration</label>
|
|
<input id="nextcalibrationdate" v-model="form.nextcalibrationdate" type="date" class="form-control" />
|
|
<small class="form-help">Drives the derived calibration status (overdue / due soon / current)</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="calibrationintervaldays">Interval (days)</label>
|
|
<input id="calibrationintervaldays" v-model.number="form.calibrationintervaldays" type="number" min="0" class="form-control" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="calibrationprovider">Calibration Provider</label>
|
|
<input id="calibrationprovider" v-model="form.calibrationprovider" type="text" class="form-control" />
|
|
<small class="form-help">Lab or in-house team that performs the calibration</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Location Section -->
|
|
<h3 class="form-section-title">Location & Organization</h3>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="locationid">Location</label>
|
|
<select id="locationid" v-model="form.locationid" class="form-control">
|
|
<option value="">Select location...</option>
|
|
<option v-for="l in locations" :key="l.locationid" :value="l.locationid">{{ l.locationname }}</option>
|
|
</select>
|
|
<small class="form-help">Operation locations (e.g. "0615 Blisk Inspection") hold measuring tools</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="businessunitid">Business Unit</label>
|
|
<select id="businessunitid" v-model="form.businessunitid" class="form-control">
|
|
<option value="">Select business unit...</option>
|
|
<option v-for="bu in businessunits" :key="bu.businessunitid" :value="bu.businessunitid">{{ bu.businessunit }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes Section -->
|
|
<h3 class="form-section-title">Notes</h3>
|
|
<div class="form-group">
|
|
<textarea id="notes" v-model="form.notes" class="form-control" rows="3" placeholder="Additional notes..."></textarea>
|
|
</div>
|
|
|
|
<!-- Site-defined custom fields for measuring tools -->
|
|
<CustomFieldsInputs ref="customFieldsRef" :assettypeid="assettypeid" :assetid="currentAssetId" />
|
|
|
|
<div v-if="error" class="error-message">{{ error }}</div>
|
|
|
|
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
|
<button type="submit" class="btn btn-primary" :disabled="saving">
|
|
{{ saving ? 'Saving...' : 'Save Measuring Tool' }}
|
|
</button>
|
|
<router-link to="/measuringtools" class="btn btn-secondary">Cancel</router-link>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { measuringtoolsApi, assetsApi, locationsApi, businessunitsApi } from '@/api'
|
|
import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
|
|
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
|
import { apiError } from '@/utils/apiError'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const { isEnabled } = useIdentifierFlags()
|
|
|
|
const isEdit = computed(() => !!route.params.id)
|
|
|
|
// Resolved dynamically from /api/assets/types (no hardcoded id) so custom
|
|
// fields load for the measuring_tool asset type.
|
|
const assettypeid = ref(null)
|
|
const customFieldsRef = ref(null)
|
|
const currentAssetId = ref(null)
|
|
|
|
const loading = ref(true)
|
|
const saving = ref(false)
|
|
const error = ref('')
|
|
|
|
const form = ref({
|
|
assetnumber: '',
|
|
name: '',
|
|
gaugelabreference: '',
|
|
maintenancereference: '',
|
|
serialnumber: '',
|
|
statusid: '',
|
|
measuringtooltypeid: '',
|
|
lastcalibrationdate: '',
|
|
nextcalibrationdate: '',
|
|
calibrationintervaldays: null,
|
|
calibrationprovider: '',
|
|
locationid: '',
|
|
businessunitid: '',
|
|
notes: '',
|
|
})
|
|
|
|
const types = ref([])
|
|
const statuses = ref([])
|
|
const locations = ref([])
|
|
const businessunits = ref([])
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const [typesResponse, statusResponse, locResponse, buResponse, assetTypesResponse] = await Promise.all([
|
|
measuringtoolsApi.types.list({ perpage: 200 }),
|
|
assetsApi.statuses.list(),
|
|
locationsApi.list({ perpage: 500 }),
|
|
businessunitsApi.list({ perpage: 500 }),
|
|
assetsApi.types.list(),
|
|
])
|
|
types.value = typesResponse.data.data || []
|
|
statuses.value = statusResponse.data.data || []
|
|
locations.value = locResponse.data.data || []
|
|
businessunits.value = buResponse.data.data || []
|
|
|
|
const assetType = (assetTypesResponse.data.data || []).find(a => a.assettype === 'measuring_tool')
|
|
assettypeid.value = assetType ? assetType.assettypeid : null
|
|
|
|
if (isEdit.value) {
|
|
const response = await measuringtoolsApi.get(route.params.id)
|
|
const data = response.data.data
|
|
currentAssetId.value = data.assetid || null
|
|
const ext = data.measuringtool || {}
|
|
form.value = {
|
|
assetnumber: data.assetnumber || '',
|
|
name: data.name || '',
|
|
gaugelabreference: data.gaugelabreference || '',
|
|
maintenancereference: data.maintenancereference || '',
|
|
serialnumber: data.serialnumber || '',
|
|
statusid: data.statusid || '',
|
|
measuringtooltypeid: ext.measuringtooltypeid || '',
|
|
lastcalibrationdate: ext.lastcalibrationdate || '',
|
|
nextcalibrationdate: ext.nextcalibrationdate || '',
|
|
calibrationintervaldays: ext.calibrationintervaldays ?? null,
|
|
calibrationprovider: ext.calibrationprovider || '',
|
|
locationid: data.locationid || '',
|
|
businessunitid: data.businessunitid || '',
|
|
notes: data.notes || '',
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading data:', err)
|
|
error.value = 'Failed to load data'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
async function saveTool() {
|
|
error.value = ''
|
|
saving.value = true
|
|
try {
|
|
const data = {
|
|
assetnumber: form.value.assetnumber,
|
|
name: form.value.name || null,
|
|
gaugelabreference: form.value.gaugelabreference || null,
|
|
maintenancereference: form.value.maintenancereference || null,
|
|
serialnumber: form.value.serialnumber || null,
|
|
statusid: form.value.statusid || null,
|
|
measuringtooltypeid: form.value.measuringtooltypeid || null,
|
|
lastcalibrationdate: form.value.lastcalibrationdate || null,
|
|
nextcalibrationdate: form.value.nextcalibrationdate || null,
|
|
calibrationintervaldays: form.value.calibrationintervaldays ?? null,
|
|
calibrationprovider: form.value.calibrationprovider || null,
|
|
locationid: form.value.locationid || null,
|
|
businessunitid: form.value.businessunitid || null,
|
|
notes: form.value.notes || null,
|
|
}
|
|
|
|
let saved
|
|
if (isEdit.value) {
|
|
const response = await measuringtoolsApi.update(route.params.id, data)
|
|
saved = response.data.data
|
|
} else {
|
|
const response = await measuringtoolsApi.create(data)
|
|
saved = response.data.data
|
|
}
|
|
|
|
const assetId = saved.assetid
|
|
if (assetId && customFieldsRef.value) {
|
|
try {
|
|
await customFieldsRef.value.save(assetId)
|
|
} catch (customFieldsErr) {
|
|
console.error('Error saving custom fields:', customFieldsErr)
|
|
}
|
|
}
|
|
|
|
router.push(`/measuringtools/${saved.measuringtool?.measuringtoolid || route.params.id}`)
|
|
} catch (err) {
|
|
console.error('Error saving measuring tool:', err)
|
|
error.value = apiError(err, 'Failed to save measuring tool')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.form-section-title {
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
margin: 1.5rem 0 0.75rem 0;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.form-section-title:first-of-type {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.form-help {
|
|
display: block;
|
|
margin-top: 0.25rem;
|
|
font-size: 0.8rem;
|
|
color: var(--text-light);
|
|
}
|
|
</style>
|