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:
@@ -177,6 +177,8 @@ import { Cog, Monitor, Printer, Globe, Package } from 'lucide-vue-next'
|
||||
import { assetsApi, relationshipTypesApi } from '../api'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useToast } from '../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps({
|
||||
assetId: {
|
||||
@@ -349,7 +351,7 @@ async function saveRelationship() {
|
||||
emit('updated')
|
||||
} catch (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')
|
||||
} catch (error) {
|
||||
console.error('Failed to delete relationship:', error)
|
||||
alert('Failed to delete relationship')
|
||||
toast.error('Failed to delete relationship')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
75
frontend/src/components/ToastHost.vue
Normal file
75
frontend/src/components/ToastHost.vue
Normal 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">×</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>
|
||||
35
frontend/src/composables/toast.js
Normal file
35
frontend/src/composables/toast.js
Normal 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),
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,14 @@
|
||||
</div>
|
||||
<router-view />
|
||||
</main>
|
||||
<ToastHost />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ToastHost from '../components/ToastHost.vue'
|
||||
import {
|
||||
Sun, Moon, LayoutDashboard, Calendar, Map, Cog, Monitor,
|
||||
Printer, Globe, Usb, AppWindow, BookOpen, BarChart3, Bell, Image, ShieldCheck
|
||||
|
||||
@@ -109,6 +109,8 @@ import { Cog, Monitor, Printer, Globe, Package, MapPin } from 'lucide-vue-next'
|
||||
import ShopFloorMap from '../components/ShopFloorMap.vue'
|
||||
import { assetsApi } from '../api'
|
||||
import { currentTheme } from '../stores/theme'
|
||||
import { useToast } from '../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const assets = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -196,7 +198,7 @@ async function savePosition() {
|
||||
pickedPosition.value = null
|
||||
} catch (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
|
||||
} catch (error) {
|
||||
console.error('Failed to clear position:', error)
|
||||
alert('Failed to clear position')
|
||||
toast.error('Failed to clear position')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ import { useAuthStore } from '../stores/auth'
|
||||
import { loadMapConfig, state as mapConfig } from '../composables/mapConfig'
|
||||
import { exportMapPdf } from '../utils/mapPdf'
|
||||
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
|
||||
import { useToast } from '../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
@@ -254,7 +256,7 @@ async function exportPdf() {
|
||||
})
|
||||
} catch (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 {
|
||||
exporting.value = false
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { employeesApi, usbApi, notificationsApi } from '@/api'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -244,7 +246,7 @@ async function checkinDevice(device) {
|
||||
await loadCheckoutHistory()
|
||||
} catch (err) {
|
||||
console.error('Error checking in device:', err)
|
||||
alert('Failed to check in device')
|
||||
toast.error('Failed to check in device')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,8 @@ import AssetRelationships from '../../components/AssetRelationships.vue'
|
||||
import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
|
||||
import WarrantyPanel from '../../components/WarrantyPanel.vue'
|
||||
import { useIdentifierFlags } from '../../composables/identifierSettings'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const { isEnabled } = useIdentifierFlags()
|
||||
|
||||
@@ -267,7 +269,7 @@ async function confirmDelete() {
|
||||
router.push('/network')
|
||||
} catch (error) {
|
||||
console.error('Error deleting device:', error)
|
||||
alert('Failed to delete device')
|
||||
toast.error('Failed to delete device')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,8 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { businessunitsApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -181,7 +183,7 @@ async function deleteItem() {
|
||||
toDelete.value = null
|
||||
loadData()
|
||||
} catch (err) {
|
||||
alert('Failed to delete')
|
||||
toast.error('Failed to delete')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { assetsApi, customFieldsApi } from '../../api'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const assetTypes = ref([])
|
||||
const assettypeid = ref(null)
|
||||
@@ -206,7 +208,7 @@ async function deleteField(f) {
|
||||
await customFieldsApi.remove(f.fieldid)
|
||||
loadFields()
|
||||
} 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>
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { dashboardDefaultsApi, businessUnitsApi } from '../../api'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const businessUnits = ref([])
|
||||
@@ -181,7 +183,7 @@ async function deleteItem() {
|
||||
toDelete.value = null
|
||||
loadData()
|
||||
} catch (err) {
|
||||
alert('Failed to delete')
|
||||
toast.error('Failed to delete')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { equipmentApi } from '../../api'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const showInactive = ref(false)
|
||||
@@ -136,7 +138,7 @@ async function deleteType(t) {
|
||||
await equipmentApi.types.remove(t.equipmenttypeid)
|
||||
loadData()
|
||||
} 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>
|
||||
|
||||
@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { locationsApi } from '../../api'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const showInactive = ref(false)
|
||||
@@ -136,7 +138,7 @@ async function deleteType(t) {
|
||||
await locationsApi.types.remove(t.locationtypeid)
|
||||
loadData()
|
||||
} 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>
|
||||
|
||||
@@ -211,6 +211,8 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { locationsApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const locations = ref([])
|
||||
const locationTypes = ref([])
|
||||
@@ -371,7 +373,7 @@ async function deleteLocation() {
|
||||
loadLocations()
|
||||
} catch (err) {
|
||||
console.error('Error deleting location:', err)
|
||||
alert('Failed to delete location')
|
||||
toast.error('Failed to delete location')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -145,6 +145,8 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { machinetypesApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const machineTypes = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -256,7 +258,7 @@ async function deleteType() {
|
||||
loadTypes()
|
||||
} catch (err) {
|
||||
console.error('Error deleting machine type:', err)
|
||||
alert('Failed to delete machine type')
|
||||
toast.error('Failed to delete machine type')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,6 +183,8 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { modelsApi, vendorsApi, machinetypesApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const models = ref([])
|
||||
const vendors = ref([])
|
||||
@@ -345,7 +347,7 @@ async function deleteModel() {
|
||||
loadModels()
|
||||
} catch (err) {
|
||||
console.error('Error deleting model:', err)
|
||||
alert('Failed to delete model')
|
||||
toast.error('Failed to delete model')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { networkApi } from '../../api'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const showInactive = ref(false)
|
||||
@@ -136,7 +138,7 @@ async function deleteType(t) {
|
||||
await networkApi.types.remove(t.networkdevicetypeid)
|
||||
loadData()
|
||||
} 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>
|
||||
|
||||
@@ -119,6 +119,8 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { operatingsystemsApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -206,7 +208,7 @@ async function deleteItem() {
|
||||
toDelete.value = null
|
||||
loadData()
|
||||
} catch (err) {
|
||||
alert('Failed to delete')
|
||||
toast.error('Failed to delete')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -86,6 +86,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { computersApi } from '../../api'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const pcTypes = ref([])
|
||||
const showInactive = ref(false)
|
||||
@@ -130,7 +132,7 @@ async function deleteType(pt) {
|
||||
await computersApi.types.remove(pt.computertypeid)
|
||||
loadData()
|
||||
} 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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { printersApi } from '../../api'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const models = ref([])
|
||||
@@ -171,7 +173,7 @@ async function deleteDriver(d) {
|
||||
await printersApi.drivers.delete(d.driverid)
|
||||
loadData()
|
||||
} 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>
|
||||
|
||||
@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { printersApi } from '../../api'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const showInactive = ref(false)
|
||||
@@ -136,7 +138,7 @@ async function deleteType(t) {
|
||||
await printersApi.types.remove(t.printertypeid)
|
||||
loadData()
|
||||
} 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>
|
||||
|
||||
@@ -76,6 +76,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { relationshipTypesApi } from '../../api'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const showInactive = ref(false)
|
||||
@@ -136,7 +138,7 @@ async function deleteType(t) {
|
||||
await relationshipTypesApi.remove(t.relationshiptypeid)
|
||||
loadData()
|
||||
} 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>
|
||||
|
||||
@@ -139,6 +139,8 @@ import { ref, onMounted } from 'vue'
|
||||
import { assetsApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import ColorSwatchPicker from '@/components/ColorSwatchPicker.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const statuses = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -251,7 +253,7 @@ async function deleteStatus() {
|
||||
} catch (err) {
|
||||
console.error('Error deleting status:', err)
|
||||
error.value = err.response?.data?.message || 'Failed to delete status'
|
||||
alert(error.value)
|
||||
toast.error(error.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,8 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { networkApi, locationsApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -489,7 +491,7 @@ async function deleteSubnet() {
|
||||
loadSubnets()
|
||||
} catch (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>
|
||||
|
||||
@@ -187,6 +187,8 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { networkApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const vlans = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -326,7 +328,7 @@ async function deleteVLAN() {
|
||||
loadVLANs()
|
||||
} catch (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>
|
||||
|
||||
@@ -168,6 +168,8 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { usbApi } from '../../api'
|
||||
import Modal from '../../components/Modal.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -217,7 +219,7 @@ async function doCheckout() {
|
||||
await loadDevice()
|
||||
} catch (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()
|
||||
} catch (error) {
|
||||
console.error('Checkin error:', error)
|
||||
alert(error.response?.data?.message || 'Check in failed')
|
||||
toast.error(error.response?.data?.message || 'Check in failed')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,8 @@ import { usbApi } from '../../api'
|
||||
import Modal from '../../components/Modal.vue'
|
||||
import EmployeeSearch from '../../components/EmployeeSearch.vue'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const devices = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -241,7 +243,7 @@ async function doCheckout() {
|
||||
loadDevices()
|
||||
} catch (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()
|
||||
} catch (error) {
|
||||
console.error('Checkin error:', error)
|
||||
alert(error.response?.data?.message || 'Check in failed')
|
||||
toast.error(error.response?.data?.message || 'Check in failed')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
frontend/src/views/vendors/VendorsList.vue
vendored
4
frontend/src/views/vendors/VendorsList.vue
vendored
@@ -186,6 +186,8 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { vendorsApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
import { useToast } from '../../composables/toast'
|
||||
const toast = useToast()
|
||||
|
||||
const vendors = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -321,7 +323,7 @@ async function deleteVendor() {
|
||||
loadVendors()
|
||||
} catch (err) {
|
||||
console.error('Error deleting vendor:', err)
|
||||
alert('Failed to delete vendor')
|
||||
toast.error('Failed to delete vendor')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<button class="btn btn-primary" @click="openModal()">+ Add Warranty</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="filters">
|
||||
<label>Status
|
||||
<select v-model="statusFilter" class="form-control" @change="loadData">
|
||||
@@ -138,6 +139,9 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { colorStyle } from '@/utils/colorStyle'
|
||||
import { warrantyApi, assetsApi } from '../../api'
|
||||
import { useToast } from '../../composables/toast'
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const items = ref([])
|
||||
const loading = ref(true)
|
||||
@@ -261,7 +265,7 @@ async function deleteWarranty(w) {
|
||||
await warrantyApi.remove(w.warrantyid)
|
||||
loadData()
|
||||
} 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)
|
||||
loadData()
|
||||
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) {
|
||||
alert(apiError(err, 'Refresh failed'))
|
||||
toast.error(apiError(err, 'Refresh failed'))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user