145 lines
4.5 KiB
Vue
145 lines
4.5 KiB
Vue
<script setup>
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
usePageTitle('List Stores');
|
|
|
|
import { ref, onMounted, computed, watch } from 'vue';
|
|
import axios from 'axios';
|
|
import { useNavigate } from '../composables/Core/useNavigate';
|
|
import { useFileBlobCache } from '../composables/useFileBlobCache';
|
|
import SearchBar from '../Components/Core/Search/SearchBar.vue';
|
|
import StoreCard from '../Components/Market/StoreCard.vue';
|
|
import StoreListSkeleton from '../Components/Core/Skeleton/StoreListSkeleton.vue';
|
|
import LoadingSpinner from '../Components/LoadingSpinner.vue';
|
|
import usePageData from '../composables/usePageData';
|
|
|
|
const { navigate } = useNavigate();
|
|
const { preCacheFiles, blobCache } = useFileBlobCache();
|
|
const { data: pageData, loading: pageLoading, fetchPageData } = usePageData();
|
|
|
|
const stores = ref([]);
|
|
const loading = ref(true);
|
|
const searchQuery = ref('');
|
|
|
|
const goToAddStore = () => {
|
|
navigate({ page: 'CreateStore' });
|
|
};
|
|
|
|
const fetchStores = async () => {
|
|
loading.value = true; // Still show local loading if NO cache exists
|
|
const result = await fetchPageData('/ListStores/List/data', {}, 'POST');
|
|
if (result && result.data) {
|
|
stores.value = result.data;
|
|
}
|
|
loading.value = false;
|
|
};
|
|
|
|
const filteredStores = computed(() => {
|
|
if (!searchQuery.value) return stores.value;
|
|
const q = searchQuery.value.toLowerCase();
|
|
return stores.value.filter(s =>
|
|
s.name.toLowerCase().includes(q) ||
|
|
s.category?.toLowerCase().includes(q) ||
|
|
s.subcategory?.toLowerCase().includes(q)
|
|
);
|
|
});
|
|
|
|
const viewStore = (store) => {
|
|
navigate({
|
|
page: 'ViewStoreMarket',
|
|
props: { target: store.hashkey }
|
|
});
|
|
};
|
|
|
|
// Pre-cache store photos when stores are loaded
|
|
const preCacheStorePhotos = async () => {
|
|
const photoHashes = stores.value
|
|
.map(store => store.photourl)
|
|
.filter(hash => hash && hash.length);
|
|
|
|
await preCacheFiles(photoHashes);
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await fetchStores();
|
|
// Pre-cache photos after stores are loaded
|
|
await preCacheStorePhotos();
|
|
});
|
|
|
|
// Watch for stores changes and pre-cache photos
|
|
watch(stores, (newStores) => {
|
|
if (newStores && newStores.length > 0) {
|
|
const photoHashes = newStores
|
|
.map(store => store.photourl)
|
|
.filter(hash => hash && hash.length);
|
|
preCacheFiles(photoHashes);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="list-stores-page pb-5">
|
|
<div class="tf-container mt-4">
|
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
|
<h3 class="fw_6 mb-0">Browse Stores</h3>
|
|
<div class="badge bg-soft-info px-3 py-2 rounded-pill text-info">
|
|
{{ filteredStores.length }} Stores
|
|
</div>
|
|
<button @click="goToAddStore" class="btn btn-primary rounded-pill px-3">
|
|
Create Store
|
|
</button>
|
|
</div>
|
|
|
|
<SearchBar v-model="searchQuery" placeholder="Search stores..." class="mb-4" />
|
|
|
|
<div v-if="loading" class="mt-2 text-center">
|
|
<StoreListSkeleton :count="6" />
|
|
</div>
|
|
|
|
<div v-else-if="filteredStores.length === 0" class="text-center py-5 no-results">
|
|
<img src="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/85605eacd4c8.bin" style="width: 120px; opacity: 0.3;" class="mb-3">
|
|
<h5>No stores found</h5>
|
|
<p class="text-muted">Try adjusting your search criteria</p>
|
|
</div>
|
|
|
|
<div v-else class="row g-3 store-grid">
|
|
<div v-for="store in filteredStores" :key="store.hashkey" class="col-12 col-sm-6 col-md-4">
|
|
<StoreCard :name="store.name" :category="store.category" :subcategory="store.subcategory"
|
|
:image="blobCache[store.photourl] || 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/85605eacd4c8.bin'" @click="viewStore(store)" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.badge.bg-soft-info {
|
|
background-color: rgba(0, 123, 255, 0.1);
|
|
}
|
|
|
|
.store-grid {
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.no-results {
|
|
background: #f8f9fa;
|
|
border-radius: 20px;
|
|
border: 2px dashed #dee2e6;
|
|
}
|
|
|
|
:global(.dark-mode) .no-results {
|
|
background: #1a1c20;
|
|
border-color: #2c3e50;
|
|
}
|
|
</style> |