Files
BarangaySystem/resources/js/Pages/ListStores.vue
2026-06-06 18:43:00 +08:00

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>