add quick town selection and category selection from homescreen with localstorage!
This commit is contained in:
parent
73f5683fbc
commit
02e699aad5
24 changed files with 619 additions and 137 deletions
|
|
@ -1,18 +1,68 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import DirectoryGrid from '@/components/directory/DirectoryGrid.vue'
|
||||
import DirectoryFilter from '@/components/directory/DirectoryFilter.vue'
|
||||
import { mockDirectoryItems } from '@/data/directory'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useTheme } from '@/components/theme-provider'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { currentTown } = useTheme()
|
||||
|
||||
// Use the imported mock data
|
||||
const items = mockDirectoryItems
|
||||
|
||||
const category = ref('all')
|
||||
const search = ref('')
|
||||
const town = ref(currentTown.value)
|
||||
|
||||
// Watch for route changes to update filters
|
||||
watch(() => route.query, (newQuery) => {
|
||||
category.value = newQuery.category?.toString() || 'all'
|
||||
search.value = newQuery.search?.toString() || ''
|
||||
town.value = newQuery.town === undefined ? currentTown.value : (newQuery.town?.toString() || 'all')
|
||||
}, { immediate: true })
|
||||
|
||||
// Watch for filter changes and update URL
|
||||
watch([category, search, town], ([newCategory, newSearch, newTown]) => {
|
||||
// Don't update URL if it matches current query params
|
||||
if (
|
||||
newCategory === route.query.category &&
|
||||
newSearch === route.query.search &&
|
||||
newTown === route.query.town
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const query = {
|
||||
...(newCategory !== 'all' && { category: newCategory }),
|
||||
...(newSearch && { search: newSearch }),
|
||||
...(newTown !== currentTown.value && { town: newTown })
|
||||
}
|
||||
router.replace({ query })
|
||||
})
|
||||
|
||||
// Filter items based on category, search, and town
|
||||
const filteredItems = computed(() => {
|
||||
return items.filter(item => {
|
||||
const matchesCategory = category.value === 'all' || item.category === category.value
|
||||
const matchesSearch = !search.value ||
|
||||
item.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
item.description?.toLowerCase().includes(search.value.toLowerCase())
|
||||
const matchesTown = town.value === 'all' || item.town === town.value
|
||||
return matchesCategory && matchesSearch && matchesTown
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-2 md:py-8">
|
||||
<div class="space-y-4 sm:space-y-8">
|
||||
<!-- Directory Header - Hidden on mobile -->
|
||||
<!-- Directory Header -->
|
||||
<div class="hidden sm:block text-center space-y-4">
|
||||
<h1 class="text-3xl font-bold tracking-tight sm:text-4xl">
|
||||
{{ t('directory.title') }}
|
||||
|
|
@ -22,8 +72,15 @@ const items = mockDirectoryItems
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Directory Filter -->
|
||||
<DirectoryFilter
|
||||
v-model:category="category"
|
||||
v-model:search="search"
|
||||
v-model:town="town"
|
||||
/>
|
||||
|
||||
<!-- Directory Grid -->
|
||||
<DirectoryGrid :items="items" />
|
||||
<DirectoryGrid :items="filteredItems" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,74 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useTheme } from '@/components/theme-provider'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
UtensilsCrossed,
|
||||
Bed,
|
||||
ShoppingBag,
|
||||
Sparkles,
|
||||
Car,
|
||||
Ship,
|
||||
HelpCircle,
|
||||
} from 'lucide-vue-next'
|
||||
import TukTuk from '@/components/icons/TukTuk.vue'
|
||||
import { type DirectoryItem } from '@/types/directory'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { currentTown, setCurrentTown } = useTheme()
|
||||
|
||||
type CategoryType = DirectoryItem['category']
|
||||
|
||||
const categories: CategoryType[] = ['tuktuk', 'restaurant', 'services', 'goods', 'lodging', 'taxi', 'lancha']
|
||||
|
||||
const categoryIcons = {
|
||||
restaurant: UtensilsCrossed,
|
||||
lodging: Bed,
|
||||
goods: ShoppingBag,
|
||||
services: Sparkles,
|
||||
tuktuk: TukTuk,
|
||||
taxi: Car,
|
||||
lancha: Ship,
|
||||
other: HelpCircle,
|
||||
} as const
|
||||
|
||||
const categoryColors = {
|
||||
tuktuk: 'text-amber-500',
|
||||
restaurant: 'text-orange-500',
|
||||
lodging: 'text-purple-500',
|
||||
goods: 'text-green-500',
|
||||
services: 'text-pink-500',
|
||||
taxi: 'text-yellow-500',
|
||||
lancha: 'text-blue-500',
|
||||
other: 'text-gray-500',
|
||||
} as const
|
||||
|
||||
const towns = computed(() => [
|
||||
{ id: 'all', label: t('directory.towns.all') },
|
||||
{ id: 'San Marcos', label: 'San Marcos' },
|
||||
{ id: 'San Pedro', label: 'San Pedro' },
|
||||
{ id: 'Tzununa', label: 'Tzununa' },
|
||||
{ id: 'Jaibalito', label: 'Jaibalito' },
|
||||
{ id: 'San Pablo', label: 'San Pablo' },
|
||||
{ id: 'Panajachel', label: 'Panajachel' },
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-4xl mx-auto text-center space-y-8">
|
||||
<h1 class="text-4xl font-bold tracking-tight sm:text-6xl">
|
||||
<h1 class="text-3xl font-bold tracking-tight sm:text-6xl">
|
||||
{{ t('home.title') }}
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-muted-foreground">
|
||||
<p class="text-lg sm:text-xl text-muted-foreground">
|
||||
{{ t('home.subtitle') }}
|
||||
</p>
|
||||
|
||||
|
|
@ -26,6 +83,53 @@ const { t } = useI18n()
|
|||
{{ t('home.learnMore') }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- Town Selector -->
|
||||
<div class="flex flex-col items-center gap-2 sm:gap-4">
|
||||
<h2 class="text-lg sm:text-xl font-semibold">{{ t('home.selectTown') }}</h2>
|
||||
<Select :model-value="currentTown" @update:model-value="setCurrentTown">
|
||||
<SelectTrigger class="w-[200px]">
|
||||
<SelectValue :placeholder="t('home.selectTownPlaceholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="town in towns" :key="town.id" :value="town.id">
|
||||
{{ town.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- Category Buttons -->
|
||||
<div class="relative mt-4 sm:mt-8">
|
||||
<!-- Gradient Fade Edges -->
|
||||
<div class="absolute left-0 top-0 bottom-0 w-4 bg-gradient-to-r from-background to-transparent z-10"></div>
|
||||
<div class="absolute right-0 top-0 bottom-0 w-4 bg-gradient-to-l from-background to-transparent z-10"></div>
|
||||
|
||||
<!-- Scrollable Container -->
|
||||
<div class="flex overflow-x-auto gap-1 px-4 pb-4
|
||||
[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]
|
||||
sm:justify-center">
|
||||
<router-link
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
:to="`/directory?category=${category}&town=${currentTown}`"
|
||||
class="group flex-shrink-0 w-[100px] sm:w-[120px]"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="default"
|
||||
class="w-full h-20 sm:h-24 flex flex-col items-center justify-center gap-1 sm:gap-2 group-hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<component
|
||||
:is="categoryIcons[category]"
|
||||
class="h-6 w-6 sm:h-7 sm:w-7"
|
||||
:class="categoryColors[category]"
|
||||
/>
|
||||
<span class="text-xs sm:text-sm">{{ t(`directory.categories.${category}`) }}</span>
|
||||
</Button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue