Feat subitem create button (#691)

Co-authored-by: greg1904 <github@koppenatsch.de>
This commit is contained in:
greg1904
2025-05-11 21:46:53 +02:00
committed by GitHub
parent 469a7df448
commit e4a45ddb59
5 changed files with 100 additions and 7 deletions

View File

@@ -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...)
}

View File

@@ -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,

View File

@@ -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",

View File

@@ -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",

View File

@@ -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>