toolshed/frontend/src/components/workflow/steps/FotoFirstStep4.vue
2026-07-23 04:41:04 +02:00

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>