Files
shopdb-flask/frontend/src/views/machines/MachinesList.vue
cproudlock 78a0ee8d83 Add custom fields + warranty plugin, rework settings into two-pane shell
Feature work from the 2026-07 session:

Settings IA
- Replace the flat 27-card settings hub with a persistent two-pane shell
  (SettingsLayout.vue): grouped, searchable left rail + content pane.
- Nest all settings/* routes under the shell via router post-processing;
  shared nav catalog in settingsNav.js. Group by asset class (PCs, Printers,
  Equipment, Network) so per-type settings stop scattering.

Custom fields (core)
- customfields + customfieldvalues tables (migration 7d14), CRUD API at
  /api/customfields, per-asset value get/save.
- Settings management page + reusable CustomFieldsSection (detail) and
  CustomFieldsInputs (form) wired into all four asset types.

Warranty (new plugin)
- plugins/warranty: warranties + warrantyassets (migration 7d15), derived
  coverage status, provider abstraction (manual now; Dell/Lenovo/HP stubs).
- API CRUD + per-asset panel + report buckets; WarrantyPanel on all four
  detail pages; Warranties management page; Warranty report + Reports card.
- Seed warranty.* permissions.

Printer drivers
- printerdrivers table (migration 7d13) linked to printer models; drivers now
  surface on the matching printer's detail page.

Other
- PCDetail rebalanced (Network + Status + Warranty + custom fields on the right).
- Rename PCs list "Features" column to "Remote Access"; fix badge hover underline.
- Drop equipment islocationonly field.
- Centralize asset-type label/route maps into utils/assetTypes.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:37:21 -04:00

145 lines
3.7 KiB
Vue

<template>
<div>
<div class="page-header">
<h2>Equipment</h2>
<router-link to="/machines/new" class="btn btn-primary">Add Equipment</router-link>
</div>
<!-- Filters -->
<div class="filters">
<input
v-model="search"
type="text"
class="form-control"
placeholder="Search equipment..."
@input="debouncedSearch"
/>
</div>
<div class="card">
<div v-if="loading" class="loading">Loading...</div>
<template v-else>
<div class="table-container">
<table>
<thead>
<tr>
<th>Machine #</th>
<th>Name</th>
<th>Serial Number</th>
<th>Type</th>
<th>Vendor</th>
<th>Status</th>
<th>Location</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="item in equipment" :key="item.assetid">
<td>{{ item.assetnumber }}</td>
<td>{{ item.name || '-' }}</td>
<td class="mono">{{ item.serialnumber || '-' }}</td>
<td>{{ item.equipment?.equipmenttypename || '-' }}</td>
<td>{{ item.equipment?.vendorname || '-' }}</td>
<td>
<span class="badge" :style="colorStyle(item.statuscolor)">
{{ item.statusname || 'Unknown' }}
</span>
</td>
<td>{{ item.locationname || '-' }}</td>
<td class="actions">
<router-link
:to="`/machines/${item.equipment?.equipmentid || item.assetid}`"
class="btn btn-secondary btn-sm"
>
View
</router-link>
</td>
</tr>
<tr v-if="equipment.length === 0">
<td colspan="8" style="text-align: center; color: var(--text-light);">
No equipment found
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<PaginationBar
:page="page"
:totalPages="totalPages"
:perPage="perPage"
@update:page="goToPage"
@update:perPage="changePerPage"
/>
</template>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { colorStyle } from "@/utils/colorStyle"
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
onMounted(() => {
loadEquipment()
})
async function loadEquipment() {
loading.value = true
try {
const params = {
page: page.value,
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?.totalpages || response.data.meta?.pagination?.total_pages || 1
} catch (error) {
console.error('Error loading equipment:', error)
} finally {
loading.value = false
}
}
function debouncedSearch() {
clearTimeout(searchTimeout)
searchTimeout = setTimeout(() => {
page.value = 1
loadEquipment()
}, 300)
}
function goToPage(p) {
page.value = p
loadEquipment()
}
function changePerPage(newPerPage) {
perPage.value = newPerPage
page.value = 1
loadEquipment()
}
</script>
<style scoped>
.mono {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
}
</style>