Put the dual-bay sibling in the triage band, and fix the gage counterpart

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.
This commit is contained in:
2026-08-29 19:38:07 -04:00
parent 8666e0271c
commit dd7dd435c0
3 changed files with 57 additions and 32 deletions

View File

@@ -19,6 +19,20 @@
</span> </span>
</div> </div>
<div v-for="item in extraitems" :key="item.label" class="triage-item">
<span class="triage-label">{{ item.label }}</span>
<span class="triage-values">
<component
v-for="link in item.links"
:key="link.text"
:is="link.to ? 'router-link' : 'span'"
:to="link.to || undefined"
class="triage-chip"
:class="{ link: Boolean(link.to) }"
>{{ link.text }}</component>
</span>
</div>
<div v-if="accessmethods && accessmethods.length" class="triage-item"> <div v-if="accessmethods && accessmethods.length" class="triage-item">
<span class="triage-label">Remote access</span> <span class="triage-label">Remote access</span>
<span class="triage-values"> <span class="triage-values">
@@ -86,7 +100,15 @@ const props = defineProps({
// printer through usesprinter and defaultprinter. Hardcoding 'controls' // printer through usesprinter and defaultprinter. Hardcoding 'controls'
// left the band blank on the printer page, which is the one page where the // left the band blank on the printer page, which is the one page where the
// link is not obvious from the asset itself. // link is not obvious from the asset itself.
relationshiptypes: { type: Array, default: () => ['controls'] } relationshiptypes: { type: Array, default: () => ['controls'] },
// Triage facts a page knows but core cannot derive from a relationship
// edge. The machine page's dual-bay sibling is the case this exists for:
// it is resolved by the machines plugin, not from the relationships
// endpoint, and it used to sit in its own banner above the hero - two
// strips of chrome saying two halves of the same "what am I looking at".
// Shape: [{ label, links: [{ text, to }] }]. A link with no `to` renders
// as plain text rather than a dead chip.
extraitems: { type: Array, default: () => [] }
}) })
const machines = ref([]) const machines = ref([])
@@ -102,6 +124,7 @@ const hasAnything = computed(() =>
Boolean(slots.actions) || Boolean(slots.actions) ||
Boolean(props.liveness) || Boolean(props.liveness) ||
machines.value.length > 0 || machines.value.length > 0 ||
props.extraitems.length > 0 ||
(props.accessmethods && props.accessmethods.length > 0) || (props.accessmethods && props.accessmethods.length > 0) ||
Boolean(props.locationname) Boolean(props.locationname)
) )

View File

@@ -20,16 +20,6 @@
<div v-if="loading" class="loading">Loading...</div> <div v-if="loading" class="loading">Loading...</div>
<template v-else-if="machine"> <template v-else-if="machine">
<!-- Dual-bay sibling banner: a Dualpath pair is one physical machine -->
<div v-if="machine.dualpathpartner" class="dualpath-banner">
Dual-bay machine - sibling bay:
<router-link
v-if="machine.dualpathpartner.machineid"
:to="`/machines/${machine.dualpathpartner.machineid}`"
>{{ machine.dualpathpartner.assetnumber }}</router-link>
<span v-else>{{ machine.dualpathpartner.assetnumber }}</span>
</div>
<!-- Hero Section --> <!-- Hero Section -->
<div class="hero-card"> <div class="hero-card">
<div class="hero-image" v-if="machine.machine?.imageurl"> <div class="hero-image" v-if="machine.machine?.imageurl">
@@ -80,6 +70,7 @@
:assetid="machine.assetid" :assetid="machine.assetid"
counterparttype="computer" counterparttype="computer"
counterpartlabel="Controlled by" counterpartlabel="Controlled by"
:extraitems="triageextras"
/> />
</div> </div>
</div> </div>
@@ -259,7 +250,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { machinesApi } from '@/api' import { machinesApi } from '@/api'
import { withBase } from '@/utils/basePath' import { withBase } from '@/utils/basePath'
@@ -281,6 +272,25 @@ const { isEnabled } = useIdentifierFlags()
const loading = ref(true) const loading = ref(true)
const machine = ref(null) const machine = ref(null)
// The dual-bay sibling, as a triage chip. It had its own banner above the hero
// until the band existed; two strips of chrome answering two halves of the same
// "what am I looking at" read as clutter, and the sibling belongs beside the
// PC that drives the machine rather than above it.
const triageextras = computed(() => {
const partner = machine.value?.dualpathpartner
if (!partner) return []
return [{
// Says what the pair MEANS, not just that a link exists: a dualpath pair is
// one physical machine in two bays, which is why the old banner spelled it
// out rather than showing a bare number.
label: 'Dual-bay sibling',
links: [{
text: partner.assetnumber,
to: partner.machineid ? `/machines/${partner.machineid}` : null
}]
}]
})
const { heroWarranty, warrantyDate } = useWarrantyBadge(() => machine.value?.assetid) const { heroWarranty, warrantyDate } = useWarrantyBadge(() => machine.value?.assetid)
// relationships render via the shared AssetRelationships card // relationships render via the shared AssetRelationships card
@@ -334,21 +344,6 @@ async function confirmRetire() {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
} }
.dualpath-banner {
margin-bottom: 16px;
padding: 10px 14px;
border-radius: 6px;
background: var(--bg-card);
border: 1px solid var(--border);
border-left: 4px solid var(--primary);
color: var(--text);
font-size: 0.9rem;
}
.dualpath-banner a {
color: var(--link);
font-weight: 600;
}
.feature-tag { .feature-tag {
display: inline-block; display: inline-block;

View File

@@ -47,13 +47,20 @@
</div> </div>
</div> </div>
<!-- The 6am band. A gage usually has no controls edge at all, <!-- The 6am band: which PC drives this instrument.
and the band renders nothing when it has nothing to say - it is A metrology tool is reached through the PC that runs it - the
here so the five asset pages stay one shape rather than five. --> 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 <AssetTriageBand
:assetid="tool.assetid" :assetid="tool.assetid"
counterparttype="machine" counterparttype="computer"
counterpartlabel="Used on" counterpartlabel="Controlled by"
/> />
</div> </div>
</div> </div>