Files
website/layers/packer/app/components/PackerCategory.vue
T
2026-10-03 23:11:51 +02:00

141 lines
4.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
const props = defineProps<{ category: PackerCategory, checked: Set<number> }>()
const emit = defineEmits<{
toggle: [id: number, value: boolean]
add: [name: string, weightG: number | null]
remove: [id: number]
weight: [id: number, weightG: number | null]
}>()
/** Grams typed into a field; empty or not a positive number is no weight */
function grams(text: string | number | null | undefined): number | null {
const value = Number(String(text ?? '').replace(',', '.'))
return value > 0 ? Math.round(value) : null
}
const newItem = ref('')
const newWeight = ref('')
function add() {
const name = newItem.value.trim()
if (!name) return
emit('add', name, grams(newWeight.value))
newItem.value = ''
newWeight.value = ''
}
// Weight of one piece, edited in place; an empty field goes back to the table's weight
const editing = ref<number | null>(null)
const draft = ref('')
function startEdit(entry: PackerEntry) {
editing.value = entry.id
draft.value = entry.weightG ? String(entry.weightG) : ''
}
function commit() {
if (editing.value === null) return
emit('weight', editing.value, grams(draft.value))
editing.value = null
}
</script>
<template>
<section class="mb-8 break-inside-avoid print:mb-5">
<h2 class="mb-2 text-sm font-semibold text-highlighted">
{{ props.category.name }}
</h2>
<ul class="flex flex-col gap-1.5">
<li v-for="entry in props.category.entries" :key="entry.id" class="group flex min-h-6 items-center gap-2">
<!-- print-color-adjust: browsers leave out the ticked box's fill when printing -->
<UCheckbox
:model-value="props.checked.has(entry.id)"
:ui="{ base: 'print:[print-color-adjust:exact]', label: props.checked.has(entry.id) ? 'text-muted line-through' : '' }"
@update:model-value="emit('toggle', entry.id, !!$event)"
>
<template #label>
<span v-if="entry.count > 1" class="tabular-nums">{{ entry.count }} × </span>{{ entry.name }}
</template>
</UCheckbox>
<UBadge v-if="entry.added" label="neu" color="neutral" variant="outline" size="sm" />
<UInput
v-if="editing === entry.id"
v-model="draft"
inputmode="numeric"
size="xs"
autofocus
:placeholder="entry.count > 1 ? 'g pro Stück' : 'g'"
:aria-label="`Gewicht von ${entry.name} in Gramm${entry.count > 1 ? ' pro Stück' : ''}`"
class="ms-auto w-24"
:ui="{ trailing: 'pe-2' }"
@keydown.enter="commit"
@keydown.esc="editing = null"
@blur="commit"
>
<template #trailing>
<span class="text-xs text-muted">g</span>
</template>
</UInput>
<!-- Click to change the weight for this session; changed weights are in the primary color -->
<button
v-else
type="button"
class="ms-auto rounded text-xs tabular-nums hover:text-highlighted focus-visible:outline-2 focus-visible:outline-primary"
:class="[
entry.weightEdited ? 'text-primary' : 'text-muted',
{ 'sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100 print:hidden': !entry.weightG }
]"
:title="entry.weightEdited ? 'Gewicht geändert (nur in dieser Sitzung)' : 'Gewicht ändern'"
:aria-label="`Gewicht von ${entry.name} ändern`"
@click="startEdit(entry)"
>
{{ entry.weightG ? packerWeight(entry.weightG * entry.count) : '+ g' }}
</button>
<!-- Always visible on touch screens, on hover with a mouse -->
<UButton
icon="i-lucide-x"
color="neutral"
variant="link"
size="xs"
:aria-label="`${entry.name} entfernen`"
class="sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100 print:hidden"
@click="emit('remove', entry.id)"
/>
</li>
</ul>
<form class="mt-2 flex gap-2 print:hidden" @submit.prevent="add">
<!-- The plus lines up with the checkboxes, the text with the item names -->
<UInput
v-model="newItem"
icon="i-lucide-plus"
placeholder="Hinzufügen"
variant="ghost"
size="sm"
:aria-label="`Zu ${props.category.name} hinzufügen`"
class="min-w-0 flex-1"
:ui="{ leading: 'ps-0', base: 'ps-6' }"
/>
<!-- Only once a name is typed, to keep the list calm -->
<UInput
v-if="newItem"
v-model="newWeight"
inputmode="numeric"
placeholder="Gewicht"
variant="ghost"
size="sm"
:aria-label="`Gewicht in Gramm`"
class="w-24"
:ui="{ trailing: 'pe-2' }"
>
<template #trailing>
<span class="text-xs text-muted">g</span>
</template>
</UInput>
<!-- Enter in either field adds the item -->
<button type="submit" class="hidden" />
</form>
</section>
</template>