All files / src/views HomeView.vue

84.74% Statements 50/59
67.56% Branches 25/37
72.22% Functions 13/18
82% Lines 41/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141                        10x 10x 10x 10x 10x   10x 10x 10x   10x   10x 10x 10x 10x 10x 10x                             10x         10x 10x 1x 1x 1x 1x   1x       10x 10x 10x 10x               11x   1x   1x   1x               1x 1x     1x 1x     1x 1x                     1x             6x 10x                           2x                          
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useListsStore } from '../stores/lists'
import ListSection from '../components/list/ListSection.vue'
import ListCard from '../components/list/ListCard.vue'
import FloatingActionButton from '../components/common/FloatingActionButton.vue'
import AddListModal from '../components/common/AddListModal.vue'
import { connectWebSocket, subscribe } from '../services/websocket'
import { getUserId } from '../services/userId'
import { useNotificationsStore } from '../stores/notifications'
 
const route = useRoute()
const router = useRouter()
const listsStore = useListsStore()
const notificationsStore = useNotificationsStore()
const showAddModal = ref(false)
 
const initialPresetId = ref<string | null>(null)
const initialPresetEmoji = ref<string | null>(null)
const initialPresetName = ref<string | null>(null)
 
let unsubscribe: (() => void) | null = null
 
onMounted(async () => {
  listsStore.fetchAll()
  const userId = getUserId()
  Eif (userId) {
    await connectWebSocket()
    unsubscribe = subscribe(`/topic/user/${userId}`, (msg: unknown) => {
      const body = msg as { type?: string; listId?: string; listName?: string; conflictCount?: number }
      if (body?.type === 'LIST_ADDED') {
        listsStore.fetchAll()
      } else if (body?.type === 'CONFLICT_DETECTED' && body.listId && body.listName) {
        notificationsStore.add({
          listId: body.listId,
          listName: body.listName,
          message: 'Konflikt automatisch gelöst',
        })
      }
    })
  }
})
 
onUnmounted(() => {
  unsubscribe?.()
})
 
// Open AddListModal pre-filled when coming from LibraryView via query params
watch(() => route.query, (q) => {
  if (q.presetId) {
    initialPresetId.value = q.presetId as string
    initialPresetEmoji.value = (q.presetEmoji as string) || null
    initialPresetName.value = (q.presetName as string) || null
    showAddModal.value = true
    // Clear query params without navigating away — keep userId in URL
    router.replace({ name: 'home', params: { userId: getUserId()! }, query: {} })
  }
}, { immediate: true })
 
const lists = computed(() => listsStore.lists)
const totalDone = computed(() => lists.value.reduce((a, l) => a + l.checkedCount, 0))
const totalRemaining = computed(() => lists.value.reduce((a, l) => a + (l.itemCount - l.checkedCount), 0))
const sharedCount = computed(() => lists.value.filter(l => l.participantCount > 1).length)
 
async function handleCreate(name: string, emoji: string, presetId: string | null) {
  await listsStore.create({ name, emoji, presetId })
}
</script>
 
<template>
  <div class="pt-16 pb-24 px-5 max-w-lg mx-auto">
    <!-- Greeting -->
    <div class="mt-4 mb-6 animate-fade-up">
      <p class="text-ctp-overlay1 text-sm">Willkommen zurück</p>
      <h2 class="text-2xl font-bold text-ctp-text mt-0.5">
        Deine Listen
        <span class="text-ctp-overlay0 font-normal text-base ml-1">
          ({{ lists.length }})
        </span>
      </h2>
    </div>
 
    <!-- Quick stats -->
    <div class="grid grid-cols-3 gap-3 mb-8 animate-fade-up" style="animation-delay: 60ms">
      <div class="bg-ctp-surface0/60 border border-ctp-surface1/40 rounded-2xl p-3 text-center">
        <p class="text-xl font-bold text-ctp-green tabular-nums">{{ totalDone }}</p>
        <p class="text-[10px] text-ctp-overlay0 mt-0.5 uppercase tracking-wider">Erledigt</p>
      </div>
      <div class="bg-ctp-surface0/60 border border-ctp-surface1/40 rounded-2xl p-3 text-center">
        <p class="text-xl font-bold text-ctp-teal tabular-nums">{{ totalRemaining }}</p>
        <p class="text-[10px] text-ctp-overlay0 mt-0.5 uppercase tracking-wider">Offen</p>
      </div>
      <div class="bg-ctp-surface0/60 border border-ctp-surface1/40 rounded-2xl p-3 text-center">
        <p class="text-xl font-bold text-ctp-sapphire tabular-nums">{{ sharedCount }}</p>
        <p class="text-[10px] text-ctp-overlay0 mt-0.5 uppercase tracking-wider">Geteilt</p>
      </div>
    </div>
 
    <!-- Loading skeletons -->
    <div v-if="listsStore.loading" class="space-y-3 animate-fade-up">
      <div v-for="n in 3" :key="n" class="h-24 bg-ctp-surface0 rounded-2xl skeleton" />
    </div>
 
    <!-- Error -->
    <div v-else-if="listsStore.error" class="text-center py-12 text-ctp-red text-sm animate-fade-up">
      {{ listsStore.error }}
    </div>
 
    <!-- Lists -->
    <template v-else>
      <ListSection title="Meine Listen" :count="lists.length" class="mb-8">
        <ListCard
          v-for="(list, i) in lists"
          :key="list.id"
          :list="list"
          :index="i"
        />
 
        <div v-if="lists.length === 0" class="text-center py-12 animate-fade-up">
          <p class="text-4xl mb-3">🛒</p>
          <p class="text-ctp-overlay0 text-sm">Noch keine Listen. Erstelle deine erste!</p>
        </div>
      </ListSection>
    </template>
 
    <!-- FAB -->
    <FloatingActionButton @click="showAddModal = true" />
 
    <!-- Add modal -->
    <AddListModal
      :open="showAddModal"
      :initial-preset-id="initialPresetId"
      :initial-preset-emoji="initialPresetEmoji"
      :initial-preset-name="initialPresetName"
      @close="showAddModal = false"
      @create="handleCreate"
    />
  </div>
</template>