mitlist/fe/src/pages/ListDetailPage.vue
mohamad 5a2e80eeee feat: Enhance WebSocket connection handling and introduce skeleton components
This commit includes several improvements and new features:

- Updated the WebSocket connection logic in `websocket.py` to include connection status messages and periodic pings for maintaining the connection.
- Introduced new skeleton components (`Skeleton.vue`, `SkeletonDashboard.vue`, `SkeletonList.vue`) for improved loading states in the UI, enhancing user experience during data fetching.
- Refactored the Vite configuration to support advanced code splitting and caching strategies, optimizing the build process.
- Enhanced ESLint configuration for better compatibility with project structure.

These changes aim to improve real-time communication, user interface responsiveness, and overall application performance.
2025-06-28 23:02:23 +02:00

144 lines
4.1 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.

<template>
<main class="p-4 max-w-screen-md mx-auto space-y-6">
<!-- Loading -->
<Spinner v-if="isLoading" :label="$t('listDetailPage.loading.list')" />
<!-- Error -->
<Alert v-else-if="error" type="error" :message="error" />
<!-- Main Content -->
<template v-else-if="list">
<div class="flex items-center justify-between gap-4">
<Heading :level="1">{{ list.name }}</Heading>
<Switch v-model="supermarktMode" />
</div>
<ProgressBar v-if="supermarktMode" :value="completion" />
<ItemsList :list="list" :items="itemsWithUI" :is-online="isOnline" :supermarkt-mode="supermarktMode"
:category-options="categoryOptions" :new-item="newItem" :categories="categories" @add-item="handleAddItem"
@checkbox-change="handleCheckboxChange" @delete-item="handleDeleteItem"
@update:newItemName="val => (newItem.name = val)"
@update:newItemCategoryId="val => (newItem.category_id = val)" />
</template>
</main>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useNetwork } from '@vueuse/core'
import { storeToRefs } from 'pinia'
// Stores
import { useListsStore } from '@/stores/listsStore'
import { useAuthStore } from '@/stores/auth'
import { useCategoryStore } from '@/stores/categoryStore'
// UI Primitives
import { Heading, Spinner, Alert, ProgressBar, Switch } from '@/components/ui'
// Components
import ItemsList from '@/components/list-detail/ItemsList.vue'
const { t } = useI18n()
const { isOnline } = useNetwork()
const route = useRoute()
// Pinia stores
const listsStore = useListsStore()
const authStore = useAuthStore()
const categoryStore = useCategoryStore()
// Store refs
const { currentList: list, isLoading, error } = storeToRefs(listsStore)
const { categories } = storeToRefs(categoryStore)
// Local state
const supermarktMode = ref(false)
const newItem = reactive<{ name: string; category_id: number | null }>({ name: '', category_id: null })
// Derived data
const completion = computed(() => {
if (!list.value?.items.length) return 0
const done = list.value.items.filter(i => i.is_complete).length
return (done / list.value.items.length) * 100
})
const itemsWithUI = computed(() => {
return (list.value?.items || []).map(i => ({
...i,
updating: false,
deleting: false,
priceInput: i.price ?? null,
swiped: false,
}))
})
const categoryOptions = computed(() => [
{ label: t('listDetailPage.noCategory'), value: null },
...categories.value.map((c: { id: number; name: string }) => ({ label: c.name, value: c.id })),
])
// Lifecycle
onMounted(() => {
const listId = String(route.params.id)
listsStore.fetchListDetails(listId)
categoryStore.fetchCategories()
let stop: (() => void) | null = null
stop = watch(
() => authStore.accessToken,
token => {
if (token) {
listsStore.connectWebSocket(Number(listId), token)
if (stop) stop()
}
},
{ immediate: true },
)
})
onUnmounted(() => {
listsStore.disconnectWebSocket()
})
// Stub handlers (to be implemented)
function handleAddItem() {
if (!list.value) return
const nameTrimmed = newItem.name.trim()
if (!nameTrimmed) return
listsStore
.addItem(list.value.id, {
name: nameTrimmed,
category_id: newItem.category_id ?? undefined,
})
.then(() => {
newItem.name = ''
newItem.category_id = null
})
.catch(err => {
console.error('Add item failed', err)
})
}
function handleCheckboxChange(item: any, checked: boolean) {
if (!list.value) return
listsStore
.updateItem(list.value.id, item.id, { is_complete: checked, version: item.version })
.catch(err => console.error('Update item failed', err))
}
function handleDeleteItem(item: any) {
if (!list.value) return
listsStore
.deleteItem(list.value.id, item.id, item.version)
.catch(err => console.error('Delete item failed', err))
}
</script>
<style scoped>
/* No extra styles Tailwind utility classes cover layout */
</style>