diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 4d05410..80f69d0 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -970,8 +970,8 @@ export const warrantyApi = { refresh(id) { return api.post(`/warranty/${id}/refresh`) }, - syncDell() { - return api.post('/warranty/sync/dell') + syncDell(all = false) { + return api.post('/warranty/sync/dell', null, { params: all ? { all: 'true' } : {} }) }, report() { return api.get('/warranty/report') diff --git a/frontend/src/components/AssetRelationships.vue b/frontend/src/components/AssetRelationships.vue index 12a0b00..c01a1bb 100644 --- a/frontend/src/components/AssetRelationships.vue +++ b/frontend/src/components/AssetRelationships.vue @@ -178,6 +178,7 @@ import { assetsApi, relationshipTypesApi } from '../api' import { colorStyle } from '@/utils/colorStyle' import { useAuthStore } from '../stores/auth' import { useToast } from '../composables/toast' +import { apiError } from '../utils/apiError' const toast = useToast() const props = defineProps({ @@ -351,7 +352,7 @@ async function saveRelationship() { emit('updated') } catch (error) { console.error('Failed to create relationship:', error) - toast.error('Failed to create relationship: ' + (error.response?.data?.message || error.message)) + toast.error(apiError(error, 'Failed to create relationship')) } } diff --git a/frontend/src/composables/warrantyBadge.js b/frontend/src/composables/warrantyBadge.js new file mode 100644 index 0000000..9fa2b11 --- /dev/null +++ b/frontend/src/composables/warrantyBadge.js @@ -0,0 +1,44 @@ +// Shared warranty fetch + hero-badge derivation for asset detail pages. +// Pass a getter for the asset id; get back the warranties list (to feed a +// WarrantyPanel) and a heroWarranty for a compact status badge. +import { ref, computed, watch } from 'vue' +import { warrantyApi } from '../api' + +const STATUS_RANK = { expired: 0, expiring: 1, active: 2, unknown: 3 } +const STATUS_LABELS = { + active: 'Under Warranty', + expiring: 'Warranty Expiring', + expired: 'Warranty Expired', + unknown: 'Warranty', +} + +export function useWarrantyBadge(getAssetId) { + const warranties = ref([]) + + // Worst-case warranty drives the badge: expired > expiring > active > unknown. + const heroWarranty = computed(() => { + if (!warranties.value.length) return null + const worst = [...warranties.value] + .sort((a, b) => (STATUS_RANK[a.status] ?? 9) - (STATUS_RANK[b.status] ?? 9))[0] + return { ...worst, label: STATUS_LABELS[worst.status] || 'Warranty' } + }) + + function warrantyDate(d) { + return d ? new Date(d + 'T00:00:00').toLocaleDateString() : '' + } + + async function load() { + const assetid = getAssetId() + if (!assetid) { warranties.value = []; return } + try { + const response = await warrantyApi.forAsset(assetid) + warranties.value = response.data.data || [] + } catch (err) { + warranties.value = [] + } + } + + watch(getAssetId, load, { immediate: true }) + + return { warranties, heroWarranty, warrantyDate, reloadWarranties: load } +} diff --git a/frontend/src/utils/apiError.js b/frontend/src/utils/apiError.js new file mode 100644 index 0000000..0a3ed65 --- /dev/null +++ b/frontend/src/utils/apiError.js @@ -0,0 +1,9 @@ +// Extract a human-readable message from an API error. The backend nests the +// message at data.data.error.message (see error_response); older call sites read +// shallower paths, so try them all before falling back. +export function apiError(err, fallback = 'Something went wrong') { + return err?.response?.data?.data?.error?.message + || err?.response?.data?.error?.message + || err?.response?.data?.message + || fallback +} diff --git a/frontend/src/views/applications/ApplicationForm.vue b/frontend/src/views/applications/ApplicationForm.vue index 0594338..17e6371 100644 --- a/frontend/src/views/applications/ApplicationForm.vue +++ b/frontend/src/views/applications/ApplicationForm.vue @@ -1,278 +1,279 @@ - - - - - + + + + + diff --git a/frontend/src/views/knowledgebase/KnowledgeBaseForm.vue b/frontend/src/views/knowledgebase/KnowledgeBaseForm.vue index 1cd22a1..2726e4a 100644 --- a/frontend/src/views/knowledgebase/KnowledgeBaseForm.vue +++ b/frontend/src/views/knowledgebase/KnowledgeBaseForm.vue @@ -1,166 +1,167 @@ - - - - - + + + + + diff --git a/frontend/src/views/machines/MachineDetail.vue b/frontend/src/views/machines/MachineDetail.vue index 9be8f32..cc1f896 100644 --- a/frontend/src/views/machines/MachineDetail.vue +++ b/frontend/src/views/machines/MachineDetail.vue @@ -30,6 +30,10 @@ {{ equipment.statusname || 'Unknown' }} + + {{ heroWarranty.label }} +
@@ -207,7 +211,7 @@ - +
@@ -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(() => { diff --git a/frontend/src/views/machines/MachineForm.vue b/frontend/src/views/machines/MachineForm.vue index 04b11dd..d6bc1bf 100644 --- a/frontend/src/views/machines/MachineForm.vue +++ b/frontend/src/views/machines/MachineForm.vue @@ -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 } diff --git a/frontend/src/views/network/NetworkDeviceDetail.vue b/frontend/src/views/network/NetworkDeviceDetail.vue index 2cab592..fa6327c 100644 --- a/frontend/src/views/network/NetworkDeviceDetail.vue +++ b/frontend/src/views/network/NetworkDeviceDetail.vue @@ -27,6 +27,10 @@ {{ device.networkdevice.vendorname }} + + {{ heroWarranty.label }} +
@@ -91,7 +95,7 @@ - +
@@ -198,6 +202,7 @@ import { networkApi } from '../../api' import AssetRelationships from '../../components/AssetRelationships.vue' import CustomFieldsSection from '../../components/CustomFieldsSection.vue' import WarrantyPanel from '../../components/WarrantyPanel.vue' +import { useWarrantyBadge } from '../../composables/warrantyBadge' import { useIdentifierFlags } from '../../composables/identifierSettings' import { useToast } from '../../composables/toast' const toast = useToast() @@ -210,6 +215,7 @@ const authStore = useAuthStore() const deviceId = route.params.id const device = ref(null) +const { warranties, heroWarranty, warrantyDate } = useWarrantyBadge(() => device.value?.assetid) const loading = ref(true) onMounted(async () => { diff --git a/frontend/src/views/network/NetworkDeviceForm.vue b/frontend/src/views/network/NetworkDeviceForm.vue index e7cf12d..b3a0a8c 100644 --- a/frontend/src/views/network/NetworkDeviceForm.vue +++ b/frontend/src/views/network/NetworkDeviceForm.vue @@ -1,551 +1,552 @@ - - - - - + + + + + diff --git a/frontend/src/views/notifications/NotificationForm.vue b/frontend/src/views/notifications/NotificationForm.vue index a23f561..d985791 100644 --- a/frontend/src/views/notifications/NotificationForm.vue +++ b/frontend/src/views/notifications/NotificationForm.vue @@ -1,641 +1,642 @@ - - - - - + + + + + diff --git a/frontend/src/views/notifications/NotificationTypesList.vue b/frontend/src/views/notifications/NotificationTypesList.vue index e7058e2..6d47777 100644 --- a/frontend/src/views/notifications/NotificationTypesList.vue +++ b/frontend/src/views/notifications/NotificationTypesList.vue @@ -139,6 +139,7 @@ import { ref, onMounted } from 'vue' import { notificationsApi } from '@/api' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' +import { apiError } from '../../utils/apiError' const types = ref([]) const loading = ref(true) @@ -238,7 +239,7 @@ async function save() { editing.value = false await load() } catch (err) { - error.value = err.response?.data?.error?.message || err.response?.data?.message || 'Save failed.' + error.value = apiError(err, 'Save failed.') } finally { saving.value = false } diff --git a/frontend/src/views/pcs/PCDetail.vue b/frontend/src/views/pcs/PCDetail.vue index 6dd31e3..368ef1c 100644 --- a/frontend/src/views/pcs/PCDetail.vue +++ b/frontend/src/views/pcs/PCDetail.vue @@ -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 { diff --git a/frontend/src/views/pcs/PCForm.vue b/frontend/src/views/pcs/PCForm.vue index e9f4e89..f42bb7d 100644 --- a/frontend/src/views/pcs/PCForm.vue +++ b/frontend/src/views/pcs/PCForm.vue @@ -1,608 +1,609 @@ - - - - - + + + + + diff --git a/frontend/src/views/printers/PrinterDetail.vue b/frontend/src/views/printers/PrinterDetail.vue index e7588d5..77e27b9 100644 --- a/frontend/src/views/printers/PrinterDetail.vue +++ b/frontend/src/views/printers/PrinterDetail.vue @@ -25,6 +25,10 @@ CSF Color Network + + {{ heroWarranty.label }} +
@@ -121,7 +125,7 @@ - +
@@ -263,6 +267,7 @@ import { printersApi } 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() @@ -270,6 +275,7 @@ const { isEnabled } = useIdentifierFlags() const loading = ref(true) const printer = ref(null) +const { warranties, heroWarranty, warrantyDate } = useWarrantyBadge(() => printer.value?.assetid) const supplies = ref([]) const drivers = ref([]) diff --git a/frontend/src/views/printers/PrinterForm.vue b/frontend/src/views/printers/PrinterForm.vue index 788c22c..2999031 100644 --- a/frontend/src/views/printers/PrinterForm.vue +++ b/frontend/src/views/printers/PrinterForm.vue @@ -1,651 +1,652 @@ - - - - - + + + + + diff --git a/frontend/src/views/reports/WarrantyReport.vue b/frontend/src/views/reports/WarrantyReport.vue index 3cccc4f..00f63ba 100644 --- a/frontend/src/views/reports/WarrantyReport.vue +++ b/frontend/src/views/reports/WarrantyReport.vue @@ -33,7 +33,7 @@ {{ w.vendor }} - {{ w.servicelevel || '-' }} + {{ w.servicelevel || '-' }} {{ w.enddate ? formatDate(w.enddate) : '-' }} {{ a.assetnumber }} @@ -100,4 +100,5 @@ onMounted(async () => { background: var(--bg); border-radius: 12px; font-size: 0.8rem; text-decoration: none; color: var(--text); } .muted { color: var(--text-light); } +.servicelevel-cell { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/frontend/src/views/settings/AccessProtocolsList.vue b/frontend/src/views/settings/AccessProtocolsList.vue index 14c571f..ec4b71b 100644 --- a/frontend/src/views/settings/AccessProtocolsList.vue +++ b/frontend/src/views/settings/AccessProtocolsList.vue @@ -92,6 +92,7 @@ diff --git a/frontend/src/views/settings/DashboardDefaultsList.vue b/frontend/src/views/settings/DashboardDefaultsList.vue index 7b0653b..17a9b64 100644 --- a/frontend/src/views/settings/DashboardDefaultsList.vue +++ b/frontend/src/views/settings/DashboardDefaultsList.vue @@ -105,6 +105,7 @@ import { ref, onMounted } from 'vue' import { dashboardDefaultsApi, businessUnitsApi } from '../../api' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -168,7 +169,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } diff --git a/frontend/src/views/settings/EquipmentTypesList.vue b/frontend/src/views/settings/EquipmentTypesList.vue index 6b4d2eb..29c901b 100644 --- a/frontend/src/views/settings/EquipmentTypesList.vue +++ b/frontend/src/views/settings/EquipmentTypesList.vue @@ -77,6 +77,7 @@ import { equipmentApi } from '../../api' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import { colorStyle } from '@/utils/colorStyle' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -126,7 +127,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.error?.message || err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } @@ -138,7 +139,7 @@ async function deleteType(t) { await equipmentApi.types.remove(t.equipmenttypeid) loadData() } catch (err) { - toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') + toast.error(apiError(err, 'Failed to delete')) } } diff --git a/frontend/src/views/settings/LocationTypesList.vue b/frontend/src/views/settings/LocationTypesList.vue index 616b262..c71115b 100644 --- a/frontend/src/views/settings/LocationTypesList.vue +++ b/frontend/src/views/settings/LocationTypesList.vue @@ -77,6 +77,7 @@ import { locationsApi } from '../../api' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import { colorStyle } from '@/utils/colorStyle' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -126,7 +127,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.error?.message || err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } @@ -138,7 +139,7 @@ async function deleteType(t) { await locationsApi.types.remove(t.locationtypeid) loadData() } catch (err) { - toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') + toast.error(apiError(err, 'Failed to delete')) } } diff --git a/frontend/src/views/settings/LocationsList.vue b/frontend/src/views/settings/LocationsList.vue index 2ac8a98..492ff07 100644 --- a/frontend/src/views/settings/LocationsList.vue +++ b/frontend/src/views/settings/LocationsList.vue @@ -212,6 +212,7 @@ import { ref, computed, onMounted } from 'vue' import { locationsApi } from '../../api' import PaginationBar from '../../components/PaginationBar.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const locations = ref([]) @@ -354,7 +355,7 @@ async function saveLocation() { loadLocations() } catch (err) { console.error('Error saving location:', err) - error.value = err.response?.data?.message || 'Failed to save location' + error.value = apiError(err, 'Failed to save location') } finally { saving.value = false } diff --git a/frontend/src/views/settings/MachineTypesList.vue b/frontend/src/views/settings/MachineTypesList.vue index 16ca6b5..84ec488 100644 --- a/frontend/src/views/settings/MachineTypesList.vue +++ b/frontend/src/views/settings/MachineTypesList.vue @@ -146,6 +146,7 @@ import { ref, onMounted } from 'vue' import { machinetypesApi } from '../../api' import PaginationBar from '../../components/PaginationBar.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const machineTypes = ref([]) @@ -239,7 +240,7 @@ async function saveType() { loadTypes() } catch (err) { console.error('Error saving machine type:', err) - error.value = err.response?.data?.message || 'Failed to save machine type' + error.value = apiError(err, 'Failed to save machine type') } finally { saving.value = false } diff --git a/frontend/src/views/settings/ModelSuppliesList.vue b/frontend/src/views/settings/ModelSuppliesList.vue index 41497fd..421959e 100644 --- a/frontend/src/views/settings/ModelSuppliesList.vue +++ b/frontend/src/views/settings/ModelSuppliesList.vue @@ -147,6 +147,7 @@ diff --git a/frontend/src/views/settings/OperatingSystemsList.vue b/frontend/src/views/settings/OperatingSystemsList.vue index 4d07911..9dd56d0 100644 --- a/frontend/src/views/settings/OperatingSystemsList.vue +++ b/frontend/src/views/settings/OperatingSystemsList.vue @@ -120,6 +120,7 @@ import { ref, onMounted } from 'vue' import { operatingsystemsApi } from '../../api' import PaginationBar from '../../components/PaginationBar.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -193,7 +194,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } diff --git a/frontend/src/views/settings/PCTypesList.vue b/frontend/src/views/settings/PCTypesList.vue index 61e2871..0118bfd 100644 --- a/frontend/src/views/settings/PCTypesList.vue +++ b/frontend/src/views/settings/PCTypesList.vue @@ -87,6 +87,7 @@ import { computersApi } from '../../api' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import { colorStyle } from '@/utils/colorStyle' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const pcTypes = ref([]) @@ -132,7 +133,7 @@ async function deleteType(pt) { await computersApi.types.remove(pt.computertypeid) loadData() } catch (err) { - toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') + toast.error(apiError(err, 'Failed to delete')) } } @@ -148,7 +149,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } diff --git a/frontend/src/views/settings/PrinterDriversList.vue b/frontend/src/views/settings/PrinterDriversList.vue index 3eb5b52..efa2d14 100644 --- a/frontend/src/views/settings/PrinterDriversList.vue +++ b/frontend/src/views/settings/PrinterDriversList.vue @@ -98,6 +98,7 @@ import { ref, computed, onMounted } from 'vue' import { printersApi } from '../../api' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -161,7 +162,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.error?.message || err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } @@ -173,7 +174,7 @@ async function deleteDriver(d) { await printersApi.drivers.delete(d.driverid) loadData() } catch (err) { - toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') + toast.error(apiError(err, 'Failed to delete')) } } diff --git a/frontend/src/views/settings/PrinterTypesList.vue b/frontend/src/views/settings/PrinterTypesList.vue index ef05974..7510cd0 100644 --- a/frontend/src/views/settings/PrinterTypesList.vue +++ b/frontend/src/views/settings/PrinterTypesList.vue @@ -77,6 +77,7 @@ import { printersApi } from '../../api' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import { colorStyle } from '@/utils/colorStyle' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -126,7 +127,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.error?.message || err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } @@ -138,7 +139,7 @@ async function deleteType(t) { await printersApi.types.remove(t.printertypeid) loadData() } catch (err) { - toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') + toast.error(apiError(err, 'Failed to delete')) } } diff --git a/frontend/src/views/settings/RelationshipTypesList.vue b/frontend/src/views/settings/RelationshipTypesList.vue index 8a79490..3615c23 100644 --- a/frontend/src/views/settings/RelationshipTypesList.vue +++ b/frontend/src/views/settings/RelationshipTypesList.vue @@ -77,6 +77,7 @@ import { relationshipTypesApi } from '../../api' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import { colorStyle } from '@/utils/colorStyle' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const items = ref([]) @@ -126,7 +127,7 @@ async function save() { closeModal() loadData() } catch (err) { - error.value = err.response?.data?.error?.message || err.response?.data?.message || 'Failed to save' + error.value = apiError(err, 'Failed to save') } finally { saving.value = false } @@ -138,7 +139,7 @@ async function deleteType(t) { await relationshipTypesApi.remove(t.relationshiptypeid) loadData() } catch (err) { - toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') + toast.error(apiError(err, 'Failed to delete')) } } diff --git a/frontend/src/views/settings/SiteSettings.vue b/frontend/src/views/settings/SiteSettings.vue index 4501f11..00d5f69 100644 --- a/frontend/src/views/settings/SiteSettings.vue +++ b/frontend/src/views/settings/SiteSettings.vue @@ -31,6 +31,7 @@ diff --git a/frontend/src/views/settings/SystemSettings.vue b/frontend/src/views/settings/SystemSettings.vue index 366631a..c117ba1 100644 --- a/frontend/src/views/settings/SystemSettings.vue +++ b/frontend/src/views/settings/SystemSettings.vue @@ -657,6 +657,7 @@ import { ref, reactive, onMounted, computed, watch } from 'vue' import { useRoute } from 'vue-router' import { settingsApi, computersApi } from '../../api' import { setIdentifierFlag } from '../../composables/identifierSettings' +import { apiError } from '../../utils/apiError' // Section tabs - one section visible at a time to avoid a long scroll. The // search box filters tabs (and, while searching, shows every matching section). @@ -900,7 +901,7 @@ async function changePcTypeMapping(pxetype, computertype) { success.value = 'Setting saved' setTimeout(() => { success.value = '' }, 2000) } catch (e) { - error.value = e.response?.data?.message || 'Failed to save setting' + error.value = apiError(e, 'Failed to save setting') console.error(e) } finally { saving.value = false @@ -943,7 +944,7 @@ async function toggleIdentifier(name, assettype) { success.value = 'Setting saved' setTimeout(() => { success.value = '' }, 2000) } catch (e) { - error.value = e.response?.data?.message || 'Failed to save setting' + error.value = apiError(e, 'Failed to save setting') console.error(e) } finally { saving.value = false @@ -977,7 +978,7 @@ async function toggleSearchDomain(domainKey) { success.value = 'Setting saved' setTimeout(() => { success.value = '' }, 2000) } catch (e) { - error.value = e.response?.data?.message || 'Failed to save setting' + error.value = apiError(e, 'Failed to save setting') console.error(e) } finally { saving.value = false @@ -996,7 +997,7 @@ async function saveSetting(key, value) { success.value = 'Setting saved' setTimeout(() => { success.value = '' }, 2000) } catch (e) { - error.value = e.response?.data?.message || 'Failed to save setting' + error.value = apiError(e, 'Failed to save setting') console.error(e) } finally { saving.value = false @@ -1013,7 +1014,7 @@ async function testEmail() { // await settingsApi.testEmail() success.value = 'Test email feature coming soon' } catch (e) { - error.value = e.response?.data?.message || 'Failed to send test email' + error.value = apiError(e, 'Failed to send test email') } finally { testingEmail.value = false setTimeout(() => { success.value = '' }, 3000) diff --git a/frontend/src/views/settings/UsersList.vue b/frontend/src/views/settings/UsersList.vue index 6b6b8c4..f9dc557 100644 --- a/frontend/src/views/settings/UsersList.vue +++ b/frontend/src/views/settings/UsersList.vue @@ -1,745 +1,746 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/VLANsList.vue b/frontend/src/views/settings/VLANsList.vue index 4f423ae..66f505e 100644 --- a/frontend/src/views/settings/VLANsList.vue +++ b/frontend/src/views/settings/VLANsList.vue @@ -188,6 +188,7 @@ import { ref, onMounted } from 'vue' import { networkApi } from '../../api' import PaginationBar from '../../components/PaginationBar.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const vlans = ref([]) @@ -309,7 +310,7 @@ async function saveVLAN() { loadVLANs() } catch (err) { console.error('Error saving VLAN:', err) - error.value = err.response?.data?.message || 'Failed to save VLAN' + error.value = apiError(err, 'Failed to save VLAN') } finally { saving.value = false } @@ -328,7 +329,7 @@ async function deleteVLAN() { loadVLANs() } catch (err) { console.error('Error deleting VLAN:', err) - toast.error(err.response?.data?.message || 'Failed to delete VLAN') + toast.error(apiError(err, 'Failed to delete VLAN')) } } diff --git a/frontend/src/views/usb/USBDetail.vue b/frontend/src/views/usb/USBDetail.vue index 7a848f3..dd0a9de 100644 --- a/frontend/src/views/usb/USBDetail.vue +++ b/frontend/src/views/usb/USBDetail.vue @@ -169,6 +169,7 @@ import { useRoute } from 'vue-router' import { usbApi } from '../../api' import Modal from '../../components/Modal.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const route = useRoute() @@ -219,7 +220,7 @@ async function doCheckout() { await loadDevice() } catch (error) { console.error('Checkout error:', error) - toast.error(error.response?.data?.message || 'Checkout failed') + toast.error(apiError(error, 'Checkout failed')) } } @@ -230,7 +231,7 @@ async function doCheckin() { await loadDevice() } catch (error) { console.error('Checkin error:', error) - toast.error(error.response?.data?.message || 'Check in failed') + toast.error(apiError(error, 'Check in failed')) } } diff --git a/frontend/src/views/usb/USBForm.vue b/frontend/src/views/usb/USBForm.vue index 78c24ef..8b92658 100644 --- a/frontend/src/views/usb/USBForm.vue +++ b/frontend/src/views/usb/USBForm.vue @@ -1,230 +1,231 @@ - - - - - + + + + + diff --git a/frontend/src/views/usb/USBList.vue b/frontend/src/views/usb/USBList.vue index c6aa94a..469eb8d 100644 --- a/frontend/src/views/usb/USBList.vue +++ b/frontend/src/views/usb/USBList.vue @@ -148,6 +148,7 @@ import Modal from '../../components/Modal.vue' import EmployeeSearch from '../../components/EmployeeSearch.vue' import PaginationBar from '../../components/PaginationBar.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const devices = ref([]) @@ -243,7 +244,7 @@ async function doCheckout() { loadDevices() } catch (error) { console.error('Checkout error:', error) - toast.error(error.response?.data?.message || 'Checkout failed') + toast.error(apiError(error, 'Checkout failed')) } } @@ -254,7 +255,7 @@ async function doCheckin() { loadDevices() } catch (error) { console.error('Checkin error:', error) - toast.error(error.response?.data?.message || 'Check in failed') + toast.error(apiError(error, 'Check in failed')) } } diff --git a/frontend/src/views/vendors/VendorsList.vue b/frontend/src/views/vendors/VendorsList.vue index 6f63483..3697352 100644 --- a/frontend/src/views/vendors/VendorsList.vue +++ b/frontend/src/views/vendors/VendorsList.vue @@ -187,6 +187,7 @@ import { ref, onMounted } from 'vue' import { vendorsApi } from '../../api' import PaginationBar from '../../components/PaginationBar.vue' import { useToast } from '../../composables/toast' +import { apiError } from '../../utils/apiError' const toast = useToast() const vendors = ref([]) @@ -304,7 +305,7 @@ async function saveVendor() { loadVendors() } catch (err) { console.error('Error saving vendor:', err) - error.value = err.response?.data?.message || 'Failed to save vendor' + error.value = apiError(err, 'Failed to save vendor') } finally { saving.value = false } diff --git a/frontend/src/views/warranty/WarrantiesList.vue b/frontend/src/views/warranty/WarrantiesList.vue index f6d3e69..c01f4ea 100644 --- a/frontend/src/views/warranty/WarrantiesList.vue +++ b/frontend/src/views/warranty/WarrantiesList.vue @@ -3,14 +3,17 @@ -
+ + {{ filteredItems.length }} of {{ items.length }}
@@ -39,10 +44,10 @@ - + {{ w.vendor }} ({{ w.provider }}) {{ statusLabel(w.status) }} - {{ w.servicelevel || '-' }} + {{ w.servicelevel || '-' }} {{ w.enddate ? formatDate(w.enddate) : '-' }} - @@ -54,12 +59,18 @@ - + No warranties
+ +
@@ -141,11 +152,12 @@