The retire dialog says "History and relationships are kept, and it can be
restored." The API always kept that promise - ?active=false lists retired rows,
PUT {"isactive": true} restores one - and no screen in the product could reach
either. A retired asset simply vanished: the lists filtered it out, global
search missed it, and its detail page still rendered Edit and Retire with
nothing anywhere saying it was retired.
Five list pages gained a "Show retired" toggle that passes active=false, and a
retired row now carries a badge. Five detail pages gained a banner saying the
record is retired and what that means, a Restore button, and the suppression of
Edit and Retire while it is retired - offering Retire on an already-retired
record was the part that made the state invisible.
Verified end to end in a browser: retire a machine, the detail page says so and
offers Restore, the list hides it until the toggle is ticked and badges it when
shown, and Restore puts it back (isactive true again on the API).
One thing this does NOT do: global search still does not reach a retired asset.
That is a separate surface with its own filter, and worth deciding on
deliberately rather than sliding in here - a search that returns retired
records by default would change every result page.
Scope: product
556 lines
22 KiB
Vue
556 lines
22 KiB
Vue
<template>
|
|
<div class="detail-page">
|
|
<div class="page-header">
|
|
<h2>Printer Details</h2>
|
|
<div class="header-actions">
|
|
<router-link :to="`/print/printer-qr/${$route.params.id}`" class="btn btn-secondary" target="_blank">
|
|
Print QR
|
|
</router-link>
|
|
<router-link :to="`/print/asset-label/printer/${$route.params.id}`" class="btn btn-secondary" target="_blank">
|
|
Print Label
|
|
</router-link>
|
|
<router-link :to="`/printers/${$route.params.id}/edit`" class="btn btn-primary">Edit</router-link>
|
|
<button v-if="!isRetired" @click="confirmRetire" class="btn btn-danger">Retire</button>
|
|
<!-- A retired record offered Edit and Retire exactly like a live
|
|
one, and nothing said it was retired. Restoring it was possible
|
|
through the API and nowhere in the product. -->
|
|
<button v-if="isRetired" @click="restore" class="btn btn-primary">Restore</button>
|
|
<router-link to="/printers" class="btn btn-secondary">Back to List</router-link>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="printer && printer.isactive === false" class="retired-banner card">
|
|
<strong>This printer is retired.</strong>
|
|
It stays out of every list and report, and its history and relationships
|
|
are intact. Restore puts it back.
|
|
</div>
|
|
|
|
<div v-if="loading" class="loading">Loading...</div>
|
|
|
|
<template v-else-if="printer">
|
|
<!-- Hero Section -->
|
|
<div class="hero-card">
|
|
<div class="hero-image" v-if="printer.printer?.imageurl">
|
|
<img :src="withBase(printer.printer.imageurl)" :alt="printer.printer.modelname || 'Model photo'" />
|
|
</div>
|
|
<div class="hero-content">
|
|
<div class="hero-title">
|
|
<h1>{{ displayTitle }}</h1>
|
|
</div>
|
|
<div class="hero-meta">
|
|
<span class="badge badge-lg badge-printer">Printer</span>
|
|
<span v-if="printer.printer?.iscsf" class="badge badge-lg badge-info">CSF</span>
|
|
<span v-if="printer.printer?.iscolor" class="badge badge-lg badge-success">Color</span>
|
|
<span v-if="printer.printer?.isnetwork" class="badge badge-lg badge-secondary">Network</span>
|
|
<span v-if="heroWarranty" class="badge badge-lg" :style="{ background: heroWarranty.statuscolor, color: '#fff' }"
|
|
:title="heroWarranty.enddate ? `Warranty ends ${warrantyDate(heroWarranty.enddate)}` : 'Warranty'">
|
|
{{ heroWarranty.label }}<template v-if="heroWarranty.enddate"> - {{ warrantyDate(heroWarranty.enddate) }}</template>
|
|
</span>
|
|
</div>
|
|
<div class="hero-details">
|
|
<div class="hero-detail" v-if="printer.printer?.vendorname">
|
|
<span class="hero-detail-label">Vendor</span>
|
|
<span class="hero-detail-value">
|
|
{{ printer.printer.vendorname }}
|
|
<!-- Inherited from the catalog model, not stored on this record. -->
|
|
<small v-if="printer.printer?.vendorfrommodel" class="text-muted">(from model)</small>
|
|
</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="printer.printer?.modelname">
|
|
<span class="hero-detail-label">Model</span>
|
|
<span class="hero-detail-value">{{ printer.printer.modelname }}</span>
|
|
</div>
|
|
<!-- Only when it DIFFERS from the printer's own type: the two are
|
|
separate tables but carry the same names in practice. -->
|
|
<div class="hero-detail"
|
|
v-if="printer.printer?.modeltypename && printer.printer.modeltypename !== printer.printer?.printertypename">
|
|
<span class="hero-detail-label">Model type</span>
|
|
<span class="hero-detail-value">{{ printer.printer.modeltypename }}</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="printer.serialnumber">
|
|
<span class="hero-detail-label">Serial Number</span>
|
|
<span class="hero-detail-value mono">{{ printer.serialnumber }}</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="ipAddress">
|
|
<span class="hero-detail-label">IP Address</span>
|
|
<span class="hero-detail-value mono">{{ ipAddress }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- The 6am band: which machine stops if this printer is down.
|
|
A shop-floor printer is often a label or tag printer feeding one
|
|
machine, and that link was only visible in the relationships
|
|
card near the bottom of the page. -->
|
|
<AssetTriageBand
|
|
:assetid="printer.assetid"
|
|
counterparttype="machine"
|
|
:relationshiptypes="['usesprinter', 'defaultprinter']"
|
|
/>
|
|
</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</span>
|
|
<span class="info-value mono">{{ printer.assetnumber }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="printer.printer?.windowsname">
|
|
<span class="info-label">Windows Name</span>
|
|
<span class="info-value mono">{{ printer.printer.windowsname }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="isEnabled('fqdn', 'printer') && printer.printer?.hostname">
|
|
<span class="info-label">Hostname / FQDN</span>
|
|
<span class="info-value mono">{{ printer.printer.hostname }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="printer.printer?.sharename">
|
|
<span class="info-label">CSF Share Name</span>
|
|
<span class="info-value mono">{{ printer.printer.sharename }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="printer.serialnumber">
|
|
<span class="info-label">Serial Number</span>
|
|
<span class="info-value mono">{{ printer.serialnumber }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="isEnabled('gaugelabreference', 'printer') && printer.gaugelabreference">
|
|
<span class="info-label">Gauge Lab Reference</span>
|
|
<span class="info-value mono">{{ printer.gaugelabreference }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="isEnabled('maintenancereference', 'printer') && printer.maintenancereference">
|
|
<span class="info-label">Maintenance Reference</span>
|
|
<span class="info-value mono">{{ printer.maintenancereference }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Printer Settings -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Printer Settings</h3>
|
|
<div class="info-list">
|
|
<div class="info-row" v-if="printer.printer?.installpath">
|
|
<span class="info-label">Install Path</span>
|
|
<span class="info-value mono">{{ printer.printer.installpath }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Color</span>
|
|
<span class="info-value">{{ printer.printer?.iscolor ? 'Yes' : 'No' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Duplex</span>
|
|
<span class="info-value">{{ printer.printer?.isduplex ? 'Yes' : 'No' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Network Printer</span>
|
|
<span class="info-value">{{ printer.printer?.isnetwork ? 'Yes' : 'No' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">CSF Printer</span>
|
|
<span class="info-value">{{ printer.printer?.iscsf ? 'Yes' : 'No' }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="printer.printer?.pin">
|
|
<span class="info-label">PIN</span>
|
|
<span class="info-value mono">{{ printer.printer.pin }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column -->
|
|
<div class="content-column">
|
|
<!-- Network -->
|
|
<div class="section-card" v-if="printer.communications?.length">
|
|
<h3 class="section-title">Network</h3>
|
|
<div class="network-list">
|
|
<div v-for="comm in printer.communications" :key="comm.communicationid" class="network-item">
|
|
<div class="network-primary">
|
|
<span class="ip-address">{{ comm.ipaddress || comm.address || '-' }}</span>
|
|
<span v-if="comm.isprimary" class="primary-badge">Primary</span>
|
|
</div>
|
|
<div class="network-secondary" v-if="comm.macaddress">
|
|
<span class="mac-address">{{ comm.macaddress }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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">Map Location</span>
|
|
<span class="info-value">
|
|
<LocationMapTooltip
|
|
v-if="printer.mapx != null && printer.mapy != null"
|
|
:left="printer.mapx"
|
|
:top="printer.mapy"
|
|
:levelid="printer.levelid"
|
|
:machineName="printer.name || printer.assetnumber"
|
|
>
|
|
<span class="location-link">View on Map</span>
|
|
</LocationMapTooltip>
|
|
<span v-else>Not mapped</span>
|
|
</span>
|
|
</div>
|
|
<div class="info-row" v-if="printer.businessunitname">
|
|
<span class="info-label">Business Unit</span>
|
|
<span class="info-value">{{ printer.businessunitname }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Fields -->
|
|
<CustomFieldsSection :assetid="printer.assetid" />
|
|
|
|
<!-- Warranty -->
|
|
<PluginAssetPanels :assetid="printer.assetid" />
|
|
|
|
<!-- All relationships (defaultprinter, connectedto, ...) -->
|
|
<AssetRelationships v-if="printer.assetid" :assetid="printer.assetid" />
|
|
|
|
<!-- Notes -->
|
|
<div class="section-card" v-if="printer.notes">
|
|
<h3 class="section-title">Notes</h3>
|
|
<p class="notes-text">{{ printer.notes }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Supplies Card -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Supplies</h3>
|
|
</div>
|
|
|
|
<div v-if="supplies.length === 0" class="empty-state">
|
|
No supply information available
|
|
</div>
|
|
|
|
<div v-else class="supplies-grid">
|
|
<div v-for="supply in supplies" :key="supply.itemid || supply.name" class="supply-item">
|
|
<div class="supply-header">
|
|
<span class="supply-name">{{ supply.name }}</span>
|
|
<!-- N/A is not the same as 0%. A supply Zabbix has no usable
|
|
reading for arrives with level null and status 'unknown';
|
|
the title says which of the two reasons it is, because
|
|
"no data" and "last seen three weeks ago" need different
|
|
people to fix them. -->
|
|
<span class="supply-level" :class="supply.status"
|
|
:title="supply.status === 'unknown' ? unknownReason(supply) : null">
|
|
{{ supply.level !== null && supply.level !== undefined ? `${supply.level}%` : 'N/A' }}
|
|
</span>
|
|
</div>
|
|
<div class="supply-bar">
|
|
<div
|
|
class="supply-bar-fill"
|
|
:class="supply.status"
|
|
:style="{ width: `${supply.level || 0}%` }"
|
|
></div>
|
|
</div>
|
|
<div class="supply-meta">
|
|
<span>{{ formatSupplyType(supply.supplytype) }}<template v-if="supply.iswaste && supply.remaining !== null"> ({{ 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>
|
|
</div>
|
|
|
|
<!-- Drivers Card: pulled from the driver catalog by this printer's model -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Drivers</h3>
|
|
</div>
|
|
|
|
<div v-if="drivers.length === 0" class="empty-state">
|
|
No drivers for this model. Add one under Settings > Printer Drivers and
|
|
link it to this printer's model.
|
|
</div>
|
|
|
|
<div v-else class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Location</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="driver in drivers" :key="driver.driverid">
|
|
<td><strong>{{ driver.name }}</strong></td>
|
|
<td>
|
|
<a v-if="isHttp(driver.location)" :href="driver.location" target="_blank" class="mono">{{ driver.location }}</a>
|
|
<span v-else class="mono">{{ driver.location }}</span>
|
|
</td>
|
|
<td>{{ driver.description || '-' }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Footer -->
|
|
<div class="audit-footer">
|
|
<span>Created {{ formatDate(printer.createddate) }}<template v-if="printer.createdby"> by {{ printer.createdby }}</template></span>
|
|
<span>Modified {{ formatDate(printer.modifieddate) }}<template v-if="printer.modifiedby"> by {{ printer.modifiedby }}</template></span>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-else class="card">
|
|
<p style="text-align: center; color: var(--text-light);">Printer not found</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { printersApi } from '@/api'
|
|
import { withBase } from '@/utils/basePath'
|
|
import LocationMapTooltip from '@/components/LocationMapTooltip.vue'
|
|
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 { useWarrantyBadge } from '@/composables/warrantyBadge'
|
|
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 printer = ref(null)
|
|
const { heroWarranty, warrantyDate } = useWarrantyBadge(() => printer.value?.assetid)
|
|
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
|
|
})
|
|
|
|
function isHttp(loc) {
|
|
return typeof loc === 'string' && /^https?:\/\//i.test(loc)
|
|
}
|
|
|
|
// Get IP address from communications
|
|
const ipAddress = computed(() => {
|
|
if (!printer.value?.communications) return null
|
|
const primaryComm = printer.value.communications.find(c => c.isprimary) || printer.value.communications[0]
|
|
return primaryComm?.ipaddress || null
|
|
})
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const [printerRes, suppliesRes] = await Promise.all([
|
|
printersApi.get(route.params.id),
|
|
printersApi.getSupplies(route.params.id).catch(() => ({ data: { data: [] } }))
|
|
])
|
|
|
|
printer.value = printerRes.data.data
|
|
// the page's own heading logic, which already discards the literal
|
|
// name 'NONE' that a discovered queue can carry
|
|
setRecordTitle(displayTitle.value)
|
|
// supplies endpoint returns {ipaddress, pingstatus, supplies:[...]}
|
|
supplies.value = suppliesRes.data.data?.supplies || []
|
|
// drivers are attached to the printer detail, matched by model
|
|
drivers.value = printerRes.data.data?.drivers || []
|
|
} catch (error) {
|
|
console.error('Error loading printer:', error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
function getStatusClass(status) {
|
|
if (!status) return 'badge-info'
|
|
const s = status.toLowerCase()
|
|
if (s === 'in use' || s === 'active' || s === 'online') return 'badge-success'
|
|
if (s === 'in repair' || s === 'offline') return 'badge-warning'
|
|
if (s === 'retired' || s === 'error') return 'badge-danger'
|
|
return 'badge-info'
|
|
}
|
|
|
|
function formatSupplyType(supplytype) {
|
|
if (!supplytype) return ''
|
|
return supplytype.charAt(0).toUpperCase() + supplytype.slice(1)
|
|
}
|
|
|
|
// Why a supply reads N/A. lastseen is the epoch of the newest reading Zabbix
|
|
// holds: absent means the item has never collected, present means it has but
|
|
// the reading is too old to present as current. Those are different faults and
|
|
// the tooltip is the only place either one is stated.
|
|
function unknownReason(supply) {
|
|
if (!supply.lastseen) return 'Zabbix has no reading for this supply'
|
|
return `No reading since ${new Date(supply.lastseen * 1000).toLocaleString()}`
|
|
}
|
|
|
|
function formatDate(dateStr) {
|
|
if (!dateStr) return '-'
|
|
return new Date(dateStr).toLocaleString()
|
|
}
|
|
|
|
// Retire, not delete: the server deactivates the asset, so the printer
|
|
// leaves every list and report while its history and relationships stay put.
|
|
// Reversible by an admin setting isactive back to true.
|
|
const isRetired = computed(() => printer.value?.isactive === false)
|
|
|
|
async function restore() {
|
|
const label = printer.value?.assetnumber || printer.value?.name || 'this printer'
|
|
try {
|
|
await printersApi.update(route.params.id, { isactive: true })
|
|
toast.success(`Restored ${label}`)
|
|
const response = await printersApi.get(route.params.id)
|
|
printer.value = response.data.data
|
|
} catch (error) {
|
|
console.error('Error restoring:', error)
|
|
toast.error(apiError(error, 'Failed to restore'))
|
|
}
|
|
}
|
|
|
|
async function confirmRetire() {
|
|
const label = displayTitle.value || 'this printer'
|
|
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 printersApi.remove(route.params.id)
|
|
toast.success(`Retired ${label}`)
|
|
router.push('/printers')
|
|
} catch (error) {
|
|
console.error('Error retiring printer:', error)
|
|
toast.error(apiError(error, 'Failed to retire printer'))
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.retired-banner {
|
|
border-left: 3px solid var(--warning);
|
|
margin-bottom: 1rem;
|
|
}
|
|
/* Printer-specific styles - shared styles are in global style.css */
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
color: var(--text-light);
|
|
padding: 2.5rem;
|
|
font-size: 1.125rem;
|
|
}
|
|
|
|
/* Supplies */
|
|
.mono { font-family: monospace; font-size: 0.85rem; word-break: break-all; }
|
|
|
|
.supplies-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 1.25rem;
|
|
}
|
|
|
|
.supply-item {
|
|
background: var(--bg);
|
|
padding: 1.25rem;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.supply-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.supply-name {
|
|
font-weight: 500;
|
|
font-size: 1.125rem;
|
|
}
|
|
|
|
.supply-level {
|
|
font-weight: 600;
|
|
font-size: 1.125rem;
|
|
}
|
|
|
|
.supply-level.ok { color: var(--success); }
|
|
.supply-level.low { color: var(--warning); }
|
|
.supply-level.critical { color: var(--danger); }
|
|
/* Muted on purpose. An unknown level is not a severity - colouring it like one
|
|
would put it in the same reading as critical, which is the confusion this
|
|
whole change exists to end. The help cursor points at the tooltip. */
|
|
.supply-level.unknown { color: var(--text-light); cursor: help; }
|
|
|
|
.supply-bar {
|
|
height: 10px;
|
|
background: var(--border);
|
|
border-radius: 5px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.supply-bar-fill {
|
|
height: 100%;
|
|
transition: width 0.3s ease;
|
|
background: var(--success);
|
|
}
|
|
|
|
.supply-bar-fill.low { background: var(--warning); }
|
|
.supply-bar-fill.critical { background: var(--danger); }
|
|
|
|
.supply-meta {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 1rem;
|
|
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>
|