Add the measuringtools plugin (ADR-005) and the plugin-system tutorial
Gage-lab instruments as Asset extensions: measuringtooltypes (color-coded lookup) + measuringtools (calibration interval/dates, provider, notes) with calibration status derived at read time (overdue / due soon / current / unknown), never stored. Full CRUD API with permission-gated writes, types management with in-use guard, calibration report, nav/reports/config-schema hooks, and a complete frontend (list/detail/form, types settings page, calibration report page, gated routes per ADR-009). First plugin whose migration chain really creates tables post-cutover (ADR-008), and the working example for docs/PLUGIN-GUIDE.md - a 12-section walkthrough of building a plugin on this framework, linked from PLUGIN-QUICKSTART and PLUGINS. Verified: full suite 323 passing, live E2E on all four pages, fresh scratch-MySQL migration dry-run green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
191
frontend/src/views/measuringtools/MeasuringToolDetail.vue
Normal file
191
frontend/src/views/measuringtools/MeasuringToolDetail.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="detail-page">
|
||||
<div class="page-header">
|
||||
<h2>Measuring Tool Details</h2>
|
||||
<div class="header-actions">
|
||||
<router-link :to="`/measuringtools/${tool?.measuringtool?.measuringtoolid}/edit`" class="btn btn-primary" v-if="tool">
|
||||
Edit
|
||||
</router-link>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<span class="info-label">Gauge Lab Reference</span>
|
||||
<span class="info-value mono">{{ tool.gaugelabreference }}</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 -->
|
||||
<WarrantyPanel :assetid="tool.assetid" :items="warranties" />
|
||||
|
||||
<!-- 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 } from 'vue-router'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { measuringtoolsApi } from '../../api'
|
||||
import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
|
||||
import WarrantyPanel from '../../components/WarrantyPanel.vue'
|
||||
import { useWarrantyBadge } from '../../composables/warrantyBadge'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(true)
|
||||
const tool = ref(null)
|
||||
const { warranties } = useWarrantyBadge(() => tool.value?.assetid)
|
||||
|
||||
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 {
|
||||
const response = await measuringtoolsApi.get(route.params.id)
|
||||
tool.value = response.data.data
|
||||
} catch (err) {
|
||||
console.error('Error loading measuring tool:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mono {
|
||||
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user