This commit is contained in:
j3d1 2026-08-01 22:20:45 +02:00
parent cfcc2c15d3
commit 4f2fe011c0
28 changed files with 3499 additions and 2922 deletions

View file

@ -0,0 +1,102 @@
<template>
<BaseLayout>
<main class="content">
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">{{ item.name }}</div>
<div class="card-body">
<div class="mb-3">
<label for="description" class="form-label">Description</label>
Foreighn {{ item.description }}
</div>
<div class="mb-3">
<label for="tags" class="form-label">Tags</label>
<span class="badge bg-dark" v-for="(tag, index) in item.tags" :key="index">
{{ getNameFromHandle(tag) }}
</span>
</div>
<div class="mb-3">
<label for="property" class="form-label">Properties</label>
<span class="badge bg-dark" v-for="(property, index) in item.properties" :key="index">
{{ property.name }}={{ property.value }}
</span>
</div>
<div class="mb-3">
<label for="quantity" class="form-label">Quantity</label>
{{ item.owned_quantity }}
</div>
<div class="mb-3">
<label for="image" class="form-label">Image</label>
<div>
<authenticated-image v-for="file in item.files" :key="file.id" :src="file.name"
:owner="file.owner" class="img-thumbnail border-info"></authenticated-image>
</div>
</div>
</div>
</div>
<div class="card">
<button class="btn btn-primary" @click="$router.push('/inventory/' + id + '/edit')">
<b-icon-pencil-square></b-icon-pencil-square>
Edit
</button>
<button type="submit" class="btn btn-danger"
@click="deleteInventoryItem(item).then(() => $router.push('/inventory'))">
<b-icon-trash></b-icon-trash>
Delete
</button>
</div>
</div>
</div>
</main>
</BaseLayout>
</template>
<script>
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import {mapActions, mapGetters, mapState} from "vuex";
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
export default {
name: "InventoryDetail",
components: {
AuthenticatedImage,
BaseLayout,
...BIcons
},
props: {
id: {
type: String,
required: true
}
},
computed: {
...mapGetters(["loaded_items", "getNameFromHandle"]),
...mapState(["storage_locations"]),
item() {
return this.loaded_items.find(item => item.id === parseInt(this.id)) || {}
},
location() {
return this.storage_locations.find(loc => loc.id === this.item.storage_location) || null
}
},
methods: {
...mapActions(["fetchInventoryItems", "deleteInventoryItem", "fetchFilesByItem", "fetchStorageLocations"]),
},
async mounted() {
await this.fetchInventoryItems()
await this.fetchStorageLocations()
}
}
</script>
<style scoped>
img {
width: 190px;
height: 107px;
object-fit: contain;
}
</style>

View file

@ -27,20 +27,13 @@
<tbody>
<tr v-for="item in search_results" :key="item.id">
<td>
<router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link>
<router-link :to="`/inventory/${item.handle}`">{{ item.name }}</router-link>
</td>
<td>
<user-name-tag :user="item"/>
</td>
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
<td class="table-action">
<!--<router-link :to="`/inventory/${item.id}/edit`">
<b-icon-pencil-square></b-icon-pencil-square>
</router-link>
<a :href="`/inventory/${item.id}/delete`"
@click.prevent="deleteInventoryItem(item)">
<b-icon-trash></b-icon-trash>
</a>-->
</td>
</tr>
</tbody>
@ -56,7 +49,7 @@
class="card-img-top img-preview"/>
<div class="card-body">
<h5 class="card-title mb-0">
<router-link :to="`/inventory/${item.id}`">
<router-link :to="`/inventory/${item.handle}`">
{{ item.name }}
</router-link></h5>
<div class="card-text text-black-50">
@ -78,7 +71,7 @@
</template>
<script>
import {mapActions} from 'vuex';
import {mapActions, mapState} from 'vuex';
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import SearchBox from "@/components/SearchBox.vue";
@ -114,10 +107,13 @@ export default {
},
loadResults() {
this.fetchSearchResults({query: this.query}).then((results) => {
this.search_results = results;
this.search_results = results.map(e=>({...e, handle: e.owner==this.user?e.id:"shared/"+e.owner+"/"+e.id}));
});
}
},
computed: {
...mapState(['user'])
},
watch: {
query: {
immediate: false,

View file

@ -142,14 +142,15 @@
<div class="card-body">
<!-- Step-specific content based on workflow type and current step -->
<component
v-if="stepComponent"
:is="stepComponent"
v-if="workflowComponent"
:is="workflowComponent"
:workflow-instance="workflowInstance"
:step="currentStep"
:payload="workflowInstance.payload"
@update="handleStepUpdate"
@next="handleNextStep"
@prev="handlePrevStep"
@complete="completeWorkflow"
/>
<!-- Default step content if no specific component -->
@ -201,8 +202,7 @@
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import { mapState, mapActions } from 'vuex';
import { workflowRegistry } from '@/workflows.js';
import { getStepComponent, hasStepComponent } from '@/components/workflow/ComponentRegistry.js';
import { getWorkflow, getWorkflowComponent } from '@/workflows.js';
export default {
name: 'WorkflowDetail',
@ -238,11 +238,11 @@ export default {
workflowDefinition() {
// `name` doubles as the workflow type identifier (e.g. 'import-items').
if (!this.workflowInstance?.name) return null;
return workflowRegistry.get(this.workflowInstance.name);
return getWorkflow(this.workflowInstance.name);
},
stepDefinitions() {
return this.workflowDefinition?.getStepDefinitions() || [];
return this.workflowDefinition?.stepDefinitions || [];
},
totalSteps() {
@ -257,13 +257,12 @@ export default {
return this.workflowInstance?.state === 'running';
},
stepComponent() {
// Return step-specific component if it exists using the component registry
workflowComponent() {
// Return the single component implementing the whole workflow, if any,
// using the workflow component registry. The component itself decides
// what to render based on the `step` prop it receives.
const workflowType = this.workflowInstance?.name;
if (workflowType && this.currentStep) {
return getStepComponent(workflowType, this.currentStep);
}
return null;
return workflowType ? getWorkflowComponent(workflowType) : null;
},
currentStepIndex() {
@ -282,8 +281,8 @@ export default {
},
hasCustomComponent() {
// Check if the current step has a custom component defined
return this.stepComponent !== null;
// Check if the current workflow has a custom component defined
return this.workflowComponent !== null;
},
componentStatusClass() {
@ -463,12 +462,6 @@ export default {
formatDateTime(dateString) {
if (!dateString) return 'N/A';
return new Date(dateString).toLocaleString();
},
hasStepComponent(stepNumber) {
// Check if a specific step has a custom component available
const workflowType = this.workflowInstance?.name;
return workflowType ? hasStepComponent(workflowType, stepNumber) : false;
}
}
}

View file

@ -128,7 +128,7 @@
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import { mapState, mapActions } from 'vuex';
import { workflowRegistry } from '@/workflows.js';
import { getAllWorkflows, getWorkflow, buildWorkflowApiPayload } from '@/workflows.js';
export default {
name: 'Workflows',
@ -148,7 +148,7 @@ export default {
return this.active_workflows;
},
availableWorkflows() {
return workflowRegistry.getAll();
return getAllWorkflows();
}
},
async mounted() {
@ -221,22 +221,22 @@ export default {
},
getWorkflowDisplayName(workflow) {
// `name` doubles as the workflow type identifier (e.g. 'import-items').
return workflowRegistry.get(workflow.name)?.name || workflow.name;
return getWorkflow(workflow.name)?.name || workflow.name;
},
async startWorkflow(workflow) {
try {
this.loading = true;
this.error = null;
// Use the workflow's toApiFormat method to get properly formatted data
const workflowData = workflow.toApiFormat();
// Build the properly formatted data to start this workflow
const workflowData = buildWorkflowApiPayload(workflow);
const newWorkflow = await this.createWorkflow(workflowData);
console.log('Workflow started successfully:', newWorkflow);
// Immediately navigate to the workflow detail view
// Get the first step from the workflow definition
const firstStep = workflow.getStepDefinitions()?.[0]?.step || "initial";
const firstStep = workflow.stepDefinitions?.[0]?.step || "initial";
this.$router.push({
name: 'workflow-detail',
params: {
@ -258,7 +258,7 @@ export default {
// Use the workflow's current step if available, otherwise use the first step
const currentStep = workflow.current_step ||
workflow.payload?.current_step ||
workflow.getStepDefinitions?.()?.[0]?.step ||
getWorkflow(workflow.name)?.stepDefinitions?.[0]?.step ||
"initial";
this.$router.push({