mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-27 15:41:42 +01:00
Add frontend support for label parent/child relationships
Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,11 @@
|
||||
default: "md",
|
||||
},
|
||||
});
|
||||
|
||||
// Type guard to check if label is LabelOut (has parent/children)
|
||||
const isLabelOut = (label: LabelOut | LabelSummary): label is LabelOut => {
|
||||
return 'parent' in label || 'children' in label;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,6 +47,10 @@
|
||||
<MdiArrowUp class="hidden group-hover/label-chip:block" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="isLabelOut(label) && label.parent">
|
||||
<span class="opacity-70">{{ label.parent.name }}</span>
|
||||
<span class="opacity-50">/</span>
|
||||
</template>
|
||||
{{ label.name }}
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
:max-length="1000"
|
||||
/>
|
||||
<ColorSelector v-model="form.color" :label="$t('components.label.create_modal.label_color')" :show-hex="true" />
|
||||
<LabelParentSelector v-model="form.parentId" :labels="labels" />
|
||||
<div class="mt-4 flex flex-row-reverse">
|
||||
<ButtonGroup>
|
||||
<Button :disabled="loading" type="submit">{{ $t("global.create") }}</Button>
|
||||
@@ -37,6 +38,8 @@
|
||||
import FormTextField from "~/components/Form/TextField.vue";
|
||||
import FormTextArea from "~/components/Form/TextArea.vue";
|
||||
import { Button, ButtonGroup } from "~/components/ui/button";
|
||||
import LabelParentSelector from "@/components/Label/ParentSelector.vue";
|
||||
import type { LabelOut } from "~/lib/api/types/data-contracts";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -49,13 +52,25 @@
|
||||
const form = reactive({
|
||||
name: "",
|
||||
description: "",
|
||||
color: "", // Future!
|
||||
color: "",
|
||||
parentId: null as string | null,
|
||||
});
|
||||
|
||||
const labels = ref<LabelOut[]>([]);
|
||||
|
||||
// Load labels for parent selection
|
||||
onMounted(async () => {
|
||||
const { data } = await api.labels.getAll();
|
||||
if (data) {
|
||||
labels.value = data;
|
||||
}
|
||||
});
|
||||
|
||||
function reset() {
|
||||
form.name = "";
|
||||
form.description = "";
|
||||
form.color = "";
|
||||
form.parentId = null;
|
||||
focused.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
40
frontend/components/Label/ParentSelector.vue
Normal file
40
frontend/components/Label/ParentSelector.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-1">
|
||||
<Label :for="id">
|
||||
{{ $t('components.label.parent_selector.label') }}
|
||||
</Label>
|
||||
<Select v-model="modelValue">
|
||||
<SelectTrigger :id="id">
|
||||
<SelectValue :placeholder="$t('components.label.parent_selector.placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">{{ $t('components.label.parent_selector.no_parent') }}</SelectItem>
|
||||
<SelectItem v-for="label in props.labels" :key="label.id" :value="label.id">
|
||||
{{ label.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import type { LabelOut } from "~/lib/api/types/data-contracts";
|
||||
|
||||
const id = useId();
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String as () => string | null,
|
||||
default: null,
|
||||
},
|
||||
labels: {
|
||||
type: Array as () => LabelOut[],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emit);
|
||||
</script>
|
||||
@@ -21,6 +21,8 @@
|
||||
import PageQRCode from "~/components/global/PageQRCode.vue";
|
||||
import Markdown from "~/components/global/Markdown.vue";
|
||||
import ItemViewSelectable from "~/components/Item/View/Selectable.vue";
|
||||
import LabelParentSelector from "@/components/Label/ParentSelector.vue";
|
||||
import LabelChip from "@/components/Label/Chip.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["auth"],
|
||||
@@ -69,12 +71,19 @@
|
||||
name: "",
|
||||
description: "",
|
||||
color: "",
|
||||
parentId: null as string | null,
|
||||
});
|
||||
|
||||
const { data: allLabels } = useAsyncData("all-labels", async () => {
|
||||
const { data } = await api.labels.getAll();
|
||||
return data || [];
|
||||
});
|
||||
|
||||
function openUpdate() {
|
||||
updateData.name = label.value?.name || "";
|
||||
updateData.description = label.value?.description || "";
|
||||
updateData.color = "";
|
||||
updateData.parentId = label.value?.parent?.id || null;
|
||||
openDialog(DialogID.UpdateLabel);
|
||||
}
|
||||
|
||||
@@ -151,6 +160,7 @@
|
||||
:show-hex="true"
|
||||
:starting-color="label.color"
|
||||
/>
|
||||
<LabelParentSelector v-if="allLabels" v-model="updateData.parentId" :labels="allLabels.filter(l => l.id !== labelId)" />
|
||||
<DialogFooter>
|
||||
<Button type="submit" :loading="updating"> {{ $t("global.update") }} </Button>
|
||||
</DialogFooter>
|
||||
@@ -204,6 +214,25 @@
|
||||
</header>
|
||||
<Separator v-if="label && label.description" />
|
||||
<Markdown v-if="label && label.description" class="mt-3 text-base" :source="label.description" />
|
||||
|
||||
<!-- Display parent and children -->
|
||||
<div v-if="label && (label.parent || (label.children && label.children.length > 0))" class="mt-3">
|
||||
<Separator />
|
||||
<div class="mt-3">
|
||||
<div v-if="label.parent" class="mb-2">
|
||||
<span class="text-sm font-medium">{{ $t("labels.parent_label") }}:</span>
|
||||
<div class="mt-1">
|
||||
<LabelChip :label="label.parent" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="label.children && label.children.length > 0">
|
||||
<span class="text-sm font-medium">{{ $t("labels.child_labels") }}:</span>
|
||||
<div class="mt-1 flex flex-wrap gap-2">
|
||||
<LabelChip v-for="child in label.children" :key="child.id" :label="child" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<section v-if="label && items">
|
||||
<ItemViewSelectable :items="items.items" @refresh="refreshItemList" />
|
||||
|
||||
Reference in New Issue
Block a user