mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 13:23:14 +01:00
Feat subitem create button (#691)
Co-authored-by: greg1904 <github@koppenatsch.de>
This commit is contained in:
@@ -579,6 +579,10 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCr
|
||||
SetLocationID(data.LocationID).
|
||||
SetAssetID(int(data.AssetID))
|
||||
|
||||
if data.ParentID != uuid.Nil {
|
||||
q.SetParentID(data.ParentID)
|
||||
}
|
||||
|
||||
if len(data.LabelIDs) > 0 {
|
||||
q.AddLabelIDs(data.LabelIDs...)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
<BaseModal dialog-id="create-item" :title="$t('components.item.create_modal.title')">
|
||||
<form class="flex flex-col gap-2" @submit.prevent="create()">
|
||||
<LocationSelector v-model="form.location" />
|
||||
<ItemSelector
|
||||
v-if="subItemCreate"
|
||||
v-model="parent"
|
||||
v-model:search="query"
|
||||
:label="$t('components.item.create_modal.parent_item')"
|
||||
:items="results"
|
||||
item-text="name"
|
||||
no-results-text="Type to search..."
|
||||
/>
|
||||
<FormTextField
|
||||
ref="nameInput"
|
||||
v-model="form.name"
|
||||
@@ -139,6 +148,7 @@
|
||||
import { AttachmentTypes } from "~~/lib/api/types/non-generated";
|
||||
import { useDialog, useDialogHotkey } from "~/components/ui/dialog-provider";
|
||||
import LabelSelector from "~/components/Label/Selector.vue";
|
||||
import ItemSelector from "~/components/Item/Selector.vue";
|
||||
|
||||
interface PhotoPreview {
|
||||
photoName: string;
|
||||
@@ -160,6 +170,12 @@
|
||||
const labels = computed(() => labelStore.labels);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const parent = ref();
|
||||
const { query, results } = useItemSearch(api, { immediate: false });
|
||||
const subItemCreateParam = useRouteQuery("subItemCreate", "n");
|
||||
const subItemCreate = ref();
|
||||
|
||||
const labelId = computed(() => {
|
||||
if (route.fullPath.includes("/label/")) {
|
||||
@@ -175,12 +191,20 @@
|
||||
return null;
|
||||
});
|
||||
|
||||
const itemId = computed(() => {
|
||||
if (route.fullPath.includes("/item/")) {
|
||||
return route.params.id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const nameInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const loading = ref(false);
|
||||
const focused = ref(false);
|
||||
const form = reactive({
|
||||
location: locations.value && locations.value.length > 0 ? locations.value[0] : ({} as LocationOut),
|
||||
parentId: null,
|
||||
name: "",
|
||||
quantity: 1,
|
||||
description: "",
|
||||
@@ -189,6 +213,18 @@
|
||||
photos: [] as PhotoPreview[],
|
||||
});
|
||||
|
||||
watch(
|
||||
parent,
|
||||
newParent => {
|
||||
if (newParent && newParent.id && subItemCreate.value) {
|
||||
form.parentId = newParent.id;
|
||||
} else {
|
||||
form.parentId = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const { shift } = useMagicKeys();
|
||||
|
||||
function deleteImage(index: number) {
|
||||
@@ -223,10 +259,43 @@
|
||||
|
||||
watch(
|
||||
() => activeDialog.value,
|
||||
active => {
|
||||
async active => {
|
||||
if (active === "create-item") {
|
||||
if (locationId.value) {
|
||||
const found = locations.value.find(l => l.id === locationId.value);
|
||||
// needed since URL will be cleared in the next step => ParentId Selection should stay though
|
||||
subItemCreate.value = subItemCreateParam.value === "y";
|
||||
let parentItemLocationId = null;
|
||||
|
||||
if (subItemCreate.value && itemId.value) {
|
||||
const itemIdRead = typeof itemId.value === "string" ? (itemId.value as string) : itemId.value[0];
|
||||
const { data, error } = await api.items.get(itemIdRead);
|
||||
if (error || !data) {
|
||||
toast.error("Failed to load parent item - please select manually");
|
||||
console.error("Parent item fetch error:", error);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
parent.value = data;
|
||||
}
|
||||
|
||||
if (data.location) {
|
||||
const { location } = data;
|
||||
parentItemLocationId = location.id;
|
||||
}
|
||||
|
||||
// clear URL Parameter (subItemCreate) since intention was communicated and received
|
||||
const currentQuery = { ...route.query };
|
||||
delete currentQuery.subItemCreate;
|
||||
await router.push({ query: currentQuery });
|
||||
} else {
|
||||
// since Input is hidden in this case, make sure no accidental parent information is sent out
|
||||
parent.value = {};
|
||||
form.parentId = null;
|
||||
}
|
||||
|
||||
const locId = locationId.value ? locationId.value : parentItemLocationId;
|
||||
|
||||
if (locId) {
|
||||
const found = locations.value.find(l => l.id === locId);
|
||||
if (found) {
|
||||
form.location = found;
|
||||
}
|
||||
@@ -254,7 +323,7 @@
|
||||
if (shift.value) close = false;
|
||||
|
||||
const out: ItemCreate = {
|
||||
parentId: null,
|
||||
parentId: form.parentId,
|
||||
name: form.name,
|
||||
quantity: form.quantity,
|
||||
description: form.description,
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"item_name": "Gegenstandsname",
|
||||
"item_photo": "Artikel Bild",
|
||||
"item_quantity": "Artikel Menge",
|
||||
"parent_item" :"Übergeordneter Gegenstand",
|
||||
"title": "Gegenstand erstellen",
|
||||
"upload_photos": "Upload Bilder"
|
||||
},
|
||||
@@ -125,6 +126,7 @@
|
||||
"create": "Erstellen",
|
||||
"create_and_add": "Erstellen und weiteren hinzufügen",
|
||||
"created": "Erstellt",
|
||||
"create_subitem": "Sub-Gegenstand erstellen",
|
||||
"delete": "Löschen",
|
||||
"details": "Details",
|
||||
"duplicate": "Duplizieren",
|
||||
@@ -214,7 +216,7 @@
|
||||
"options": "Optionen",
|
||||
"order_by": "Sortieren nach",
|
||||
"pages": "Seite { page } von { totalPages }",
|
||||
"parent_item": "Übergeordnetes Element",
|
||||
"parent_item": "Übergeordneter Gegenstand",
|
||||
"photo": "Foto",
|
||||
"photos": "Fotos",
|
||||
"prev_page": "Vorherige Seite",
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"item_name": "Item Name",
|
||||
"item_photo": "Item Photo 📷",
|
||||
"item_quantity": "Item Quantity",
|
||||
"parent_item" :"Parent Item",
|
||||
"title": "Create Item",
|
||||
"upload_photos": "Upload Photos"
|
||||
},
|
||||
@@ -125,6 +126,7 @@
|
||||
"create": "Create",
|
||||
"create_and_add": "Create and Add Another",
|
||||
"created": "Created",
|
||||
"create_subitem": "Create Subitem",
|
||||
"delete": "Delete",
|
||||
"details": "Details",
|
||||
"duplicate": "Duplicate",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const api = useUserApi();
|
||||
|
||||
const itemId = computed<string>(() => route.params.id as string);
|
||||
@@ -509,6 +510,17 @@
|
||||
toast.success("Item deleted");
|
||||
navigateTo("/home");
|
||||
}
|
||||
|
||||
async function createSubitem() {
|
||||
// setting URL Parameter that is read and immidiately removed in the Item-CreateModal
|
||||
await router.push({
|
||||
query: {
|
||||
subItemCreate: "y",
|
||||
},
|
||||
});
|
||||
|
||||
openDialog("create-item");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -582,11 +594,15 @@
|
||||
type="asset"
|
||||
/>
|
||||
<LabelMaker v-else :id="item.id" type="item" />
|
||||
<Button class="w-9 md:w-auto" @click="duplicateItem">
|
||||
<Button class="w-9 md:w-auto" :aria-label="$t('global.create_subitem')" @click="createSubitem">
|
||||
<MdiPlus />
|
||||
<span class="hidden md:inline">{{ $t("global.create_subitem") }}</span>
|
||||
</Button>
|
||||
<Button class="w-9 md:w-auto" :aria-label="$t('global.duplicate')" @click="duplicateItem">
|
||||
<MdiContentCopy />
|
||||
<span class="hidden md:inline">{{ $t("global.duplicate") }}</span>
|
||||
</Button>
|
||||
<Button variant="destructive" class="w-9 md:w-auto" @click="deleteItem">
|
||||
<Button variant="destructive" class="w-9 md:w-auto" :aria-label="$t('global.delete')" @click="deleteItem">
|
||||
<MdiDelete />
|
||||
<span class="hidden md:inline">{{ $t("global.delete") }}</span>
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user