Files
webMontime/src/views/app/AppHomeView.vue
T

157 lines
4.6 KiB
Vue
Raw Normal View History

2026-05-18 20:18:28 +03:30
<template>
<WebAppLayout>
2026-05-31 16:42:32 +03:30
<div v-if="bootstrapping" class="py-12 text-center text-sm text-muted">
در حال بارگذاری…
</div>
<section v-else class="space-y-5">
<div>
<h1 class="text-xl font-bold text-text">کشف خدمات</h1>
<p class="mt-1 text-sm text-muted">کسب‌وکارهای محلی نزدیک شما</p>
</div>
2026-05-31 16:42:32 +03:30
<div v-if="showSearch" class="relative">
<label class="sr-only" for="search">جستجو</label>
2026-05-25 16:29:03 +03:30
<AppIcon
name="search"
size="sm"
class="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted"
/>
<input
id="search"
v-model="searchQuery"
type="search"
placeholder="جستجو در نام یا آدرس…"
2026-05-25 16:29:03 +03:30
class="w-full rounded-xl border border-border bg-surface-2 py-3 pe-4 ps-10 text-sm text-text outline-none placeholder:text-muted focus:border-brand-500 focus:ring-2 focus:ring-brand-500/30"
/>
</div>
<CategoryFilter
2026-05-31 16:42:32 +03:30
v-if="showCategoryFilter"
:categories="activeCategories"
:selected-id="selectedCategoryId"
@select="onCategorySelect"
/>
<div v-if="loadingBusinesses" class="py-8 text-center text-sm text-muted">
در حال بارگذاری…
</div>
<p
v-else-if="error"
class="rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
>
{{ error }}
2026-05-18 20:18:28 +03:30
</p>
<div
v-else-if="!filteredBusinesses.length"
class="rounded-2xl border border-dashed border-border bg-surface-2 p-8 text-center text-sm text-muted"
2026-05-18 20:18:28 +03:30
>
کسب‌وکاری یافت نشد.
2026-05-18 20:18:28 +03:30
</div>
<ul v-else class="space-y-2">
<li v-for="b in filteredBusinesses" :key="b.id">
<BusinessCard :business="b" />
</li>
</ul>
2026-05-18 20:18:28 +03:30
</section>
</WebAppLayout>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
2026-05-31 16:42:32 +03:30
import { useRouter } from 'vue-router';
import { fetchBusinessCategories } from '@/api/business-categories';
import { fetchBusinesses } from '@/api/businesses';
import { ApiError } from '@/api/client';
import type { Business, BusinessCategory } from '@/api/types';
import BusinessCard from '@/components/business/BusinessCard.vue';
2026-05-25 16:29:03 +03:30
import AppIcon from '@/components/ui/AppIcon.vue';
import CategoryFilter from '@/components/business/CategoryFilter.vue';
2026-05-18 20:18:28 +03:30
import WebAppLayout from '@/layouts/WebAppLayout.vue';
2026-05-31 16:42:32 +03:30
const router = useRouter();
const categories = ref<BusinessCategory[]>([]);
const businesses = ref<Business[]>([]);
const selectedCategoryId = ref<string | null>(null);
const searchQuery = ref('');
2026-05-31 16:42:32 +03:30
const bootstrapping = ref(true);
const loadingBusinesses = ref(false);
const error = ref('');
const activeCategories = computed(() =>
categories.value.filter((c) => c.isActive),
);
2026-05-31 16:42:32 +03:30
const showCategoryFilter = computed(() => activeCategories.value.length > 1);
const showSearch = computed(() => businesses.value.length > 1);
const filteredBusinesses = computed(() => {
const q = searchQuery.value.trim().toLowerCase();
if (!q) {
return businesses.value;
}
return businesses.value.filter((b) => {
const haystack = [b.title, b.description, b.address]
.filter(Boolean)
.join(' ')
.toLowerCase();
return haystack.includes(q);
});
});
async function loadCategories() {
try {
2026-05-31 16:42:32 +03:30
categories.value = await fetchBusinessCategories();
if (activeCategories.value.length === 1) {
selectedCategoryId.value = activeCategories.value[0].id;
}
} catch (e) {
error.value =
e instanceof ApiError ? e.message : 'بارگذاری دسته‌ها ناموفق بود.';
}
}
async function loadBusinesses() {
loadingBusinesses.value = true;
error.value = '';
try {
businesses.value = await fetchBusinesses({
categoryId: selectedCategoryId.value ?? undefined,
isActive: true,
});
} catch (e) {
error.value =
e instanceof ApiError ? e.message : 'بارگذاری کسب‌وکارها ناموفق بود.';
} finally {
loadingBusinesses.value = false;
}
}
function onCategorySelect(id: string | null) {
selectedCategoryId.value = id;
}
watch(selectedCategoryId, () => {
2026-05-31 16:42:32 +03:30
if (!bootstrapping.value) {
void loadBusinesses();
}
});
2026-05-31 16:42:32 +03:30
onMounted(async () => {
await loadCategories();
await loadBusinesses();
if (businesses.value.length === 1) {
await router.replace({
name: 'app-business-detail',
params: { id: businesses.value[0].id },
});
return;
}
bootstrapping.value = false;
});
2026-05-18 20:18:28 +03:30
</script>