From 1bd69c9c97924a990f672af5339e2236ca4b7e24 Mon Sep 17 00:00:00 2001
From: webfussel <fiona@webfussel.de>
Date: Sat, 10 May 2025 18:36:42 +0200
Subject: [PATCH] add: subheader

Subheader for dynamic content
---
 app/assets/styles/button.css |   1 -
 app/assets/styles/header.css | 100 +++++++++++----------
 app/assets/styles/page.css   |   7 ++
 app/components/Pp/Header.vue |  37 ++++----
 app/pages/index.vue          |  50 ++++-------
 app/pages/other.vue          | 165 +++++++++++++++++++++++++++++++++++
 6 files changed, 259 insertions(+), 101 deletions(-)
 create mode 100644 app/pages/other.vue

diff --git a/app/assets/styles/button.css b/app/assets/styles/button.css
index be84428..11a6a95 100755
--- a/app/assets/styles/button.css
+++ b/app/assets/styles/button.css
@@ -102,7 +102,6 @@
 
         &:hover {
             scale: 1.2;
-            --background: var(--color-light);
         }
     }
 
diff --git a/app/assets/styles/header.css b/app/assets/styles/header.css
index 8db1384..74df67a 100755
--- a/app/assets/styles/header.css
+++ b/app/assets/styles/header.css
@@ -1,57 +1,61 @@
 .Header {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: var(--padding-default);
-    background: var(--color-main-darkest);
     position: sticky;
     top: 0;
     z-index: 100;
-    font-family: 'Roboto', sans-serif;
-    font-weight: bold;
+    background: var(--color-main-darkest);
 
-    & a {
-        text-decoration: none;
-        color: var(--color-lightest);
-    }
-
-    & .header-text {
-        font-size: 1.5em;
-    }
-
-    & input[type="checkbox"] {
-        display: none;
-    }
-
-    & input[type="checkbox"]:checked + nav {
-        translate: 0;
-    }
-
-    & nav,
-    & ul {
-        gap: 1em;
-    }
-
-    & nav {
-        position: fixed;
-        padding: var(--padding-default);
-        translate: 100% 0;
-        width: 100vw;
-        right: 0;
-        top: 0;
-        height: 100dvh;
-        transition: 150ms ease-in-out;
-        background: var(--color-lightest);
-        font-size: 2em;
-        align-items: end;
-        z-index: 100;
-    }
-
-    & ul {
-        width: 100%;
+    & header {
+        display: flex;
         align-items: center;
-        & li {
-            list-style: none;
+        justify-content: space-between;
+        padding: var(--padding-default);
+        padding-bottom: 0;
+        font-family: 'Roboto', sans-serif;
+        font-weight: bold;
+
+        & a {
+            text-decoration: none;
+            color: var(--color-lightest);
+        }
+
+        & .header-text {
+            font-size: 1.5em;
+        }
+
+        & input[type="checkbox"] {
+            display: none;
+        }
+
+        & input[type="checkbox"]:checked + nav {
+            translate: 0;
+        }
+
+        & nav,
+        & ul {
+            gap: 1em;
+        }
+
+        & nav {
+            position: fixed;
+            padding: var(--padding-default);
+            translate: 100% 0;
+            width: 100vw;
+            right: 0;
+            top: 0;
+            height: 100dvh;
+            transition: 150ms ease-in-out;
+            background: var(--color-lightest);
+            font-size: 2em;
+            align-items: end;
+            z-index: 100;
+        }
+
+        & ul {
+            width: 100%;
+            align-items: center;
+            & li {
+                list-style: none;
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/app/assets/styles/page.css b/app/assets/styles/page.css
index 8538a05..469d149 100644
--- a/app/assets/styles/page.css
+++ b/app/assets/styles/page.css
@@ -22,7 +22,14 @@
 }
 
 .search-bar {
+    z-index: 100;
     padding: 0 1rem 1rem 1rem;
+
+    & p {
+        font-family: 'Roboto Condensed', sans-serif;
+        font-weight: lighter;
+        color: var(--color-lightest);
+    }
 }
 
 .content {
diff --git a/app/components/Pp/Header.vue b/app/components/Pp/Header.vue
index d52d4a1..81c1ffd 100755
--- a/app/components/Pp/Header.vue
+++ b/app/components/Pp/Header.vue
@@ -1,22 +1,25 @@
 <template>
-  <header class="Header">
-    <NuxtLink class="header-text" to="/">
-      ProPapier
-    </NuxtLink>
-    <label for="burger_nav_toggle" v-if="available">
-      <Icon name="solar:hamburger-menu-broken" size="2em" />
-    </label>
-    <input type="checkbox" id="burger_nav_toggle" v-if="available" />
-    <nav class="flex-col" v-if="available">
-      <label for="burger_nav_toggle">
-        <Icon name="solar:close-circle-broken" />
+  <div class="Header">
+    <header>
+      <NuxtLink class="header-text" to="/">
+        ProPapier
+      </NuxtLink>
+      <label for="burger_nav_toggle" v-if="available">
+        <Icon name="solar:hamburger-menu-broken" size="2em" />
       </label>
-      <ul class="flex-col">
-        <li>Home</li>
-        <li>Übersicht</li>
-      </ul>
-    </nav>
-  </header>
+      <input type="checkbox" id="burger_nav_toggle" v-if="available" />
+      <nav class="flex-col" v-if="available">
+        <label for="burger_nav_toggle">
+          <Icon name="solar:close-circle-broken" />
+        </label>
+        <ul class="flex-col">
+          <li>Home</li>
+          <li>Übersicht</li>
+        </ul>
+      </nav>
+    </header>
+    <div id="subheader" />
+  </div>
 </template>
 
 <script setup lang="ts">
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 8b566c4..b2b0a12 100755
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -12,13 +12,11 @@
           :current-card-index="currentCardIndex"
           @update="updateCard()"
       />
-      <div class="search-bar">
-        <PpFormSearch
-          v-model="search"
-          label="Suche nach Klopapier!"
-          id="search_field"
-        />
-      </div>
+      <Teleport to="#subheader">
+        <div class="search-bar">
+          <p>Preise. Schnell. Unkompliziert.</p>
+        </div>
+      </Teleport>
       <section class="content flex-col">
         <aside class="filter-bar">
           <button v-for="(button, index) in filterButtons" @click="() => sort(index)" :class="{ 'active': button.active }">
@@ -38,15 +36,6 @@
         </div>
       </section>
       <PpToolbar>
-        <PpButton class="mini-button text-white transparent" @click="sort(currentSort)">
-          <Icon class="icon" name="uil:refresh" mode="svg" />
-          <span>Neu sortieren</span>
-          <span
-              class="dot"
-              :class="{ visible : isDirty}"
-              aria-hidden="true"
-          />
-        </PpButton>
         <PpButton class="mini-button text-white transparent" @click="openModal(true, -1)">
           <Icon class="icon" name="uil:plus" mode="svg" />
           <span>Hinzufügen</span>
@@ -63,15 +52,12 @@ import { PpPriceCardDialog, PpDeleteDialog, PpPriceCard } from '#components'
 
 const cards = useLocalStorage<Card[]>('cards', [])
 const currentSort = useLocalStorage<number>('sort', 0)
-const isDirty = ref(false)
 const currentCard = ref<Card>()
 const currentCardIndex = ref<number>(-1)
 const modal = useTemplateRef<typeof PpPriceCardDialog>('modal')
 const deleteModal = useTemplateRef<typeof PpDeleteDialog>('deleteModal')
 const priceCards = useTemplateRef<(typeof PpPriceCard)[]>('priceCard')
 
-const search = ref('')
-
 const createCard = (uuid : string) : Card => ({
   uuid,
   name: '',
@@ -84,12 +70,12 @@ const createCard = (uuid : string) : Card => ({
 
 const addCard = (card : Card) => {
   cards.value.unshift({ ...card })
-  isDirty.value = true
+  sort()
 }
 
 const removeCard = (index : number) => {
   cards.value.splice(index, 1)
-  isDirty.value = true
+  sort()
 }
 
 const updateCard = () => {
@@ -100,7 +86,7 @@ const updateCard = () => {
 
   const newCard = { ...currentCard.value! }
   cards.value.splice(currentCardIndex.value, 1, newCard)
-  isDirty.value = true
+  sort()
 }
 
 const openModal = (createNew : boolean, index : number) => {
@@ -149,23 +135,17 @@ const sortBy = (key : 'ppr' | 'pps' | 'ppl') => {
   })
 }
 
-const sort = (index : number) => {
+const sort = async (index : number = currentSort.value) => {
   currentSort.value = index
   filterButtons.value.forEach(button => { button.active = false })
   filterButtons.value[index]!.active = true
 
-  switch (index) {
-    case 0:
-      sortBy('ppr')
-      break
-    case 1:
-      sortBy('pps')
-      break
-    case 2:
-      sortBy('ppl')
-      break
-  }
+  await nextTick()
 
-  isDirty.value = false
+  switch (index) {
+    case 0: return sortBy('ppr')
+    case 1: return sortBy('pps')
+    case 2: return sortBy('ppl')
+  }
 }
 </script>
diff --git a/app/pages/other.vue b/app/pages/other.vue
new file mode 100644
index 0000000..bd60632
--- /dev/null
+++ b/app/pages/other.vue
@@ -0,0 +1,165 @@
+<template>
+  <div>
+    <ClientOnly>
+      <PpDeleteDialog
+          ref="deleteModal"
+          :current-card-index="currentCardIndex"
+          @delete="removeCard(currentCardIndex)"
+      />
+      <PpPriceCardDialog
+          ref="modal"
+          :current-card="currentCard"
+          :current-card-index="currentCardIndex"
+          @update="updateCard()"
+      />
+      <div class="search-bar">
+        <PpFormSearch
+            v-model="search"
+            label="Suche nach Klopapier!"
+            id="search_field"
+        />
+      </div>
+      <section class="content flex-col">
+        <aside class="filter-bar">
+          <button v-for="(button, index) in filterButtons" @click="() => sort(index)" :class="{ 'active': button.active }">
+            {{ button.label }}
+          </button>
+        </aside>
+        <div class="flex-col" role="list">
+          <PpPriceCard
+              ref="priceCard"
+              v-for="(card, index) in cards"
+              :key="card.uuid"
+              :deletable="cards.length > 1"
+              :card="card"
+              @update="openModal(false, index)"
+              @remove="openDeleteModal()"
+          />
+        </div>
+      </section>
+      <PpToolbar>
+        <PpButton class="mini-button text-white transparent" @click="sort(currentSort)">
+          <Icon class="icon" name="uil:refresh" mode="svg" />
+          <span>Neu sortieren</span>
+          <span
+              class="dot"
+              :class="{ visible : isDirty}"
+              aria-hidden="true"
+          />
+        </PpButton>
+        <PpButton class="mini-button text-white transparent" @click="openModal(true, -1)">
+          <Icon class="icon" name="uil:plus" mode="svg" />
+          <span>Hinzufügen</span>
+        </PpButton>
+      </PpToolbar>
+    </ClientOnly>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { Card } from '../../shared/Card'
+import type { Button } from '../../shared/ButtonGroup'
+import { PpPriceCardDialog, PpDeleteDialog, PpPriceCard } from '#components'
+
+const cards = useLocalStorage<Card[]>('cards', [])
+const currentSort = useLocalStorage<number>('sort', 0)
+const isDirty = ref(false)
+const currentCard = ref<Card>()
+const currentCardIndex = ref<number>(-1)
+const modal = useTemplateRef<typeof PpPriceCardDialog>('modal')
+const deleteModal = useTemplateRef<typeof PpDeleteDialog>('deleteModal')
+const priceCards = useTemplateRef<(typeof PpPriceCard)[]>('priceCard')
+
+const search = ref('')
+
+const createCard = (uuid : string) : Card => ({
+  uuid,
+  name: '',
+  price: '',
+  roles: '',
+  sheets: '',
+  layers: '',
+})
+
+
+const addCard = (card : Card) => {
+  cards.value.unshift({ ...card })
+  isDirty.value = true
+}
+
+const removeCard = (index : number) => {
+  cards.value.splice(index, 1)
+  isDirty.value = true
+}
+
+const updateCard = () => {
+  if (currentCardIndex.value === -1) {
+    addCard(currentCard.value!)
+    return
+  }
+
+  const newCard = { ...currentCard.value! }
+  cards.value.splice(currentCardIndex.value, 1, newCard)
+  isDirty.value = true
+}
+
+const openModal = (createNew : boolean, index : number) => {
+  if (createNew) {
+    currentCardIndex.value = -1
+    currentCard.value = createCard(randomUUID())
+    modal.value?.$el.showModal()
+    return
+  }
+
+  currentCardIndex.value = index
+  currentCard.value = { ...cards.value[index]! }
+
+  modal.value?.$el.showModal()
+  return
+}
+
+const openDeleteModal = () => {
+  deleteModal.value?.$el.showModal()
+}
+
+const filterButtons = ref<Button[]>([
+  {
+    label: 'Rollen',
+    icon: 'uil:toilet-paper',
+    active: currentSort.value === 0,
+  },
+  {
+    label: 'Blatt',
+    icon: 'uil:file-landscape',
+    active: currentSort.value === 1,
+  },
+  {
+    label: 'Lagen',
+    icon: 'uil:layer-group',
+    active: currentSort.value === 2,
+  },
+])
+
+const sortBy = (key : 'ppr' | 'pps' | 'ppl') => {
+  cards.value.sort((a, b) => {
+    const aCard = priceCards.value?.find(card => card.uuid === a.uuid) || null
+    const bCard = priceCards.value?.find(card => card.uuid === b.uuid) || null
+    if (!aCard || !bCard) return 0
+    return aCard[key] - bCard[key]
+  })
+}
+
+const sort = (index : number) => {
+  currentSort.value = index
+  filterButtons.value.forEach(button => { button.active = false })
+  filterButtons.value[index]!.active = true
+
+  isDirty.value = false
+
+  switch (index) {
+    case 0: return sortBy('ppr')
+    case 1: return sortBy('pps')
+    case 2: return sortBy('ppl')
+  }
+}
+</script>