Add print badges, pagination, route splitting, JWT auth fixes, and list page alignment
- Fix equipment badge barcode not rendering (loading race condition) - Fix printer QR code not rendering on initial load (same race condition) - Add model image to equipment badge via imageurl from Model table - Fix white-on-white machine number text on badge, tighten barcode spacing - Add PaginationBar component used across all list pages - Split monolithic router into per-plugin route modules - Fix 25 GET API endpoints returning 401 (jwt_required -> optional=True) - Align list page columns across Equipment, PCs, and Network pages - Add print views: EquipmentBadge, PrinterQRSingle, PrinterQRBatch, USBLabelBatch - Add PC Relationships report, migration docs, and CLAUDE.md project guide - Various plugin model, API, and frontend refinements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
<div class="page-header">
|
||||
<h2>Equipment Details</h2>
|
||||
<div class="header-actions">
|
||||
<router-link :to="`/print/equipment-badge/${equipment?.equipment?.equipmentid}`" class="btn btn-secondary" v-if="equipment" target="_blank">
|
||||
Print Badge
|
||||
</router-link>
|
||||
<router-link :to="`/machines/${equipment?.equipment?.equipmentid}/edit`" class="btn btn-primary" v-if="equipment">
|
||||
Edit
|
||||
</router-link>
|
||||
@@ -22,28 +25,28 @@
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span class="badge badge-lg badge-primary">
|
||||
{{ equipment.assettype_name || 'Equipment' }}
|
||||
{{ equipment.assettypename || 'Equipment' }}
|
||||
</span>
|
||||
<span class="badge badge-lg" :class="getStatusClass(equipment.status_name)">
|
||||
{{ equipment.status_name || 'Unknown' }}
|
||||
<span class="badge badge-lg" :class="getStatusClass(equipment.statusname)">
|
||||
{{ equipment.statusname || 'Unknown' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hero-details">
|
||||
<div class="hero-detail" v-if="equipment.equipment?.equipmenttype_name">
|
||||
<div class="hero-detail" v-if="equipment.equipment?.equipmenttypename">
|
||||
<span class="hero-detail-label">Type</span>
|
||||
<span class="hero-detail-value">{{ equipment.equipment.equipmenttype_name }}</span>
|
||||
<span class="hero-detail-value">{{ equipment.equipment.equipmenttypename }}</span>
|
||||
</div>
|
||||
<div class="hero-detail" v-if="equipment.equipment?.vendor_name">
|
||||
<div class="hero-detail" v-if="equipment.equipment?.vendorname">
|
||||
<span class="hero-detail-label">Vendor</span>
|
||||
<span class="hero-detail-value">{{ equipment.equipment.vendor_name }}</span>
|
||||
<span class="hero-detail-value">{{ equipment.equipment.vendorname }}</span>
|
||||
</div>
|
||||
<div class="hero-detail" v-if="equipment.equipment?.model_name">
|
||||
<div class="hero-detail" v-if="equipment.equipment?.modelname">
|
||||
<span class="hero-detail-label">Model</span>
|
||||
<span class="hero-detail-value">{{ equipment.equipment.model_name }}</span>
|
||||
<span class="hero-detail-value">{{ equipment.equipment.modelname }}</span>
|
||||
</div>
|
||||
<div class="hero-detail" v-if="equipment.location_name">
|
||||
<div class="hero-detail" v-if="equipment.locationname">
|
||||
<span class="hero-detail-label">Location</span>
|
||||
<span class="hero-detail-value">{{ equipment.location_name }}</span>
|
||||
<span class="hero-detail-value">{{ equipment.locationname }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,30 +81,30 @@
|
||||
<div class="info-list">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Type</span>
|
||||
<span class="info-value">{{ equipment.equipment?.equipmenttype_name || '-' }}</span>
|
||||
<span class="info-value">{{ equipment.equipment?.equipmenttypename || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Vendor</span>
|
||||
<span class="info-value">{{ equipment.equipment?.vendor_name || '-' }}</span>
|
||||
<span class="info-value">{{ equipment.equipment?.vendorname || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Model</span>
|
||||
<span class="info-value">{{ equipment.equipment?.model_name || '-' }}</span>
|
||||
<span class="info-value">{{ equipment.equipment?.modelname || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controller Section (for CNC machines) -->
|
||||
<div class="section-card" v-if="equipment.equipment?.controller_vendor_name || equipment.equipment?.controller_model_name">
|
||||
<div class="section-card" v-if="equipment.equipment?.controllervendorname || equipment.equipment?.controllermodelname">
|
||||
<h3 class="section-title">Controller</h3>
|
||||
<div class="info-list">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Vendor</span>
|
||||
<span class="info-value">{{ equipment.equipment?.controller_vendor_name || '-' }}</span>
|
||||
<span class="info-value">{{ equipment.equipment?.controllervendorname || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Model</span>
|
||||
<span class="info-value">{{ equipment.equipment?.controller_model_name || '-' }}</span>
|
||||
<span class="info-value">{{ equipment.equipment?.controllermodelname || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,14 +167,14 @@
|
||||
:top="equipment.maptop"
|
||||
:machineName="equipment.assetnumber"
|
||||
>
|
||||
<span class="location-link">{{ equipment.location_name || 'On Map' }}</span>
|
||||
<span class="location-link">{{ equipment.locationname || 'On Map' }}</span>
|
||||
</LocationMapTooltip>
|
||||
<span v-else>{{ equipment.location_name || '-' }}</span>
|
||||
<span v-else>{{ equipment.locationname || '-' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Business Unit</span>
|
||||
<span class="info-value">{{ equipment.businessunit_name || '-' }}</span>
|
||||
<span class="info-value">{{ equipment.businessunitname || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,7 +186,7 @@
|
||||
No controlling PC assigned
|
||||
</div>
|
||||
<div v-else class="connected-device">
|
||||
<router-link :to="`/pcs/${controllingPc.plugin_id || controllingPc.assetid}`" class="device-link">
|
||||
<router-link :to="`/pcs/${controllingPc.pluginid || controllingPc.assetid}`" class="device-link">
|
||||
<div class="device-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
@@ -239,20 +242,20 @@ const controllingPc = computed(() => {
|
||||
|
||||
// First check incoming - computer as source controlling this equipment
|
||||
for (const rel of relationships.value.incoming || []) {
|
||||
if (rel.source_asset?.assettype_name === 'computer' && rel.relationship_type_name === 'Controls') {
|
||||
if (rel.sourceasset?.assettypename === 'computer' && rel.relationshiptypename === 'Controls') {
|
||||
return {
|
||||
...rel.source_asset,
|
||||
relationshipType: rel.relationship_type_name
|
||||
...rel.sourceasset,
|
||||
relationshipType: rel.relationshiptypename
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check outgoing - legacy data may have equipment -> computer Controls relationships
|
||||
for (const rel of relationships.value.outgoing || []) {
|
||||
if (rel.target_asset?.assettype_name === 'computer' && rel.relationship_type_name === 'Controls') {
|
||||
if (rel.targetasset?.assettypename === 'computer' && rel.relationshiptypename === 'Controls') {
|
||||
return {
|
||||
...rel.target_asset,
|
||||
relationshipType: rel.relationship_type_name
|
||||
...rel.targetasset,
|
||||
relationshipType: rel.relationshiptypename
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +130,10 @@
|
||||
<h3 class="form-section-title">Controller</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="controller_vendorid">Controller Vendor</label>
|
||||
<label for="controllervendorid">Controller Vendor</label>
|
||||
<select
|
||||
id="controller_vendorid"
|
||||
v-model="form.controller_vendorid"
|
||||
id="controllervendorid"
|
||||
v-model="form.controllervendorid"
|
||||
class="form-control"
|
||||
>
|
||||
<option value="">Select vendor...</option>
|
||||
@@ -149,10 +149,10 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="controller_modelid">Controller Model</label>
|
||||
<label for="controllermodelid">Controller Model</label>
|
||||
<select
|
||||
id="controller_modelid"
|
||||
v-model="form.controller_modelid"
|
||||
id="controllermodelid"
|
||||
v-model="form.controllermodelid"
|
||||
class="form-control"
|
||||
>
|
||||
<option value="">Select model...</option>
|
||||
@@ -164,7 +164,7 @@
|
||||
{{ m.modelnumber }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="form-help" v-if="form.controller_vendorid">Filtered by controller vendor</small>
|
||||
<small class="form-help" v-if="form.controllervendorid">Filtered by controller vendor</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -351,8 +351,8 @@ const form = ref({
|
||||
equipmenttypeid: '',
|
||||
vendorid: '',
|
||||
modelnumberid: '',
|
||||
controller_vendorid: '',
|
||||
controller_modelid: '',
|
||||
controllervendorid: '',
|
||||
controllermodelid: '',
|
||||
locationid: '',
|
||||
businessunitid: '',
|
||||
requiresmanualconfig: false,
|
||||
@@ -383,8 +383,8 @@ const filteredModels = computed(() => {
|
||||
|
||||
// Filter models by selected controller vendor
|
||||
const filteredControllerModels = computed(() => {
|
||||
if (!form.value.controller_vendorid) return models.value
|
||||
return models.value.filter(m => m.vendorid === form.value.controller_vendorid)
|
||||
if (!form.value.controllervendorid) return models.value
|
||||
return models.value.filter(m => m.vendorid === form.value.controllervendorid)
|
||||
})
|
||||
|
||||
// Clear model selection when vendor changes
|
||||
@@ -398,11 +398,11 @@ watch(() => form.value.vendorid, (newVal, oldVal) => {
|
||||
})
|
||||
|
||||
// Clear controller model when controller vendor changes
|
||||
watch(() => form.value.controller_vendorid, (newVal, oldVal) => {
|
||||
watch(() => form.value.controllervendorid, (newVal, oldVal) => {
|
||||
if (oldVal && newVal !== oldVal) {
|
||||
const currentModel = models.value.find(m => m.modelnumberid === form.value.controller_modelid)
|
||||
const currentModel = models.value.find(m => m.modelnumberid === form.value.controllermodelid)
|
||||
if (currentModel && currentModel.vendorid !== newVal) {
|
||||
form.value.controller_modelid = ''
|
||||
form.value.controllermodelid = ''
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -413,11 +413,11 @@ onMounted(async () => {
|
||||
const [typesRes, statusRes, vendorRes, locRes, modelsRes, buRes, pcsRes, relTypesRes] = await Promise.all([
|
||||
equipmentApi.types.list(),
|
||||
assetsApi.statuses.list(),
|
||||
vendorsApi.list({ per_page: 500 }),
|
||||
locationsApi.list({ per_page: 500 }),
|
||||
modelsApi.list({ per_page: 1000 }),
|
||||
businessunitsApi.list({ per_page: 500 }),
|
||||
computersApi.list({ per_page: 500 }),
|
||||
vendorsApi.list({ perpage: 500 }),
|
||||
locationsApi.list({ perpage: 500 }),
|
||||
modelsApi.list({ perpage: 1000 }),
|
||||
businessunitsApi.list({ perpage: 500 }),
|
||||
computersApi.list({ perpage: 500 }),
|
||||
assetsApi.types.list() // Used for relationship types, will fix below
|
||||
])
|
||||
|
||||
@@ -461,8 +461,8 @@ onMounted(async () => {
|
||||
equipmenttypeid: data.equipment?.equipmenttypeid || '',
|
||||
vendorid: data.equipment?.vendorid || '',
|
||||
modelnumberid: data.equipment?.modelnumberid || '',
|
||||
controller_vendorid: data.equipment?.controller_vendorid || '',
|
||||
controller_modelid: data.equipment?.controller_modelid || '',
|
||||
controllervendorid: data.equipment?.controllervendorid || '',
|
||||
controllermodelid: data.equipment?.controllermodelid || '',
|
||||
locationid: data.locationid || '',
|
||||
businessunitid: data.businessunitid || '',
|
||||
requiresmanualconfig: data.equipment?.requiresmanualconfig || false,
|
||||
@@ -480,8 +480,8 @@ onMounted(async () => {
|
||||
|
||||
// Check incoming relationships for a controlling PC
|
||||
for (const rel of relationships.incoming || []) {
|
||||
if (rel.source_asset?.assettype_name === 'computer' && rel.relationship_type_name === 'Controls') {
|
||||
controllingPcId.value = rel.source_asset.assetid
|
||||
if (rel.sourceasset?.assettypename === 'computer' && rel.relationshiptypename === 'Controls') {
|
||||
controllingPcId.value = rel.sourceasset.assetid
|
||||
relationshipTypeId.value = rel.relationshiptypeid
|
||||
existingRelationshipId.value = rel.assetrelationshipid
|
||||
break
|
||||
@@ -531,8 +531,8 @@ async function saveEquipment() {
|
||||
equipmenttypeid: form.value.equipmenttypeid || null,
|
||||
vendorid: form.value.vendorid || null,
|
||||
modelnumberid: form.value.modelnumberid || null,
|
||||
controller_vendorid: form.value.controller_vendorid || null,
|
||||
controller_modelid: form.value.controller_modelid || null,
|
||||
controllervendorid: form.value.controllervendorid || null,
|
||||
controllermodelid: form.value.controllermodelid || null,
|
||||
locationid: form.value.locationid || null,
|
||||
businessunitid: form.value.businessunitid || null,
|
||||
requiresmanualconfig: form.value.requiresmanualconfig,
|
||||
@@ -588,8 +588,8 @@ async function saveRelationship(assetId) {
|
||||
|
||||
// Create new relationship (PC controls Equipment, so PC is source, Equipment is target)
|
||||
await assetsApi.createRelationship({
|
||||
source_assetid: controllingPcId.value,
|
||||
target_assetid: assetId,
|
||||
sourceassetid: controllingPcId.value,
|
||||
targetassetid: assetId,
|
||||
relationshiptypeid: relationshipTypeId.value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<th>Name</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Type</th>
|
||||
<th>Vendor</th>
|
||||
<th>Status</th>
|
||||
<th>Location</th>
|
||||
<th>Actions</th>
|
||||
@@ -38,13 +39,14 @@
|
||||
<td>{{ item.assetnumber }}</td>
|
||||
<td>{{ item.name || '-' }}</td>
|
||||
<td class="mono">{{ item.serialnumber || '-' }}</td>
|
||||
<td>{{ item.equipment?.equipmenttype_name || '-' }}</td>
|
||||
<td>{{ item.equipment?.equipmenttypename || '-' }}</td>
|
||||
<td>{{ item.equipment?.vendorname || '-' }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="getStatusClass(item.status_name)">
|
||||
{{ item.status_name || 'Unknown' }}
|
||||
<span class="badge" :class="getStatusClass(item.statusname)">
|
||||
{{ item.statusname || 'Unknown' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ item.location_name || '-' }}</td>
|
||||
<td>{{ item.locationname || '-' }}</td>
|
||||
<td class="actions">
|
||||
<router-link
|
||||
:to="`/machines/${item.equipment?.equipmentid || item.assetid}`"
|
||||
@@ -55,7 +57,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="equipment.length === 0">
|
||||
<td colspan="7" style="text-align: center; color: var(--text-light);">
|
||||
<td colspan="8" style="text-align: center; color: var(--text-light);">
|
||||
No equipment found
|
||||
</td>
|
||||
</tr>
|
||||
@@ -64,16 +66,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination" v-if="totalPages > 1">
|
||||
<button
|
||||
v-for="p in totalPages"
|
||||
:key="p"
|
||||
:class="{ active: p === page }"
|
||||
@click="goToPage(p)"
|
||||
>
|
||||
{{ p }}
|
||||
</button>
|
||||
</div>
|
||||
<PaginationBar
|
||||
:page="page"
|
||||
:totalPages="totalPages"
|
||||
:perPage="perPage"
|
||||
@update:page="goToPage"
|
||||
@update:perPage="changePerPage"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -83,12 +82,14 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { equipmentApi } from '../../api'
|
||||
import PaginationBar from '../../components/PaginationBar.vue'
|
||||
|
||||
const equipment = ref([])
|
||||
const loading = ref(true)
|
||||
const search = ref('')
|
||||
const page = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const perPage = ref(20)
|
||||
|
||||
let searchTimeout = null
|
||||
|
||||
@@ -101,13 +102,13 @@ async function loadEquipment() {
|
||||
try {
|
||||
const params = {
|
||||
page: page.value,
|
||||
per_page: 20
|
||||
perpage: perPage.value
|
||||
}
|
||||
if (search.value) params.search = search.value
|
||||
|
||||
const response = await equipmentApi.list(params)
|
||||
equipment.value = response.data.data || []
|
||||
totalPages.value = response.data.meta?.pagination?.total_pages || 1
|
||||
totalPages.value = response.data.meta?.pagination?.totalpages || response.data.meta?.pagination?.total_pages || 1
|
||||
} catch (error) {
|
||||
console.error('Error loading equipment:', error)
|
||||
} finally {
|
||||
@@ -128,6 +129,12 @@ function goToPage(p) {
|
||||
loadEquipment()
|
||||
}
|
||||
|
||||
function changePerPage(newPerPage) {
|
||||
perPage.value = newPerPage
|
||||
page.value = 1
|
||||
loadEquipment()
|
||||
}
|
||||
|
||||
function getStatusClass(status) {
|
||||
if (!status) return 'badge-info'
|
||||
const s = status.toLowerCase()
|
||||
|
||||
Reference in New Issue
Block a user