Warranty polish: hero badges everywhere, search/pagination, re-check, shared apiError

- Shared utils/apiError.js reads the correct nested error message (with
  fallbacks); swept 37 views/components off the shallow path so real backend
  messages (e.g. in-use 409s) surface instead of generic "Failed".
- useWarrantyBadge composable: warranty hero status/end-date badge now on PC,
  equipment, printer, and network detail heroes (one shared fetch feeds the
  badge + the WarrantyPanel).
- Warranties list: client-side search (vendor/level/tag/asset) + pagination;
  truncate long service levels so they stop blowing out the table width.
- "Re-check all" button + POST /warranty/sync/dell?all=true to re-pull dated
  Dell warranties, not just missing ones.
- Deprecate the standalone pxe-images/warranty_sync.py in favor of the plugin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-09 17:23:21 -04:00
parent 640b8de1b2
commit ec2de635ed
46 changed files with 4091 additions and 3968 deletions

View File

@@ -30,6 +30,10 @@
<span class="badge badge-lg" :class="getStatusClass(equipment.statusname)">
{{ equipment.statusname || 'Unknown' }}
</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="equipment.equipment?.equipmenttypename">
@@ -207,7 +211,7 @@
<CustomFieldsSection :assetid="equipment.assetid" />
<!-- Warranty -->
<WarrantyPanel :assetid="equipment.assetid" />
<WarrantyPanel :assetid="equipment.assetid" :items="warranties" />
<!-- Notes -->
<div class="section-card" v-if="equipment.notes">
@@ -237,6 +241,7 @@ import { equipmentApi, assetsApi } from '../../api'
import LocationMapTooltip from '../../components/LocationMapTooltip.vue'
import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
import WarrantyPanel from '../../components/WarrantyPanel.vue'
import { useWarrantyBadge } from '../../composables/warrantyBadge'
import { useIdentifierFlags } from '../../composables/identifierSettings'
const route = useRoute()
@@ -244,6 +249,7 @@ const { isEnabled } = useIdentifierFlags()
const loading = ref(true)
const equipment = ref(null)
const { warranties, heroWarranty, warrantyDate } = useWarrantyBadge(() => equipment.value?.assetid)
const relationships = ref({ incoming: [], outgoing: [] })
const controllingPc = computed(() => {

View File

@@ -353,6 +353,7 @@ import Modal from '../../components/Modal.vue'
import CustomFieldsInputs from '../../components/CustomFieldsInputs.vue'
import { currentTheme } from '../../stores/theme'
import { useIdentifierFlags } from '../../composables/identifierSettings'
import { apiError } from '../../utils/apiError'
const { isEnabled } = useIdentifierFlags()
@@ -606,7 +607,7 @@ async function saveEquipment() {
router.push(`/machines/${savedEquipment.equipment?.equipmentid || route.params.id}`)
} catch (err) {
console.error('Error saving equipment:', err)
error.value = err.response?.data?.message || 'Failed to save equipment'
error.value = apiError(err, 'Failed to save equipment')
} finally {
saving.value = false
}