The machine page opened with a dual-bay banner directly above a hero band saying 'Controlled by'. Both answer 'what am I looking at', so the sibling is now a chip in the band. The band takes a generic extraitems prop rather than learning what dualpath is - the pairing is resolved by the machines plugin, not from the relationships endpoint. MeasuringToolDetail filtered for a machine counterpart, but the import creates a controls edge PC -> tool, so a gage's counterpart is a computer. It rendered nothing on every metrology station that has a PC. Verified on screen. Full suite 2178 passed, frontend 280 passed.
250 lines
11 KiB
Vue
250 lines
11 KiB
Vue
<template>
|
|
<div class="detail-page">
|
|
<div class="page-header">
|
|
<h2>Measuring Tool Details</h2>
|
|
<div class="header-actions">
|
|
<router-link :to="`/print/asset-label/measuring_tool/${tool?.measuringtool?.measuringtoolid}`" class="btn btn-secondary" v-if="tool" target="_blank">
|
|
Print Label
|
|
</router-link>
|
|
<router-link :to="`/measuringtools/${tool?.measuringtool?.measuringtoolid}/edit`" class="btn btn-primary" v-if="tool">
|
|
Edit
|
|
</router-link>
|
|
<button @click="confirmRetire" class="btn btn-danger" v-if="tool">Retire</button>
|
|
<router-link to="/measuringtools" class="btn btn-secondary">Back to List</router-link>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="loading" class="loading">Loading...</div>
|
|
|
|
<template v-else-if="tool">
|
|
<!-- Hero Section -->
|
|
<div class="hero-card">
|
|
<div class="hero-content">
|
|
<div class="hero-title">
|
|
<h1>{{ tool.assetnumber }}</h1>
|
|
<span v-if="tool.name" class="hero-alias">{{ tool.name }}</span>
|
|
</div>
|
|
<div class="hero-meta">
|
|
<span class="badge badge-lg badge-primary">
|
|
{{ tool.assettypename || 'Measuring Tool' }}
|
|
</span>
|
|
<span class="badge badge-lg" :style="colorStyle(tool.measuringtool?.calibrationstatuscolor)">
|
|
{{ statusLabel(tool.measuringtool?.calibrationstatus) }}
|
|
</span>
|
|
</div>
|
|
<div class="hero-details">
|
|
<div class="hero-detail" v-if="tool.measuringtool?.measuringtooltypename">
|
|
<span class="hero-detail-label">Type</span>
|
|
<span class="hero-detail-value">{{ tool.measuringtool.measuringtooltypename }}</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="tool.measuringtool?.nextcalibrationdate">
|
|
<span class="hero-detail-label">Next Calibration</span>
|
|
<span class="hero-detail-value">{{ formatDate(tool.measuringtool.nextcalibrationdate) }}</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="tool.locationname">
|
|
<span class="hero-detail-label">Location</span>
|
|
<span class="hero-detail-value">{{ tool.locationname }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- The 6am band: which PC drives this instrument.
|
|
A metrology tool is reached through the PC that runs it - the
|
|
import synthesizes one measuring-tool asset per metrology PC and
|
|
a 'controls' edge PC -> tool (stage_metrology), so the
|
|
counterpart here is a COMPUTER, exactly as on the machine page.
|
|
This was first written as counterparttype="machine" labelled
|
|
"Used on", which filtered for a counterpart that cannot exist
|
|
and so rendered nothing on every gage that had a PC. A hand-held
|
|
gage genuinely has no such edge, and the band correctly shows
|
|
nothing for it. -->
|
|
<AssetTriageBand
|
|
:assetid="tool.assetid"
|
|
counterparttype="computer"
|
|
counterpartlabel="Controlled by"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
|
|
<!-- Main Content Grid -->
|
|
<div class="content-grid">
|
|
<!-- Left Column -->
|
|
<div class="content-column">
|
|
<!-- Identity Section -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Identity</h3>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<span class="info-label">Asset Number (Gage Tag)</span>
|
|
<span class="info-value">{{ tool.assetnumber }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.name">
|
|
<span class="info-label">Name</span>
|
|
<span class="info-value">{{ tool.name }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.gaugelabreference && isEnabled('gaugelabreference', 'measuring_tool')">
|
|
<span class="info-label">Gauge Lab Reference</span>
|
|
<span class="info-value mono">{{ tool.gaugelabreference }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.maintenancereference && isEnabled('maintenancereference', 'measuring_tool')">
|
|
<span class="info-label">Maintenance Reference</span>
|
|
<span class="info-value mono">{{ tool.maintenancereference }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.serialnumber">
|
|
<span class="info-label">Serial Number</span>
|
|
<span class="info-value mono">{{ tool.serialnumber }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Type</span>
|
|
<span class="info-value">{{ tool.measuringtool?.measuringtooltypename || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Calibration Section -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Calibration</h3>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<span class="info-label">Status</span>
|
|
<span class="info-value">
|
|
<span class="badge" :style="colorStyle(tool.measuringtool?.calibrationstatuscolor)">
|
|
{{ statusLabel(tool.measuringtool?.calibrationstatus) }}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Last Calibration</span>
|
|
<span class="info-value">{{ tool.measuringtool?.lastcalibrationdate ? formatDate(tool.measuringtool.lastcalibrationdate) : '-' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Next Calibration</span>
|
|
<span class="info-value">{{ tool.measuringtool?.nextcalibrationdate ? formatDate(tool.measuringtool.nextcalibrationdate) : '-' }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.measuringtool?.calibrationintervaldays">
|
|
<span class="info-label">Interval</span>
|
|
<span class="info-value">{{ tool.measuringtool.calibrationintervaldays }} days</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.measuringtool?.calibrationprovider">
|
|
<span class="info-label">Provider</span>
|
|
<span class="info-value">{{ tool.measuringtool.calibrationprovider }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column -->
|
|
<div class="content-column">
|
|
<!-- Location & Organization -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Location & Organization</h3>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<span class="info-label">Location</span>
|
|
<span class="info-value">{{ tool.locationname || '-' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Business Unit</span>
|
|
<span class="info-value">{{ tool.businessunitname || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Fields -->
|
|
<CustomFieldsSection :assetid="tool.assetid" />
|
|
|
|
<!-- Warranty -->
|
|
<PluginAssetPanels :assetid="tool.assetid" />
|
|
|
|
<!-- All relationships (partof, connectedto, ...) -->
|
|
<AssetRelationships v-if="tool.assetid" :assetid="tool.assetid" />
|
|
|
|
<!-- Notes -->
|
|
<div class="section-card" v-if="tool.notes">
|
|
<h3 class="section-title">Notes</h3>
|
|
<p class="notes-text">{{ tool.notes }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Footer -->
|
|
<div class="audit-footer">
|
|
<span>Created {{ formatDateTime(tool.createddate) }}<template v-if="tool.createdby"> by {{ tool.createdby }}</template></span>
|
|
<span>Modified {{ formatDateTime(tool.modifieddate) }}<template v-if="tool.modifiedby"> by {{ tool.modifiedby }}</template></span>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-else class="card">
|
|
<p style="text-align: center; color: var(--text-light);">Measuring tool not found</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { colorStyle } from '@/utils/colorStyle'
|
|
import { measuringtoolsApi } from '@/api'
|
|
import AssetTriageBand from '@/components/AssetTriageBand.vue'
|
|
import CustomFieldsSection from '@/components/CustomFieldsSection.vue'
|
|
import PluginAssetPanels from '@/components/PluginAssetPanels.vue'
|
|
import AssetRelationships from '@/components/AssetRelationships.vue'
|
|
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
|
import { setRecordTitle } from '@/composables/pageTitle'
|
|
import { useToast } from '@/composables/toast'
|
|
import { apiError } from '@/utils/apiError'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const toast = useToast()
|
|
|
|
const { isEnabled } = useIdentifierFlags()
|
|
|
|
const loading = ref(true)
|
|
const tool = ref(null)
|
|
|
|
const STATUS_LABELS = { overdue: 'Overdue', duesoon: 'Due Soon', current: 'Current', unknown: 'Unknown' }
|
|
function statusLabel(status) { return STATUS_LABELS[status] || 'Unknown' }
|
|
function formatDate(d) { if (!d) return '-'; return new Date(d + 'T00:00:00').toLocaleDateString() }
|
|
function formatDateTime(d) { if (!d) return '-'; return new Date(d).toLocaleString() }
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
// by-asset route resolves the tool from a core asset id (search/cross-link
|
|
// rows carry assetid, not the extension id); detail route keys on the id.
|
|
const response = route.params.assetid
|
|
? await measuringtoolsApi.getByAsset(route.params.assetid)
|
|
: await measuringtoolsApi.get(route.params.id)
|
|
tool.value = response.data.data
|
|
setRecordTitle(tool.value?.assetnumber || tool.value?.name)
|
|
} catch (err) {
|
|
console.error('Error loading measuring tool:', err)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
// Retire, not delete: the server deactivates the asset, so the measuring tool
|
|
// leaves every list and report while its history and relationships stay put.
|
|
// Reversible by an admin setting isactive back to true.
|
|
async function confirmRetire() {
|
|
const label = tool.value?.assetnumber || tool.value?.name || 'this measuring tool'
|
|
if (!confirm(`Retire ${label}?\n\nIt will no longer appear in ShopDB. `
|
|
+ `History and relationships are kept, and it can be restored.`)) return
|
|
try {
|
|
await measuringtoolsApi.remove(tool.value?.measuringtool?.measuringtoolid)
|
|
toast.success(`Retired ${label}`)
|
|
router.push('/measuringtools')
|
|
} catch (error) {
|
|
console.error('Error retiring measuring tool:', error)
|
|
toast.error(apiError(error, 'Failed to retire measuring tool'))
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.mono {
|
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
}
|
|
</style>
|