printedparts stage 17: gage-lab asset tag + print-files redesign
The gage lab assigns real WJRP asset numbers, so identity splits: the internal itemcode stays auto-minted and a new optional unique gagelabtag (migration 0003) carries the lab's number - settable on create/edit, searchable, and resolved by the kiosk for scans and bare keypad digits against the numeric tail of either identifier (unique-match only). The print-files table becomes stacked revision cards - filename with rev/current badges, one meta line, delete pinned right - ending the horizontal scroll in that column.
This commit is contained in:
@@ -47,6 +47,10 @@
|
||||
<span class="info-label">Item code</span>
|
||||
<span class="info-value">{{ item.itemcode }}</span>
|
||||
</div>
|
||||
<div class="info-row" v-if="item.gagelabtag">
|
||||
<span class="info-label">Gage lab tag</span>
|
||||
<span class="info-value">{{ item.gagelabtag }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Bin location</span>
|
||||
<span class="info-value">{{ item.binlocation || '-' }}</span>
|
||||
@@ -81,42 +85,30 @@
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="fileError" class="error-message">{{ fileError }}</div>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rev</th>
|
||||
<th>File</th>
|
||||
<th>Size</th>
|
||||
<th>By</th>
|
||||
<th>Note</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="revision in files" :key="revision.fileid"
|
||||
:class="{ 'current-revision': revision === files[0] }">
|
||||
<td>{{ revision.revision }}</td>
|
||||
<td>
|
||||
<a :href="withBase(`/api/printedparts/files/${revision.fileid}/download`)">
|
||||
{{ revision.filename }}
|
||||
</a>
|
||||
<span v-if="revision === files[0]" class="badge badge-success">current</span>
|
||||
</td>
|
||||
<td>{{ formatSize(revision.filesize) }}</td>
|
||||
<td :title="revision.uploadeddate">{{ revision.uploadedby }}</td>
|
||||
<td>{{ revision.uploadnote || '-' }}</td>
|
||||
<td class="actions">
|
||||
<button class="btn btn-secondary btn-sm"
|
||||
@click="removeRevision(revision)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="files.length === 0">
|
||||
<td colspan="6" class="empty-state">No print file uploaded yet</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul class="file-revision-list">
|
||||
<li v-for="revision in files" :key="revision.fileid"
|
||||
:class="{ 'current-revision': revision === files[0] }">
|
||||
<div class="file-main">
|
||||
<a :href="withBase(`/api/printedparts/files/${revision.fileid}/download`)"
|
||||
class="file-name">
|
||||
{{ revision.filename }}
|
||||
</a>
|
||||
<span class="badge badge-secondary">rev {{ revision.revision }}</span>
|
||||
<span v-if="revision === files[0]" class="badge badge-success">current</span>
|
||||
</div>
|
||||
<div class="file-meta">
|
||||
{{ formatSize(revision.filesize) }} -
|
||||
{{ revision.uploadedby }} -
|
||||
{{ formatDate(revision.uploadeddate) }}
|
||||
<span v-if="revision.uploadnote"> - {{ revision.uploadnote }}</span>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm file-delete"
|
||||
@click="removeRevision(revision)">Delete</button>
|
||||
</li>
|
||||
<li v-if="files.length === 0" class="empty-state">
|
||||
No print file uploaded yet
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
@@ -344,6 +336,40 @@ function formatDate(value) {
|
||||
margin-bottom: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.current-revision td { font-weight: 600; }
|
||||
.file-revision-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.file-revision-list li {
|
||||
position: relative;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.45rem;
|
||||
padding: 0.6rem 5.5rem 0.6rem 0.8rem;
|
||||
}
|
||||
.file-revision-list li.current-revision { border-color: var(--primary); }
|
||||
.file-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.file-name {
|
||||
font-weight: 600;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.file-meta {
|
||||
color: var(--text-light);
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.file-delete {
|
||||
position: absolute;
|
||||
top: 0.55rem;
|
||||
right: 0.6rem;
|
||||
}
|
||||
.qty-in { color: var(--success); }
|
||||
</style>
|
||||
|
||||
@@ -32,9 +32,14 @@
|
||||
<input v-model.number="form.lowstockthreshold" type="number" min="0"
|
||||
class="form-control" />
|
||||
</div>
|
||||
<div v-if="isEdit" class="form-group">
|
||||
<label>Item code</label>
|
||||
<input :value="itemcode" type="text" class="form-control" disabled />
|
||||
<div class="form-group">
|
||||
<label>Gage lab asset tag</label>
|
||||
<input v-model="form.gagelabtag" type="text" class="form-control"
|
||||
placeholder="e.g. WJRP0117" />
|
||||
<p class="form-hint">
|
||||
Assigned by the gage lab; optional. The kiosk finds parts by
|
||||
this tag or by the internal code.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,12 +86,12 @@ const cancelTarget = computed(() =>
|
||||
|
||||
const form = ref({
|
||||
itemname: '',
|
||||
gagelabtag: '',
|
||||
itemdescription: '',
|
||||
lowstockthreshold: null,
|
||||
binlocation: '',
|
||||
printnotes: ''
|
||||
})
|
||||
const itemcode = ref('')
|
||||
const imageurl = ref(null)
|
||||
const saving = ref(false)
|
||||
const error = ref('')
|
||||
@@ -99,7 +104,6 @@ onMounted(async () => {
|
||||
for (const key of Object.keys(form.value)) {
|
||||
form.value[key] = item[key]
|
||||
}
|
||||
itemcode.value = item.itemcode
|
||||
imageurl.value = item.imageurl
|
||||
} catch (loadError) {
|
||||
error.value = 'Could not load the item'
|
||||
@@ -115,6 +119,7 @@ async function save() {
|
||||
if (payload.lowstockthreshold === null || payload.lowstockthreshold === '') {
|
||||
delete payload.lowstockthreshold
|
||||
}
|
||||
if (!payload.gagelabtag) payload.gagelabtag = ''
|
||||
let printeditemid
|
||||
if (isEdit.value) {
|
||||
await printedpartsApi.update(route.params.id, payload)
|
||||
|
||||
Reference in New Issue
Block a user