376 lines
15 KiB
Vue
376 lines
15 KiB
Vue
<template>
|
|
<div class="foto-first-step-4">
|
|
<div class="step-header mb-4">
|
|
<h4 class="mb-2">Import Completion</h4>
|
|
<p class="text-muted">Review and finalize your imported items.</p>
|
|
</div>
|
|
|
|
<!-- Import Summary -->
|
|
<div class="import-summary mb-4">
|
|
<div class="row">
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<h3 class="text-primary mb-2">{{ totalItems }}</h3>
|
|
<p class="card-text text-muted mb-0">Items Imported</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<h3 class="text-success mb-2">{{ categorizedItems }}</h3>
|
|
<p class="card-text text-muted mb-0">With Categories</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<h3 class="text-info mb-2">{{ itemsWithLocation }}</h3>
|
|
<p class="card-text text-muted mb-0">With Locations</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<h3 class="text-warning mb-2">${{ totalValue }}</h3>
|
|
<p class="card-text text-muted mb-0">Total Value</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Breakdown -->
|
|
<div class="category-breakdown mb-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">Items by Category</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div v-if="Object.keys(categoryBreakdown).length > 0" class="row">
|
|
<div v-for="(count, category) in categoryBreakdown" :key="category" class="col-sm-6 col-md-4 col-lg-3 mb-2">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span class="text-capitalize">{{ category || 'Uncategorized' }}</span>
|
|
<span class="badge bg-secondary">{{ count }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="text-muted text-center py-3">
|
|
No items to categorize
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Import Options -->
|
|
<div class="import-options mb-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">Import Options</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-check mb-3">
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="generateQr"
|
|
v-model="importOptions.generate_qr_codes"
|
|
>
|
|
<label class="form-check-label" for="generateQr">
|
|
Generate QR codes for items
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-3">
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="sendNotification"
|
|
v-model="importOptions.send_notification"
|
|
>
|
|
<label class="form-check-label" for="sendNotification">
|
|
Send completion notification
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check mb-3">
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="createReport"
|
|
v-model="importOptions.create_report"
|
|
>
|
|
<label class="form-check-label" for="createReport">
|
|
Create import report
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-3">
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="autoBackup"
|
|
v-model="importOptions.auto_backup"
|
|
>
|
|
<label class="form-check-label" for="autoBackup">
|
|
Auto-backup imported data
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Item List -->
|
|
<div class="item-list mb-4">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">Imported Items</h6>
|
|
<div class="btn-group btn-group-sm">
|
|
<button
|
|
class="btn"
|
|
:class="viewMode === 'grid' ? 'btn-primary' : 'btn-outline-primary'"
|
|
@click="viewMode = 'grid'"
|
|
>
|
|
<b-icon-grid></b-icon-grid>
|
|
</button>
|
|
<button
|
|
class="btn"
|
|
:class="viewMode === 'list' ? 'btn-primary' : 'btn-outline-primary'"
|
|
@click="viewMode = 'list'"
|
|
>
|
|
<b-icon-list></b-icon-list>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Grid View -->
|
|
<div v-if="viewMode === 'grid'" class="row">
|
|
<div v-for="(item, index) in items" :key="index" class="col-sm-6 col-md-4 col-lg-3 mb-3">
|
|
<div class="card h-100">
|
|
<img :src="item.image.processedUrl || item.image.preview" class="card-img-top item-thumb" :alt="item.details.name">
|
|
<div class="card-body p-2">
|
|
<h6 class="card-title mb-1">{{ item.details.name }}</h6>
|
|
<p class="card-text small text-muted mb-1">{{ item.details.category || 'No category' }}</p>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<small class="text-muted">Qty: {{ item.details.quantity }}</small>
|
|
<small v-if="item.details.estimated_value" class="text-success">${{ item.details.estimated_value }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List View -->
|
|
<div v-else class="table-responsive">
|
|
<table class="table table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Image</th>
|
|
<th>Name</th>
|
|
<th>Category</th>
|
|
<th>Quantity</th>
|
|
<th>Location</th>
|
|
<th>Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, index) in items" :key="index">
|
|
<td>
|
|
<img :src="item.image.processedUrl || item.image.preview" class="list-item-thumb" :alt="item.details.name">
|
|
</td>
|
|
<td class="fw-bold">{{ item.details.name }}</td>
|
|
<td>
|
|
<span v-if="item.details.category" class="badge bg-light text-dark">{{ item.details.category }}</span>
|
|
<span v-else class="text-muted">-</span>
|
|
</td>
|
|
<td>{{ item.details.quantity }} {{ item.details.unit }}</td>
|
|
<td>{{ item.details.location || '-' }}</td>
|
|
<td>
|
|
<span v-if="item.details.estimated_value" class="text-success">${{ item.details.estimated_value }}</span>
|
|
<span v-else class="text-muted">-</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Final Action -->
|
|
<div class="final-action text-center">
|
|
<div class="card">
|
|
<div class="card-body py-4">
|
|
<b-icon-check-circle class="text-success mb-3" style="font-size: 3rem;"></b-icon-check-circle>
|
|
<h5 class="mb-3">Ready to Complete Import</h5>
|
|
<p class="text-muted mb-4">
|
|
All {{ totalItems }} items have been processed and are ready to be added to your inventory.
|
|
This action cannot be undone.
|
|
</p>
|
|
<div class="d-flex justify-content-center gap-3">
|
|
<button class="btn btn-outline-secondary" @click="$emit('prev')">
|
|
<b-icon-chevron-left class="me-1"></b-icon-chevron-left>
|
|
Go Back
|
|
</button>
|
|
<button
|
|
class="btn btn-success btn-lg"
|
|
@click="completeImport"
|
|
:disabled="importing"
|
|
>
|
|
<div v-if="importing" class="spinner-border spinner-border-sm me-2" role="status"></div>
|
|
<b-icon-check-circle v-else class="me-2"></b-icon-check-circle>
|
|
{{ importing ? 'Importing...' : 'Complete Import' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import * as BIcons from "bootstrap-icons-vue";
|
|
|
|
export default {
|
|
name: 'FotoFirstStep4',
|
|
components: {
|
|
...BIcons
|
|
},
|
|
props: {
|
|
workflowInstance: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
step: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
payload: {
|
|
type: Object,
|
|
default: () => ({})
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
importing: false,
|
|
viewMode: 'grid',
|
|
importOptions: {
|
|
generate_qr_codes: true,
|
|
send_notification: true,
|
|
create_report: true,
|
|
auto_backup: false
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
items() {
|
|
return this.payload.completed_items || [];
|
|
},
|
|
totalItems() {
|
|
return this.items.length;
|
|
},
|
|
categorizedItems() {
|
|
return this.items.filter(item => item.details.category).length;
|
|
},
|
|
itemsWithLocation() {
|
|
return this.items.filter(item => item.details.location).length;
|
|
},
|
|
totalValue() {
|
|
return this.items.reduce((sum, item) => {
|
|
return sum + (item.details.estimated_value || 0);
|
|
}, 0).toFixed(2);
|
|
},
|
|
categoryBreakdown() {
|
|
const breakdown = {};
|
|
this.items.forEach(item => {
|
|
const category = item.details.category || 'uncategorized';
|
|
breakdown[category] = (breakdown[category] || 0) + 1;
|
|
});
|
|
return breakdown;
|
|
}
|
|
},
|
|
mounted() {
|
|
// Load import options from payload
|
|
if (this.payload.import_options) {
|
|
this.importOptions = { ...this.importOptions, ...this.payload.import_options };
|
|
}
|
|
},
|
|
methods: {
|
|
async completeImport() {
|
|
if (this.totalItems === 0) {
|
|
alert('No items to import. Please go back and add items.');
|
|
return;
|
|
}
|
|
|
|
const confirmed = confirm(
|
|
`Are you sure you want to import ${this.totalItems} items? This action cannot be undone.`
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
this.importing = true;
|
|
|
|
// Update payload with final options
|
|
this.updatePayload();
|
|
|
|
// Simulate import process
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Complete the workflow
|
|
this.$emit('update', {
|
|
import_completed: true,
|
|
completion_timestamp: new Date().toISOString()
|
|
});
|
|
|
|
// Navigate to success or trigger workflow completion
|
|
this.$emit('complete');
|
|
|
|
} catch (error) {
|
|
console.error('Error completing import:', error);
|
|
alert('Error completing import. Please try again.');
|
|
} finally {
|
|
this.importing = false;
|
|
}
|
|
},
|
|
|
|
updatePayload() {
|
|
this.$emit('update', {
|
|
import_options: this.importOptions,
|
|
final_summary: {
|
|
total_items: this.totalItems,
|
|
categorized_items: this.categorizedItems,
|
|
items_with_location: this.itemsWithLocation,
|
|
total_value: parseFloat(this.totalValue),
|
|
category_breakdown: this.categoryBreakdown
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.item-thumb {
|
|
height: 120px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.list-item-thumb {
|
|
width: 40px;
|
|
height: 40px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.final-action .card {
|
|
border: 2px solid #28a745;
|
|
background: linear-gradient(135deg, #f8fff8 0%, #e8f5e8 100%);
|
|
}
|
|
</style>
|