Global toast notifications; replace all alert() calls

Add a useToast() composable + a single ToastHost mounted in AppLayout. Convert
every alert() across views/components (29 call sites, all error paths) to
toast.error, and use toast.success to confirm a warranty refresh. Kills the
native "localhost says" dialog and gives consistent, dismissable feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-09 16:56:25 -04:00
parent b90a13c7e5
commit d6142b10b4
29 changed files with 4774 additions and 4606 deletions

View File

@@ -177,6 +177,8 @@ import { Cog, Monitor, Printer, Globe, Package } from 'lucide-vue-next'
import { assetsApi, relationshipTypesApi } from '../api' import { assetsApi, relationshipTypesApi } from '../api'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useAuthStore } from '../stores/auth' import { useAuthStore } from '../stores/auth'
import { useToast } from '../composables/toast'
const toast = useToast()
const props = defineProps({ const props = defineProps({
assetId: { assetId: {
@@ -349,7 +351,7 @@ async function saveRelationship() {
emit('updated') emit('updated')
} catch (error) { } catch (error) {
console.error('Failed to create relationship:', error) console.error('Failed to create relationship:', error)
alert('Failed to create relationship: ' + (error.response?.data?.message || error.message)) toast.error('Failed to create relationship: ' + (error.response?.data?.message || error.message))
} }
} }
@@ -362,7 +364,7 @@ async function deleteRelationship(relationshipId) {
emit('updated') emit('updated')
} catch (error) { } catch (error) {
console.error('Failed to delete relationship:', error) console.error('Failed to delete relationship:', error)
alert('Failed to delete relationship') toast.error('Failed to delete relationship')
} }
} }

View File

@@ -0,0 +1,75 @@
<template>
<div class="toast-host">
<transition-group name="toast">
<div
v-for="t in toast.items"
:key="t.id"
class="toast"
:class="`toast-${t.type}`"
@click="toast.dismiss(t.id)"
>
<span class="toast-message">{{ t.message }}</span>
<button class="toast-close" @click.stop="toast.dismiss(t.id)" aria-label="Dismiss">&times;</button>
</div>
</transition-group>
</div>
</template>
<script setup>
import { useToast } from '../composables/toast'
const toast = useToast()
</script>
<style scoped>
.toast-host {
position: fixed;
bottom: 1.25rem;
right: 1.25rem;
z-index: 3000;
display: flex;
flex-direction: column;
gap: 0.6rem;
max-width: min(420px, calc(100vw - 2.5rem));
}
.toast {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem 0.9rem;
border-radius: 8px;
background: var(--bg-card);
color: var(--text);
border-left: 4px solid var(--secondary);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
cursor: pointer;
font-size: 0.9rem;
line-height: 1.35;
}
.toast-success { border-left-color: var(--success); }
.toast-error { border-left-color: var(--danger); }
.toast-info { border-left-color: var(--primary); }
.toast-message { flex: 1; }
.toast-close {
border: none;
background: none;
color: var(--text-light);
font-size: 1.1rem;
line-height: 1;
cursor: pointer;
padding: 0;
}
.toast-enter-active,
.toast-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast-enter-from,
.toast-leave-to {
opacity: 0;
transform: translateX(20px);
}
</style>

View File

@@ -0,0 +1,35 @@
// Global toast notifications. Import useToast() anywhere and call
// toast.success('...') / toast.error('...') / toast.info('...'). A single
// <ToastHost /> mounted in AppLayout renders the stack.
import { reactive } from 'vue'
const state = reactive({
items: [],
})
let nextId = 1
function dismiss(id) {
const index = state.items.findIndex(t => t.id === id)
if (index !== -1) state.items.splice(index, 1)
}
function push(message, type = 'info', duration = 5000) {
if (!message) return
const id = nextId++
state.items.push({ id, message, type })
if (duration > 0) {
setTimeout(() => dismiss(id), duration)
}
return id
}
export function useToast() {
return {
items: state.items,
dismiss,
success: (message, duration) => push(message, 'success', duration),
error: (message, duration) => push(message, 'error', duration ?? 8000),
info: (message, duration) => push(message, 'info', duration),
}
}

View File

@@ -69,12 +69,14 @@
</div> </div>
<router-view /> <router-view />
</main> </main>
<ToastHost />
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import ToastHost from '../components/ToastHost.vue'
import { import {
Sun, Moon, LayoutDashboard, Calendar, Map, Cog, Monitor, Sun, Moon, LayoutDashboard, Calendar, Map, Cog, Monitor,
Printer, Globe, Usb, AppWindow, BookOpen, BarChart3, Bell, Image, ShieldCheck Printer, Globe, Usb, AppWindow, BookOpen, BarChart3, Bell, Image, ShieldCheck

View File

@@ -109,6 +109,8 @@ import { Cog, Monitor, Printer, Globe, Package, MapPin } from 'lucide-vue-next'
import ShopFloorMap from '../components/ShopFloorMap.vue' import ShopFloorMap from '../components/ShopFloorMap.vue'
import { assetsApi } from '../api' import { assetsApi } from '../api'
import { currentTheme } from '../stores/theme' import { currentTheme } from '../stores/theme'
import { useToast } from '../composables/toast'
const toast = useToast()
const assets = ref([]) const assets = ref([])
const loading = ref(true) const loading = ref(true)
@@ -196,7 +198,7 @@ async function savePosition() {
pickedPosition.value = null pickedPosition.value = null
} catch (error) { } catch (error) {
console.error('Failed to save position:', error) console.error('Failed to save position:', error)
alert('Failed to save position') toast.error('Failed to save position')
} }
} }
@@ -222,7 +224,7 @@ async function clearPosition() {
pickedPosition.value = null pickedPosition.value = null
} catch (error) { } catch (error) {
console.error('Failed to clear position:', error) console.error('Failed to clear position:', error)
alert('Failed to clear position') toast.error('Failed to clear position')
} }
} }

View File

@@ -85,6 +85,8 @@ import { useAuthStore } from '../stores/auth'
import { loadMapConfig, state as mapConfig } from '../composables/mapConfig' import { loadMapConfig, state as mapConfig } from '../composables/mapConfig'
import { exportMapPdf } from '../utils/mapPdf' import { exportMapPdf } from '../utils/mapPdf'
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes' import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
import { useToast } from '../composables/toast'
const toast = useToast()
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
@@ -254,7 +256,7 @@ async function exportPdf() {
}) })
} catch (e) { } catch (e) {
console.error('Map PDF export failed:', e) console.error('Map PDF export failed:', e)
alert('Failed to export map PDF. See console for details.') toast.error('Failed to export map PDF. See console for details.')
} finally { } finally {
exporting.value = false exporting.value = false
} }

View File

@@ -142,6 +142,8 @@
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { employeesApi, usbApi, notificationsApi } from '@/api' import { employeesApi, usbApi, notificationsApi } from '@/api'
import { useToast } from '../../composables/toast'
const toast = useToast()
const route = useRoute() const route = useRoute()
@@ -244,7 +246,7 @@ async function checkinDevice(device) {
await loadCheckoutHistory() await loadCheckoutHistory()
} catch (err) { } catch (err) {
console.error('Error checking in device:', err) console.error('Error checking in device:', err)
alert('Failed to check in device') toast.error('Failed to check in device')
} }
} }

View File

@@ -199,6 +199,8 @@ import AssetRelationships from '../../components/AssetRelationships.vue'
import CustomFieldsSection from '../../components/CustomFieldsSection.vue' import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
import WarrantyPanel from '../../components/WarrantyPanel.vue' import WarrantyPanel from '../../components/WarrantyPanel.vue'
import { useIdentifierFlags } from '../../composables/identifierSettings' import { useIdentifierFlags } from '../../composables/identifierSettings'
import { useToast } from '../../composables/toast'
const toast = useToast()
const { isEnabled } = useIdentifierFlags() const { isEnabled } = useIdentifierFlags()
@@ -267,7 +269,7 @@ async function confirmDelete() {
router.push('/network') router.push('/network')
} catch (error) { } catch (error) {
console.error('Error deleting device:', error) console.error('Error deleting device:', error)
alert('Failed to delete device') toast.error('Failed to delete device')
} }
} }
} }

