Frontend: asset identifiers, statuses, printer types, UI fixes
- Wire gauge/maintenance refs + identifier toggles into equipment pages. - Repoint status dropdowns and Settings>Statuses to asset statuses. - Printer type column/filter; require vendor before model; printer-model picker. - Per-type identifier labels; printer detail title fallback. - Fixes: truncate long description cells, opaque supply modal, readable dark-mode autofill, drum/waste default colorless. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
<div class="hero-card">
|
||||
<div class="hero-content">
|
||||
<div class="hero-title">
|
||||
<h1>{{ printer.name || printer.assetnumber }}</h1>
|
||||
<h1>{{ displayTitle }}</h1>
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span class="badge badge-lg badge-printer">Printer</span>
|
||||
@@ -63,7 +63,7 @@
|
||||
<span class="info-label">Windows Name</span>
|
||||
<span class="info-value mono">{{ printer.printer.windowsname }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="printer.printer?.hostname">
|
||||
<div class="info-row" v-if="identifierflags.fqdn && printer.printer?.hostname">
|
||||
<span class="info-label">Hostname / FQDN</span>
|
||||
<span class="info-value mono">{{ printer.printer.hostname }}</span>
|
||||
</div>
|
||||
@@ -172,23 +172,30 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="supplies-grid">
|
||||
<div v-for="supply in supplies" :key="supply.supplyid" class="supply-item">
|
||||
<div v-for="supply in supplies" :key="supply.itemid || supply.name" class="supply-item">
|
||||
<div class="supply-header">
|
||||
<span class="supply-name">{{ supply.supplyname }}</span>
|
||||
<span class="supply-level" :class="getSupplyLevelClass(supply.currentlevel)">
|
||||
{{ supply.currentlevel !== null ? `${supply.currentlevel}%` : 'N/A' }}
|
||||
<span class="supply-name">{{ supply.name }}</span>
|
||||
<span class="supply-level" :class="supply.status">
|
||||
{{ supply.level !== null ? `${supply.level}%` : 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="supply-bar">
|
||||
<div
|
||||
class="supply-bar-fill"
|
||||
:class="getSupplyLevelClass(supply.currentlevel)"
|
||||
:style="{ width: `${supply.currentlevel || 0}%` }"
|
||||
:class="supply.status"
|
||||
:style="{ width: `${supply.level || 0}%` }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="supply-meta">
|
||||
<span>{{ supply.supplytypename }}</span>
|
||||
<span v-if="supply.partnumber">Part: {{ supply.partnumber }}</span>
|
||||
<span>{{ formatSupplyType(supply.supplytype) }}<template v-if="supply.iswaste"> ({{ supply.remaining }}% remaining)</template></span>
|
||||
<span v-if="supply.partnumbers && supply.partnumbers.length" class="supply-parts">
|
||||
<span
|
||||
v-for="part in supply.partnumbers"
|
||||
:key="part.partnumber"
|
||||
class="supply-part"
|
||||
:data-tip="`Part ${part.partnumber}` + (part.pageyield ? ` - ${part.pageyield} pages` : '')"
|
||||
>{{ part.marketingname || part.partnumber }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,14 +245,26 @@ import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { printersApi } from '../../api'
|
||||
import LocationMapTooltip from '../../components/LocationMapTooltip.vue'
|
||||
import { useIdentifierFlags } from '../../composables/identifierSettings'
|
||||
|
||||
const route = useRoute()
|
||||
const identifierflags = useIdentifierFlags()
|
||||
|
||||
const loading = ref(true)
|
||||
const printer = ref(null)
|
||||
const supplies = ref([])
|
||||
const drivers = ref([])
|
||||
|
||||
// Best display identifier for a printer. name is often the literal "NONE",
|
||||
// so fall back to the Windows name, then hostname, then asset number.
|
||||
const displayTitle = computed(() => {
|
||||
const p = printer.value
|
||||
if (!p) return ''
|
||||
const name = (p.name || '').trim()
|
||||
if (name && name.toUpperCase() !== 'NONE') return name
|
||||
return p.printer?.windowsname || p.printer?.hostname || p.assetnumber
|
||||
})
|
||||
|
||||
// Get IP address from communications
|
||||
const ipAddress = computed(() => {
|
||||
if (!printer.value?.communications) return null
|
||||
@@ -262,7 +281,8 @@ onMounted(async () => {
|
||||
])
|
||||
|
||||
printer.value = printerRes.data.data
|
||||
supplies.value = suppliesRes.data.data || []
|
||||
// supplies endpoint returns {ipaddress, pingstatus, supplies:[...]}
|
||||
supplies.value = suppliesRes.data.data?.supplies || []
|
||||
drivers.value = driversRes.data.data || []
|
||||
} catch (error) {
|
||||
console.error('Error loading printer:', error)
|
||||
@@ -280,11 +300,9 @@ function getStatusClass(status) {
|
||||
return 'badge-info'
|
||||
}
|
||||
|
||||
function getSupplyLevelClass(level) {
|
||||
if (level === null || level === undefined) return ''
|
||||
if (level <= 10) return 'critical'
|
||||
if (level <= 25) return 'low'
|
||||
return 'ok'
|
||||
function formatSupplyType(supplytype) {
|
||||
if (!supplytype) return ''
|
||||
return supplytype.charAt(0).toUpperCase() + supplytype.slice(1)
|
||||
}
|
||||
|
||||
function formatDate(dateStr) {
|
||||
@@ -360,4 +378,33 @@ function formatDate(dateStr) {
|
||||
color: var(--text-light);
|
||||
margin-top: 0.625rem;
|
||||
}
|
||||
|
||||
.supply-parts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.supply-part {
|
||||
cursor: help;
|
||||
border-bottom: 1px dotted var(--text-light);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* instant CSS tooltip, no native title delay */
|
||||
.supply-part:hover::after {
|
||||
content: attr(data-tip);
|
||||
position: absolute;
|
||||
bottom: 125%;
|
||||
right: 0;
|
||||
white-space: nowrap;
|
||||
background: var(--text);
|
||||
color: var(--bg-card);
|
||||
padding: 0.35rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user