diff --git a/frontend/src/components/AssetRelationships.vue b/frontend/src/components/AssetRelationships.vue index e82146a..12a0b00 100644 --- a/frontend/src/components/AssetRelationships.vue +++ b/frontend/src/components/AssetRelationships.vue @@ -1,686 +1,688 @@ - - - - - + + + + + diff --git a/frontend/src/components/ToastHost.vue b/frontend/src/components/ToastHost.vue new file mode 100644 index 0000000..74b0661 --- /dev/null +++ b/frontend/src/components/ToastHost.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/frontend/src/composables/toast.js b/frontend/src/composables/toast.js new file mode 100644 index 0000000..6ff949e --- /dev/null +++ b/frontend/src/composables/toast.js @@ -0,0 +1,35 @@ +// Global toast notifications. Import useToast() anywhere and call +// toast.success('...') / toast.error('...') / toast.info('...'). A single +// 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), + } +} diff --git a/frontend/src/views/AppLayout.vue b/frontend/src/views/AppLayout.vue index 83bf707..37ed1bc 100644 --- a/frontend/src/views/AppLayout.vue +++ b/frontend/src/views/AppLayout.vue @@ -69,12 +69,14 @@ + - - + + + + + diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 08b1338..fd2f139 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -1,388 +1,390 @@ - - - - - + + + + + diff --git a/frontend/src/views/employees/EmployeeDetail.vue b/frontend/src/views/employees/EmployeeDetail.vue index 272dd9f..b380e6c 100644 --- a/frontend/src/views/employees/EmployeeDetail.vue +++ b/frontend/src/views/employees/EmployeeDetail.vue @@ -1,320 +1,322 @@ - - - - - + + + + + diff --git a/frontend/src/views/network/NetworkDeviceDetail.vue b/frontend/src/views/network/NetworkDeviceDetail.vue index 7f78140..2cab592 100644 --- a/frontend/src/views/network/NetworkDeviceDetail.vue +++ b/frontend/src/views/network/NetworkDeviceDetail.vue @@ -1,369 +1,371 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/BusinessUnitsList.vue b/frontend/src/views/settings/BusinessUnitsList.vue index b409dac..5f374e0 100644 --- a/frontend/src/views/settings/BusinessUnitsList.vue +++ b/frontend/src/views/settings/BusinessUnitsList.vue @@ -1,187 +1,189 @@ - - - + + + diff --git a/frontend/src/views/settings/CustomFieldsList.vue b/frontend/src/views/settings/CustomFieldsList.vue index 232cc6e..bc994be 100644 --- a/frontend/src/views/settings/CustomFieldsList.vue +++ b/frontend/src/views/settings/CustomFieldsList.vue @@ -114,6 +114,8 @@ diff --git a/frontend/src/views/settings/DashboardDefaultsList.vue b/frontend/src/views/settings/DashboardDefaultsList.vue index beb46cb..7b0653b 100644 --- a/frontend/src/views/settings/DashboardDefaultsList.vue +++ b/frontend/src/views/settings/DashboardDefaultsList.vue @@ -104,6 +104,8 @@ diff --git a/frontend/src/views/settings/EquipmentTypesList.vue b/frontend/src/views/settings/EquipmentTypesList.vue index d8c586b..6b4d2eb 100644 --- a/frontend/src/views/settings/EquipmentTypesList.vue +++ b/frontend/src/views/settings/EquipmentTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/LocationTypesList.vue b/frontend/src/views/settings/LocationTypesList.vue index a9bc00a..616b262 100644 --- a/frontend/src/views/settings/LocationTypesList.vue +++ b/frontend/src/views/settings/LocationTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/LocationsList.vue b/frontend/src/views/settings/LocationsList.vue index 0f55f45..2ac8a98 100644 --- a/frontend/src/views/settings/LocationsList.vue +++ b/frontend/src/views/settings/LocationsList.vue @@ -1,385 +1,387 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/MachineTypesList.vue b/frontend/src/views/settings/MachineTypesList.vue index c79858c..16ca6b5 100644 --- a/frontend/src/views/settings/MachineTypesList.vue +++ b/frontend/src/views/settings/MachineTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/ModelsList.vue b/frontend/src/views/settings/ModelsList.vue index 4a8f1f6..3b03107 100644 --- a/frontend/src/views/settings/ModelsList.vue +++ b/frontend/src/views/settings/ModelsList.vue @@ -1,368 +1,370 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/NetworkTypesList.vue b/frontend/src/views/settings/NetworkTypesList.vue index f05a9aa..e67ffbe 100644 --- a/frontend/src/views/settings/NetworkTypesList.vue +++ b/frontend/src/views/settings/NetworkTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/OperatingSystemsList.vue b/frontend/src/views/settings/OperatingSystemsList.vue index aeb2bad..4d07911 100644 --- a/frontend/src/views/settings/OperatingSystemsList.vue +++ b/frontend/src/views/settings/OperatingSystemsList.vue @@ -1,224 +1,226 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/PCTypesList.vue b/frontend/src/views/settings/PCTypesList.vue index 6ceb82f..61e2871 100644 --- a/frontend/src/views/settings/PCTypesList.vue +++ b/frontend/src/views/settings/PCTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/PrinterDriversList.vue b/frontend/src/views/settings/PrinterDriversList.vue index ab20aff..3eb5b52 100644 --- a/frontend/src/views/settings/PrinterDriversList.vue +++ b/frontend/src/views/settings/PrinterDriversList.vue @@ -97,6 +97,8 @@ diff --git a/frontend/src/views/settings/PrinterTypesList.vue b/frontend/src/views/settings/PrinterTypesList.vue index 0c172a8..ef05974 100644 --- a/frontend/src/views/settings/PrinterTypesList.vue +++ b/frontend/src/views/settings/PrinterTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/RelationshipTypesList.vue b/frontend/src/views/settings/RelationshipTypesList.vue index 1c28062..8a79490 100644 --- a/frontend/src/views/settings/RelationshipTypesList.vue +++ b/frontend/src/views/settings/RelationshipTypesList.vue @@ -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') } } diff --git a/frontend/src/views/settings/StatusesList.vue b/frontend/src/views/settings/StatusesList.vue index 7af9ffa..e1a9129 100644 --- a/frontend/src/views/settings/StatusesList.vue +++ b/frontend/src/views/settings/StatusesList.vue @@ -1,315 +1,317 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/SubnetsList.vue b/frontend/src/views/settings/SubnetsList.vue index 6c092bc..0304314 100644 --- a/frontend/src/views/settings/SubnetsList.vue +++ b/frontend/src/views/settings/SubnetsList.vue @@ -1,564 +1,566 @@ - - - - - + + + + + diff --git a/frontend/src/views/settings/VLANsList.vue b/frontend/src/views/settings/VLANsList.vue index a8a9952..4f423ae 100644 --- a/frontend/src/views/settings/VLANsList.vue +++ b/frontend/src/views/settings/VLANsList.vue @@ -1,374 +1,376 @@ - - - - - + + + + + diff --git a/frontend/src/views/usb/USBDetail.vue b/frontend/src/views/usb/USBDetail.vue index 209111c..7a848f3 100644 --- a/frontend/src/views/usb/USBDetail.vue +++ b/frontend/src/views/usb/USBDetail.vue @@ -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') } } diff --git a/frontend/src/views/usb/USBList.vue b/frontend/src/views/usb/USBList.vue index 0c9abda..c6aa94a 100644 --- a/frontend/src/views/usb/USBList.vue +++ b/frontend/src/views/usb/USBList.vue @@ -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') } } diff --git a/frontend/src/views/vendors/VendorsList.vue b/frontend/src/views/vendors/VendorsList.vue index d1df70c..6f63483 100644 --- a/frontend/src/views/vendors/VendorsList.vue +++ b/frontend/src/views/vendors/VendorsList.vue @@ -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') } } diff --git a/frontend/src/views/warranty/WarrantiesList.vue b/frontend/src/views/warranty/WarrantiesList.vue index aa064b0..faccf36 100644 --- a/frontend/src/views/warranty/WarrantiesList.vue +++ b/frontend/src/views/warranty/WarrantiesList.vue @@ -5,6 +5,7 @@ +