View File

@@ -101,6 +101,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { businessunitsApi } from '../../api' import { businessunitsApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const loading = ref(true) const loading = ref(true)
@@ -181,7 +183,7 @@ async function deleteItem() {
toDelete.value = null toDelete.value = null
loadData() loadData()
} catch (err) { } catch (err) {
alert('Failed to delete') toast.error('Failed to delete')
} }
} }
</script> </script>

View File

@@ -114,6 +114,8 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { assetsApi, customFieldsApi } from '../../api' import { assetsApi, customFieldsApi } from '../../api'
import { useToast } from '../../composables/toast'
const toast = useToast()
const assetTypes = ref([]) const assetTypes = ref([])
const assettypeid = ref(null) const assettypeid = ref(null)
@@ -206,7 +208,7 @@ async function deleteField(f) {
await customFieldsApi.remove(f.fieldid) await customFieldsApi.remove(f.fieldid)
loadFields() loadFields()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -104,6 +104,8 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { dashboardDefaultsApi, businessUnitsApi } from '../../api' import { dashboardDefaultsApi, businessUnitsApi } from '../../api'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const businessUnits = ref([]) const businessUnits = ref([])
@@ -181,7 +183,7 @@ async function deleteItem() {
toDelete.value = null toDelete.value = null
loadData() loadData()
} catch (err) { } catch (err) {
alert('Failed to delete') toast.error('Failed to delete')
} }
} }
</script> </script>

View File

@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
import { equipmentApi } from '../../api' import { equipmentApi } from '../../api'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const showInactive = ref(false) const showInactive = ref(false)
@@ -136,7 +138,7 @@ async function deleteType(t) {
await equipmentApi.types.remove(t.equipmenttypeid) await equipmentApi.types.remove(t.equipmenttypeid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
import { locationsApi } from '../../api' import { locationsApi } from '../../api'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const showInactive = ref(false) const showInactive = ref(false)
@@ -136,7 +138,7 @@ async function deleteType(t) {
await locationsApi.types.remove(t.locationtypeid) await locationsApi.types.remove(t.locationtypeid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -211,6 +211,8 @@
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { locationsApi } from '../../api' import { locationsApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const locations = ref([]) const locations = ref([])
const locationTypes = ref([]) const locationTypes = ref([])
@@ -371,7 +373,7 @@ async function deleteLocation() {
loadLocations() loadLocations()
} catch (err) { } catch (err) {
console.error('Error deleting location:', err) console.error('Error deleting location:', err)
alert('Failed to delete location') toast.error('Failed to delete location')
} }
} }
</script> </script>

View File

@@ -145,6 +145,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { machinetypesApi } from '../../api' import { machinetypesApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const machineTypes = ref([]) const machineTypes = ref([])
const loading = ref(true) const loading = ref(true)
@@ -256,7 +258,7 @@ async function deleteType() {
loadTypes() loadTypes()
} catch (err) { } catch (err) {
console.error('Error deleting machine type:', err) console.error('Error deleting machine type:', err)
alert('Failed to delete machine type') toast.error('Failed to delete machine type')
} }
} }

View File

@@ -183,6 +183,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { modelsApi, vendorsApi, machinetypesApi } from '../../api' import { modelsApi, vendorsApi, machinetypesApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const models = ref([]) const models = ref([])
const vendors = ref([]) const vendors = ref([])
@@ -345,7 +347,7 @@ async function deleteModel() {
loadModels() loadModels()
} catch (err) { } catch (err) {
console.error('Error deleting model:', err) console.error('Error deleting model:', err)
alert('Failed to delete model') toast.error('Failed to delete model')
} }
} }
</script> </script>

View File

@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
import { networkApi } from '../../api' import { networkApi } from '../../api'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const showInactive = ref(false) const showInactive = ref(false)
@@ -136,7 +138,7 @@ async function deleteType(t) {
await networkApi.types.remove(t.networkdevicetypeid) await networkApi.types.remove(t.networkdevicetypeid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -119,6 +119,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { operatingsystemsApi } from '../../api' import { operatingsystemsApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const loading = ref(true) const loading = ref(true)
@@ -206,7 +208,7 @@ async function deleteItem() {
toDelete.value = null toDelete.value = null
loadData() loadData()
} catch (err) { } catch (err) {
alert('Failed to delete') toast.error('Failed to delete')
} }
} }
</script> </script>

