Relocate warranty, measuringtools, network, printers, usb, notifications, computers, and slides into plugins/<name>/frontend/. Each plugin's views are pulled from wherever they lived (own dir, plus the shared views/settings/, views/reports/, views/print/ dirs, and top-level views) into the plugin's frontend/views/, and its route file becomes the self-contained routes.js. Handled the messy cases: - computers: name mismatch (its views live in views/pcs/) - moved by following the route file's own imports, so the dir name did not matter. Its OS/access- protocol/PC-type settings views move with it (only computers.js routed them). - network: NetworkHub's sibling sub-views (NetworkDevicesList, SubnetsBrowse, not directly routed) moved too so its `./` imports resolve. - printers: the qrLogo helper is SHARED with core AssetLabel, so it stays in views/print/ and PrinterQR imports it via @/views/print/qrLogo. - slides: route file is toplevel-only (TVDashboard); SlideManager stays core (core.js routes /settings/slides). frontend/src/views/ now holds only core views; frontend/src/router/routes/ holds only core.js. All 13 plugins are self-contained under plugins/<name>/frontend/. Verified live: Network (hub + moved sub-views), Computers (name mismatch), GE-Enforce (helper), printedparts all render from their staged frontends. Build + 58 vitest + naming green.
150 lines
3.5 KiB
Vue
150 lines
3.5 KiB
Vue
<template>
|
|
<div>
|
|
<div class="page-header">
|
|
<h2>{{ isEdit ? 'Edit USB Device' : 'Add USB Device' }}</h2>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div v-if="loading" class="loading">Loading...</div>
|
|
|
|
<form v-else @submit.prevent="saveDevice">
|
|
<div class="form-group">
|
|
<label for="device_id">Serial Number *</label>
|
|
<input
|
|
id="device_id"
|
|
v-model="form.device_id"
|
|
type="text"
|
|
class="form-control"
|
|
required
|
|
maxlength="100"
|
|
:disabled="isEdit"
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="device_desc">Description</label>
|
|
<input
|
|
id="device_desc"
|
|
v-model="form.device_desc"
|
|
type="text"
|
|
class="form-control"
|
|
maxlength="100"
|
|
placeholder="e.g., USB Flash Drive #1"
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="locker_location">Locker Location</label>
|
|
<input
|
|
id="locker_location"
|
|
v-model="form.locker_location"
|
|
type="text"
|
|
class="form-control"
|
|
maxlength="200"
|
|
placeholder="Where the device is stored"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="error" class="error-message">{{ error }}</div>
|
|
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-primary" :disabled="saving">
|
|
{{ saving ? 'Saving...' : (isEdit ? 'Update Device' : 'Add Device') }}
|
|
</button>
|
|
<router-link to="/usb" class="btn btn-secondary">Cancel</router-link>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { usbApi } from '@/api'
|
|
import { apiError } from '@/utils/apiError'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const isEdit = computed(() => !!route.params.id)
|
|
|
|
const loading = ref(true)
|
|
const saving = ref(false)
|
|
const error = ref('')
|
|
|
|
const form = ref({
|
|
device_id: '',
|
|
device_desc: '',
|
|
locker_location: ''
|
|
})
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
// Load device if editing
|
|
if (isEdit.value) {
|
|
const response = await usbApi.get(route.params.id)
|
|
const device = response.data.data
|
|
|
|
form.value = {
|
|
device_id: device.device_id || '',
|
|
device_desc: device.device_desc || '',
|
|
locker_location: device.locker_location || ''
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading data:', err)
|
|
error.value = 'Failed to load data'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
async function saveDevice() {
|
|
error.value = ''
|
|
saving.value = true
|
|
|
|
try {
|
|
// Create needs device_id; update keys off the path id so it is omitted.
|
|
const data = {
|
|
device_desc: form.value.device_desc || null,
|
|
locker_location: form.value.locker_location || null
|
|
}
|
|
|
|
if (isEdit.value) {
|
|
await usbApi.update(route.params.id, data)
|
|
} else {
|
|
data.device_id = form.value.device_id
|
|
await usbApi.create(data)
|
|
}
|
|
|
|
router.push('/usb')
|
|
} catch (err) {
|
|
console.error('Error saving device:', err)
|
|
error.value = apiError(err, 'Failed to save device')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.form-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|