stash
This commit is contained in:
parent
cb0437c98b
commit
b489b56e6a
8 changed files with 60 additions and 70 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
<b-icon-chevron-down v-else></b-icon-chevron-down>
|
<b-icon-chevron-down v-else></b-icon-chevron-down>
|
||||||
</button>
|
</button>
|
||||||
<span v-else class="tree-view-toggle-spacer"></span>
|
<span v-else class="tree-view-toggle-spacer"></span>
|
||||||
<div class="tree-view-content">
|
<div class="tree-view-content text-truncate">
|
||||||
<slot :item="node" :expanded="isExpanded(node)" :has-children="hasChildren(node)"
|
<slot :item="node" :expanded="isExpanded(node)" :has-children="hasChildren(node)"
|
||||||
:depth="depth" :toggle="() => toggleNode(node)"></slot>
|
:depth="depth" :toggle="() => toggleNode(node)"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -64,9 +64,6 @@
|
||||||
.tree-view-content {
|
.tree-view-content {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,45 +13,40 @@
|
||||||
<div class="upload-section mb-4">
|
<div class="upload-section mb-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div v-if="!uploadedFile" class="upload-dropzone text-center py-5"
|
<drag-drop-file-source v-if="!uploadedFile" @input="handleFilesDropped">
|
||||||
@dragover.prevent
|
<div class="upload-dropzone text-center py-5">
|
||||||
@dragenter.prevent
|
<b-icon-cloud-upload class="text-primary mb-3" style="font-size: 4rem;"></b-icon-cloud-upload>
|
||||||
@drop.prevent="handleFileDrop"
|
<h5 class="mb-3">Upload Your Data File</h5>
|
||||||
:class="{ 'dragover': isDragOver }"
|
<p class="text-muted mb-4">
|
||||||
@dragenter="isDragOver = true"
|
Drag and drop your CSV or Excel file here, or click to browse
|
||||||
@dragleave="isDragOver = false">
|
</p>
|
||||||
|
|
||||||
<b-icon-cloud-upload class="text-primary mb-3" style="font-size: 4rem;"></b-icon-cloud-upload>
|
<input
|
||||||
<h5 class="mb-3">Upload Your Data File</h5>
|
type="file"
|
||||||
<p class="text-muted mb-4">
|
ref="fileInput"
|
||||||
Drag and drop your CSV or Excel file here, or click to browse
|
accept=".csv,.xlsx,.xls"
|
||||||
</p>
|
@change="handleFileSelect"
|
||||||
|
class="d-none"
|
||||||
|
/>
|
||||||
|
|
||||||
<input
|
<div class="mb-3">
|
||||||
type="file"
|
<button class="btn btn-primary btn-lg me-2" @click="$refs.fileInput.click()">
|
||||||
ref="fileInput"
|
<b-icon-folder-open class="me-2"></b-icon-folder-open>
|
||||||
accept=".csv,.xlsx,.xls"
|
Choose File
|
||||||
@change="handleFileSelect"
|
</button>
|
||||||
class="d-none"
|
<button class="btn btn-outline-info" @click="downloadTemplate">
|
||||||
/>
|
<b-icon-download class="me-2"></b-icon-download>
|
||||||
|
Download Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="supported-formats">
|
||||||
<button class="btn btn-primary btn-lg me-2" @click="$refs.fileInput.click()">
|
<small class="text-muted">
|
||||||
<b-icon-folder-open class="me-2"></b-icon-folder-open>
|
Supported formats: CSV (.csv), Excel (.xlsx, .xls)
|
||||||
Choose File
|
</small>
|
||||||
</button>
|
</div>
|
||||||
<button class="btn btn-outline-info" @click="downloadTemplate">
|
|
||||||
<b-icon-download class="me-2"></b-icon-download>
|
|
||||||
Download Template
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</drag-drop-file-source>
|
||||||
<div class="supported-formats">
|
|
||||||
<small class="text-muted">
|
|
||||||
Supported formats: CSV (.csv), Excel (.xlsx, .xls)
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- File Info Display -->
|
<!-- File Info Display -->
|
||||||
<div v-else class="file-info">
|
<div v-else class="file-info">
|
||||||
|
|
@ -264,6 +259,7 @@
|
||||||
<script>
|
<script>
|
||||||
import * as BIcons from "bootstrap-icons-vue";
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
|
import DragDropFileSource from "@/components/inputs/DragDropFileSource.vue";
|
||||||
|
|
||||||
// Implements every step of 'import-items'; steps 2-5,7 use a generic placeholder. See docs/implementation.md#bulk-item-import-workflow.
|
// Implements every step of 'import-items'; steps 2-5,7 use a generic placeholder. See docs/implementation.md#bulk-item-import-workflow.
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -299,6 +295,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
DragDropFileSource,
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -322,7 +319,6 @@ export default {
|
||||||
uploadTime: null,
|
uploadTime: null,
|
||||||
processingFile: false,
|
processingFile: false,
|
||||||
processingStatus: '',
|
processingStatus: '',
|
||||||
isDragOver: false,
|
|
||||||
uploadError: null,
|
uploadError: null,
|
||||||
fileAnalysis: null,
|
fileAnalysis: null,
|
||||||
detectedColumns: [],
|
detectedColumns: [],
|
||||||
|
|
@ -372,9 +368,9 @@ export default {
|
||||||
...mapActions(['createInventoryItem']),
|
...mapActions(['createInventoryItem']),
|
||||||
|
|
||||||
// --- Step 1: File upload ---
|
// --- Step 1: File upload ---
|
||||||
handleFileDrop(event) {
|
// DragDropFileSource already hashed/base64-encoded the dropped file to match the
|
||||||
this.isDragOver = false;
|
// content-addressed file model - readAsText() below reverses that back to text.
|
||||||
const files = event.dataTransfer.files;
|
handleFilesDropped(files) {
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
this.processFile(files[0]);
|
this.processFile(files[0]);
|
||||||
}
|
}
|
||||||
|
|
@ -418,10 +414,20 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Accepts either a browser File (from the "Choose File" input) or the hashed
|
||||||
|
// {name, size, mime_type, data, hash} descriptor DragDropFileSource emits on drop.
|
||||||
|
async readAsText(file) {
|
||||||
|
if (typeof file.text === 'function') {
|
||||||
|
return file.text();
|
||||||
|
}
|
||||||
|
const bytes = Uint8Array.from(atob(file.data), c => c.charCodeAt(0));
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
},
|
||||||
|
|
||||||
async analyzeFile(file) {
|
async analyzeFile(file) {
|
||||||
// Parsing happens entirely client-side; the backend only ever sees the resulting `items` list.
|
// Parsing happens entirely client-side; the backend only ever sees the resulting `items` list.
|
||||||
if (file.name.endsWith('.csv')) {
|
if (file.name.endsWith('.csv')) {
|
||||||
const text = await file.text();
|
const text = await this.readAsText(file);
|
||||||
this.parsedRows = this.parseCsv(text);
|
this.parsedRows = this.parseCsv(text);
|
||||||
} else {
|
} else {
|
||||||
// Mock implementation; a real Excel parser would use a library like SheetJS.
|
// Mock implementation; a real Excel parser would use a library like SheetJS.
|
||||||
|
|
@ -619,7 +625,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-dropzone:hover,
|
.upload-dropzone:hover,
|
||||||
.upload-dropzone.dragover {
|
.dragover .upload-dropzone {
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
background-color: #f8f9ff;
|
background-color: #f8f9ff;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,3 +68,7 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-action {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
@ -78,3 +78,11 @@
|
||||||
.input-group-text:not(:last-child) {
|
.input-group-text:not(:last-child) {
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-invalid input, .is-invalid select {
|
||||||
|
border: 1px solid var(--bs-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-invalid .invalid-feedback {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
@ -300,10 +300,6 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.table-action {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unguarded.btn-danger,
|
.unguarded.btn-danger,
|
||||||
.unguarded.btn-danger:hover,
|
.unguarded.btn-danger:hover,
|
||||||
.unguarded.btn-danger:focus {
|
.unguarded.btn-danger:focus {
|
||||||
|
|
|
||||||
|
|
@ -222,14 +222,6 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.is-invalid input, .is-invalid select {
|
|
||||||
border: 1px solid var(--bs-danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-invalid .invalid-feedback {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-text svg {
|
.input-group-text svg {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,14 +196,6 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.is-invalid input, .is-invalid select {
|
|
||||||
border: 1px solid var(--bs-danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-invalid .invalid-feedback {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -312,11 +312,6 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The th widths above leave Actions only the leftover 5% - without this it wraps mid-icon. */
|
|
||||||
.table-action {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-group.mt-auto {
|
.btn-group.mt-auto {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue