feat: add packer
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
useHead({ title: 'Packer', htmlAttrs: { lang: 'de' } })
|
||||
|
||||
const { settings, changed: settingsChanged, reset: resetSettings } = usePackerSettings()
|
||||
const { state, checked, changed: sessionChanged, toggle, add, remove, setWeight, reset: resetSession } = usePackerSession()
|
||||
const { categories, activities, stays, weathers, weatherExtras, hasSeasons, error, status } = usePackerList(settings, state)
|
||||
|
||||
// Back to a fresh list: default settings, nothing ticked, added or deleted
|
||||
const changed = computed(() => settingsChanged.value || sessionChanged.value)
|
||||
function reset() {
|
||||
resetSettings()
|
||||
resetSession()
|
||||
}
|
||||
|
||||
const shown = computed(() => categories.value.flatMap(category => category.entries))
|
||||
const packed = computed(() => shown.value.filter(entry => checked.value.has(entry.id)).length)
|
||||
|
||||
// Weight of what is packed and of the whole list, from the items with a weight; shown once the list has any
|
||||
const weighed = computed(() => shown.value.filter(entry => entry.weightG))
|
||||
const sumWeight = (entries: PackerEntry[]) => entries.reduce((sum, entry) => sum + entry.weightG! * entry.count, 0)
|
||||
const weight = computed(() => sumWeight(weighed.value))
|
||||
const packedWeight = computed(() => sumWeight(weighed.value.filter(entry => checked.value.has(entry.id))))
|
||||
const unweighed = computed(() => shown.value.length - weighed.value.length)
|
||||
|
||||
// The trip in one line, printed above the list instead of the settings
|
||||
const summary = computed(() => [
|
||||
settings.days === 1 ? '1 Tag' : `${settings.days} Tage`,
|
||||
settings.people === 1 ? '1 Person' : `${settings.people} Personen`,
|
||||
...(hasSeasons.value ? [settings.season] : []),
|
||||
...(weathers.value.includes(settings.weather) ? [settings.weather] : []),
|
||||
...weatherExtras.value.filter(weather => settings.weatherExtras.includes(weather)),
|
||||
...(settings.days > 1 && stays.value.includes(settings.stay) ? [settings.stay] : []),
|
||||
...activities.value.filter(activity => settings.activities.includes(activity)),
|
||||
...(weighed.value.length ? [`${unweighed.value ? '≥ ' : ''}${packerWeight(weight.value)}`] : [])
|
||||
].join(' · '))
|
||||
|
||||
// The PDF is named after the trip, e.g. "packer_3_tage"
|
||||
const { savePdf } = usePdfExport(() => `packer_${settings.days}_${settings.days === 1 ? 'tag' : 'tage'}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMain>
|
||||
<UContainer class="max-w-3xl py-8 print:max-w-none print:px-[12mm] print:pt-[15mm] print:pb-0">
|
||||
<div class="mb-6 flex items-center gap-3">
|
||||
<div class="me-auto">
|
||||
<h1 class="text-2xl font-semibold text-highlighted">
|
||||
Packer
|
||||
</h1>
|
||||
<p class="mt-1 hidden text-sm text-muted print:block">
|
||||
{{ summary }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="changed"
|
||||
label="Zurücksetzen"
|
||||
icon="i-lucide-rotate-ccw"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
title="Einstellungen, Häkchen, hinzugefügte und entfernte Sachen zurücksetzen"
|
||||
class="print:hidden"
|
||||
@click="reset"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PackerSettingsBar
|
||||
v-model="settings"
|
||||
:activities="activities"
|
||||
:stays="stays"
|
||||
:weathers="weathers"
|
||||
:weather-extras="weatherExtras"
|
||||
:has-seasons="hasSeasons"
|
||||
class="mb-8 print:hidden"
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
v-if="error"
|
||||
color="error"
|
||||
variant="subtle"
|
||||
icon="i-lucide-triangle-alert"
|
||||
title="Die Packliste konnte nicht geladen werden"
|
||||
:description="errorMessage(error)"
|
||||
class="mb-6"
|
||||
/>
|
||||
|
||||
<div v-if="shown.length" class="mb-6 print:hidden">
|
||||
<UProgress :model-value="packed" :max="shown.length" />
|
||||
<div class="mt-2 flex justify-between gap-4 text-sm text-muted tabular-nums">
|
||||
<span>{{ packed }} / {{ shown.length }} gepackt</span>
|
||||
<!-- "≥": some items have no weight yet -->
|
||||
<span v-if="weighed.length" :title="unweighed ? `${unweighed} ${unweighed === 1 ? 'Sache' : 'Sachen'} ohne Gewicht` : undefined">
|
||||
{{ packerWeight(packedWeight) }} / {{ unweighed ? '≥ ' : '' }}{{ packerWeight(weight) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns-1 gap-8 sm:columns-2 print:columns-2">
|
||||
<PackerCategory
|
||||
v-for="category in categories"
|
||||
:key="category.name"
|
||||
:category="category"
|
||||
:checked="checked"
|
||||
@toggle="toggle"
|
||||
@add="(name, weightG) => add(category.name, name, weightG)"
|
||||
@remove="remove"
|
||||
@weight="setWeight"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="!categories.length && !error && status !== 'pending'" class="py-16 text-center text-muted">
|
||||
Nichts zu packen.
|
||||
</p>
|
||||
|
||||
<SavePdfButton v-if="shown.length" @click="savePdf" />
|
||||
</UContainer>
|
||||
</UMain>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* As on the CV: top and bottom of every printed page. The sides and the extra space above the first page come from the
|
||||
container's print padding, which also works in browsers that ignore @page margins */
|
||||
@page {
|
||||
margin: 15mm 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user