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

@@ -254,7 +254,8 @@
import { ref, onMounted, computed } from 'vue'
import { colorStyle } from "@/utils/colorStyle"
import { useRoute } from 'vue-router'
import { computersApi, applicationsApi, assetsApi, warrantyApi } from '../../api'
import { computersApi, applicationsApi, assetsApi } from '../../api'
import { useWarrantyBadge } from '../../composables/warrantyBadge'
import LocationMapTooltip from '../../components/LocationMapTooltip.vue'
import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
import WarrantyPanel from '../../components/WarrantyPanel.vue'
@@ -267,21 +268,9 @@ const loading = ref(true)
const computer = ref(null)
const relationships = ref({ incoming: [], outgoing: [] })
const installedApps = ref([])
const warranties = ref([])
// Worst-case warranty for the hero badge: expired > expiring > active > unknown.
const heroWarranty = computed(() => {
if (!warranties.value.length) return null
const rank = { expired: 0, expiring: 1, active: 2, unknown: 3 }
const worst = [...warranties.value].sort((a, b) => (rank[a.status] ?? 9) - (rank[b.status] ?? 9))[0]
const labels = { active: 'Under Warranty', expiring: 'Warranty Expiring', expired: 'Warranty Expired', unknown: 'Warranty' }
return { ...worst, label: labels[worst.status] || 'Warranty' }
})
function formatWarrantyDate(d) {
if (!d) return ''
return new Date(d + 'T00:00:00').toLocaleDateString()
}
// Warranty fetch + hero badge (shared across asset detail pages).
const { warranties, heroWarranty, warrantyDate: formatWarrantyDate } = useWarrantyBadge(() => computer.value?.assetid)
const controlledEquipment = computed(() => {
// For computers, find related equipment in any "Controls" relationship
@@ -334,16 +323,7 @@ onMounted(async () => {
} catch (appError) {
console.log('No installed apps data:', appError.message)
}
// Load warranties (one fetch; feeds both the hero badge and the panel)
if (computer.value?.assetid) {
try {
const warrantyResponse = await warrantyApi.forAsset(computer.value.assetid)
warranties.value = warrantyResponse.data.data || []
} catch (warrantyError) {
console.log('No warranty data:', warrantyError.message)
}
}
// Warranties load via useWarrantyBadge (watches computer.assetid).
} catch (error) {
console.error('Error loading computer:', error)
} finally {

File diff suppressed because it is too large Load Diff