148 lines
5.6 KiB
Vue
148 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { LockKeyhole, Pencil, Plus, Trash2 } from 'lucide-vue-next';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import type {
|
|
MetadataEditorTarget,
|
|
MetadataEntry,
|
|
MetadataKind,
|
|
} from '@/types/project-metadata';
|
|
|
|
defineProps<{ kind: MetadataKind; entries: MetadataEntry[] }>();
|
|
defineEmits<{ edit: [target: MetadataEditorTarget] }>();
|
|
const { t } = useLocalization();
|
|
</script>
|
|
|
|
<template>
|
|
<section
|
|
class="bg-card grid min-w-0 gap-4 overflow-hidden rounded-xl border p-4 sm:p-6"
|
|
:aria-labelledby="`metadata-${kind}`"
|
|
>
|
|
<header class="flex flex-wrap items-center justify-between gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<h3 :id="`metadata-${kind}`" class="font-semibold">
|
|
{{
|
|
t(
|
|
kind === 'type'
|
|
? 'metadata.types'
|
|
: 'metadata.priorities',
|
|
)
|
|
}}
|
|
</h3>
|
|
<Badge variant="secondary">{{ entries.length }}</Badge>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
class="w-full sm:w-auto"
|
|
@click="$emit('edit', { kind, action: 'create' })"
|
|
>
|
|
<Plus class="size-4" aria-hidden="true" />{{
|
|
t(
|
|
kind === 'type'
|
|
? 'metadata.addType'
|
|
: 'metadata.addPriority',
|
|
)
|
|
}}
|
|
</Button>
|
|
</header>
|
|
<p class="text-muted-foreground text-sm">
|
|
{{
|
|
t(
|
|
kind === 'type'
|
|
? 'metadata.typesDescription'
|
|
: 'metadata.prioritiesDescription',
|
|
)
|
|
}}
|
|
</p>
|
|
<ol
|
|
class="divide-y"
|
|
:aria-label="
|
|
t(kind === 'type' ? 'metadata.types' : 'metadata.priorities')
|
|
"
|
|
>
|
|
<li
|
|
v-for="entry in entries"
|
|
:key="entry.id"
|
|
class="flex min-w-0 flex-wrap items-center gap-3 py-4"
|
|
:data-metadata-entry="`${kind}-${entry.id}`"
|
|
>
|
|
<span
|
|
class="bg-accent text-accent-foreground flex size-9 shrink-0 items-center justify-center rounded-lg text-sm font-semibold"
|
|
:aria-label="
|
|
t('workflow.positionValue', {
|
|
position: entry.position,
|
|
})
|
|
"
|
|
>{{ entry.position }}</span
|
|
>
|
|
<div class="grid min-w-0 flex-1 basis-28 gap-1">
|
|
<p class="break-words font-medium">{{ entry.name }}</p>
|
|
<div
|
|
v-if="kind === 'type'"
|
|
class="text-caption text-muted-foreground flex flex-wrap items-center gap-2"
|
|
>
|
|
<Badge variant="outline">{{
|
|
t(
|
|
entry.is_parent
|
|
? 'metadata.parentType'
|
|
: 'metadata.standardType',
|
|
)
|
|
}}</Badge>
|
|
<span>{{
|
|
t('metadata.assignedTickets', {
|
|
count: entry.tickets_count ?? 0,
|
|
})
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex shrink-0 gap-1">
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
:aria-label="
|
|
t('metadata.editEntry', { name: entry.name })
|
|
"
|
|
@click="$emit('edit', { kind, action: 'edit', entry })"
|
|
><Pencil class="size-4" aria-hidden="true"
|
|
/></Button>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
class="text-destructive"
|
|
:disabled="entries.length <= 1"
|
|
:aria-label="
|
|
t('metadata.deleteEntry', { name: entry.name })
|
|
"
|
|
:aria-describedby="
|
|
entries.length <= 1
|
|
? `metadata-${kind}-protection`
|
|
: undefined
|
|
"
|
|
@click="
|
|
$emit('edit', { kind, action: 'delete', entry })
|
|
"
|
|
><Trash2 class="size-4" aria-hidden="true"
|
|
/></Button>
|
|
</div>
|
|
</li>
|
|
</ol>
|
|
<p
|
|
v-if="entries.length <= 1"
|
|
:id="`metadata-${kind}-protection`"
|
|
class="text-caption text-muted-foreground flex items-start gap-2"
|
|
>
|
|
<LockKeyhole class="size-4 shrink-0" aria-hidden="true" />{{
|
|
t(
|
|
kind === 'type'
|
|
? 'metadata.lastType'
|
|
: 'metadata.lastPriority',
|
|
)
|
|
}}
|
|
</p>
|
|
</section>
|
|
</template>
|