View File

@@ -86,6 +86,8 @@ import { ref, computed, onMounted } from 'vue'
import { computersApi } from '../../api' import { computersApi } from '../../api'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useToast } from '../../composables/toast'
const toast = useToast()
const pcTypes = ref([]) const pcTypes = ref([])
const showInactive = ref(false) const showInactive = ref(false)
@@ -130,7 +132,7 @@ async function deleteType(pt) {
await computersApi.types.remove(pt.computertypeid) await computersApi.types.remove(pt.computertypeid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }

View File

@@ -97,6 +97,8 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { printersApi } from '../../api' import { printersApi } from '../../api'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const models = ref([]) const models = ref([])
@@ -171,7 +173,7 @@ async function deleteDriver(d) {
await printersApi.drivers.delete(d.driverid) await printersApi.drivers.delete(d.driverid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
import { printersApi } from '../../api' import { printersApi } from '../../api'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const showInactive = ref(false) const showInactive = ref(false)
@@ -136,7 +138,7 @@ async function deleteType(t) {
await printersApi.types.remove(t.printertypeid) await printersApi.types.remove(t.printertypeid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
import { relationshipTypesApi } from '../../api' import { relationshipTypesApi } from '../../api'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const showInactive = ref(false) const showInactive = ref(false)
@@ -136,7 +138,7 @@ async function deleteType(t) {
await relationshipTypesApi.remove(t.relationshiptypeid) await relationshipTypesApi.remove(t.relationshiptypeid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete') toast.error(err.response?.data?.error?.message || err.response?.data?.message || 'Failed to delete')
} }
} }
</script> </script>

View File

@@ -139,6 +139,8 @@ import { ref, onMounted } from 'vue'
import { assetsApi } from '../../api' import { assetsApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue' import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const statuses = ref([]) const statuses = ref([])
const loading = ref(true) const loading = ref(true)
@@ -251,7 +253,7 @@ async function deleteStatus() {
} catch (err) { } catch (err) {
console.error('Error deleting status:', err) console.error('Error deleting status:', err)
error.value = err.response?.data?.message || 'Failed to delete status' error.value = err.response?.data?.message || 'Failed to delete status'
alert(error.value) toast.error(error.value)
} }
} }

View File

@@ -285,6 +285,8 @@ import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { networkApi, locationsApi } from '../../api' import { networkApi, locationsApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const route = useRoute() const route = useRoute()
@@ -489,7 +491,7 @@ async function deleteSubnet() {
loadSubnets() loadSubnets()
} catch (err) { } catch (err) {
console.error('Error deleting subnet:', err) console.error('Error deleting subnet:', err)
alert(err.response?.data?.message || 'Failed to delete subnet') toast.error(err.response?.data?.message || 'Failed to delete subnet')
} }
} }
</script> </script>

View File

@@ -187,6 +187,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { networkApi } from '../../api' import { networkApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const vlans = ref([]) const vlans = ref([])
const loading = ref(true) const loading = ref(true)
@@ -326,7 +328,7 @@ async function deleteVLAN() {
loadVLANs() loadVLANs()
} catch (err) { } catch (err) {
console.error('Error deleting VLAN:', err) console.error('Error deleting VLAN:', err)
alert(err.response?.data?.message || 'Failed to delete VLAN') toast.error(err.response?.data?.message || 'Failed to delete VLAN')
} }
} }
</script> </script>

View File

@@ -168,6 +168,8 @@ import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { usbApi } from '../../api' import { usbApi } from '../../api'
import Modal from '../../components/Modal.vue' import Modal from '../../components/Modal.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const route = useRoute() const route = useRoute()
@@ -217,7 +219,7 @@ async function doCheckout() {
await loadDevice() await loadDevice()
} catch (error) { } catch (error) {
console.error('Checkout error:', error) console.error('Checkout error:', error)
alert(error.response?.data?.message || 'Checkout failed') toast.error(error.response?.data?.message || 'Checkout failed')
} }
} }
@@ -228,7 +230,7 @@ async function doCheckin() {
await loadDevice() await loadDevice()
} catch (error) { } catch (error) {
console.error('Checkin error:', error) console.error('Checkin error:', error)
alert(error.response?.data?.message || 'Check in failed') toast.error(error.response?.data?.message || 'Check in failed')
} }
} }

View File

@@ -147,6 +147,8 @@ import { usbApi } from '../../api'
import Modal from '../../components/Modal.vue' import Modal from '../../components/Modal.vue'
import EmployeeSearch from '../../components/EmployeeSearch.vue' import EmployeeSearch from '../../components/EmployeeSearch.vue'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const devices = ref([]) const devices = ref([])
const loading = ref(true) const loading = ref(true)
@@ -241,7 +243,7 @@ async function doCheckout() {
loadDevices() loadDevices()
} catch (error) { } catch (error) {
console.error('Checkout error:', error) console.error('Checkout error:', error)
alert(error.response?.data?.message || 'Checkout failed') toast.error(error.response?.data?.message || 'Checkout failed')
} }
} }
@@ -252,7 +254,7 @@ async function doCheckin() {
loadDevices() loadDevices()
} catch (error) { } catch (error) {
console.error('Checkin error:', error) console.error('Checkin error:', error)
alert(error.response?.data?.message || 'Check in failed') toast.error(error.response?.data?.message || 'Check in failed')
} }
} }

View File

@@ -186,6 +186,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { vendorsApi } from '../../api' import { vendorsApi } from '../../api'
import PaginationBar from '../../components/PaginationBar.vue' import PaginationBar from '../../components/PaginationBar.vue'
import { useToast } from '../../composables/toast'
const toast = useToast()
const vendors = ref([]) const vendors = ref([])
const loading = ref(true) const loading = ref(true)
@@ -321,7 +323,7 @@ async function deleteVendor() {
loadVendors() loadVendors()
} catch (err) { } catch (err) {
console.error('Error deleting vendor:', err) console.error('Error deleting vendor:', err)
alert('Failed to delete vendor') toast.error('Failed to delete vendor')
} }
} }
</script> </script>

View File

@@ -5,6 +5,7 @@
<button class="btn btn-primary" @click="openModal()">+ Add Warranty</button> <button class="btn btn-primary" @click="openModal()">+ Add Warranty</button>
</div> </div>
<div class="filters"> <div class="filters">
<label>Status <label>Status
<select v-model="statusFilter" class="form-control" @change="loadData"> <select v-model="statusFilter" class="form-control" @change="loadData">
@@ -138,6 +139,9 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { colorStyle } from '@/utils/colorStyle' import { colorStyle } from '@/utils/colorStyle'
import { warrantyApi, assetsApi } from '../../api' import { warrantyApi, assetsApi } from '../../api'
import { useToast } from '../../composables/toast'
const toast = useToast()
const items = ref([]) const items = ref([])
const loading = ref(true) const loading = ref(true)
@@ -261,7 +265,7 @@ async function deleteWarranty(w) {
await warrantyApi.remove(w.warrantyid) await warrantyApi.remove(w.warrantyid)
loadData() loadData()
} catch (err) { } catch (err) {
alert(apiError(err, 'Failed to delete')) toast.error(apiError(err, 'Failed to delete'))
} }
} }
@@ -270,9 +274,11 @@ async function refresh(w) {
const response = await warrantyApi.refresh(w.warrantyid) const response = await warrantyApi.refresh(w.warrantyid)
loadData() loadData()
const updated = response.data?.data const updated = response.data?.data
if (updated) alert(`Refreshed: ${updated.vendor} - ${updated.servicelevel || 'coverage'} ends ${updated.enddate || 'unknown'}`) if (updated) {
toast.success(`${updated.vendor}: ${updated.servicelevel || 'coverage'} ends ${updated.enddate || 'unknown'}`)
}
} catch (err) { } catch (err) {
alert(apiError(err, 'Refresh failed')) toast.error(apiError(err, 'Refresh failed'))
} }
} }
</script> </script>