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

356 lines
11 KiB
Vue

<script setup>
import { usePageTitle } from '../composables/Core/usePageTitle';
usePageTitle('My Stores');
import { ref, onMounted, computed } from 'vue';
import axios from 'axios';
import { useNavigate } from '../composables/Core/useNavigate';
import { useAuth } from '../composables/Core/useAuth';
import { useModal } from '../composables/Core/useModal';
import SearchBar from '../Components/Core/Search/SearchBar.vue';
import StoreCard from '../Components/Market/StoreCard.vue';
import StoreListSkeleton from '../Components/Core/Skeleton/StoreListSkeleton.vue';
import BackButton from '../Components/Core/BackButton.vue';
import FileImage from '../Components/Core/FileImage.vue';
const { navigate } = useNavigate();
const { user, isLoggedIn, isUltimate, isSuperOperator, isOperator } = useAuth();
const isBig3 = computed(() => isUltimate.value || isSuperOperator.value || isOperator.value);
const modal = useModal();
const stores = ref([]);
const loading = ref(true);
const searchQuery = ref('');
const viewMode = ref('grid'); // 'grid' or 'list'
const fetchStores = async () => {
loading.value = true;
try {
const response = await axios.post('/ListStores/MyStores/data');
if (response.data && Array.isArray(response.data)) {
stores.value = response.data;
}
} catch (error) {
console.error('Failed to fetch my stores:', error);
} finally {
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)
);
});
const viewStore = (store) => {
navigate({
page: 'ViewStoreMarket',
props: { target: store.hashkey }
});
};
const editStore = (store) => {
navigate({
page: 'EditStoreUltimate',
props: { target: store.hashkey }
});
};
const assignProducts = (store) => {
navigate({
page: 'AddProductsToStore',
props: { target: store.hashkey }
});
};
const goToBrowseStores = () => {
navigate({ page: 'ListStores' });
};
const getRoleBadgeClass = (role) => {
switch (role) {
case 'owner': return 'badge-owner';
case 'manager': return 'badge-manager';
default: return 'badge-viewer';
}
};
const getRoleLabel = (role) => {
switch (role) {
case 'owner': return 'Owner';
case 'manager': return 'Manager';
default: return 'Viewer';
}
};
onMounted(async () => {
await fetchStores();
});
</script>
<template>
<div class="my-stores-page pb-5">
<div class="tf-container mt-4">
<BackButton to="Home" />
<div class="d-flex align-items-center justify-content-between mb-3 mt-3">
<h3 class="fw_6 mb-0">My Stores</h3>
<div class="d-flex align-items-center gap-2">
<div class="badge bg-soft-primary px-3 py-2 rounded-pill text-primary">
{{ filteredStores.length }} Stores
</div>
<div class="view-toggle btn-group" role="group">
<button type="button" class="btn btn-sm" :class="viewMode === 'grid' ? 'btn-primary' : 'btn-outline-secondary'" @click="viewMode = 'grid'">
<i class="fas fa-th-large"></i>
</button>
<button type="button" class="btn btn-sm" :class="viewMode === 'list' ? 'btn-primary' : 'btn-outline-secondary'" @click="viewMode = 'list'">
<i class="fas fa-list"></i>
</button>
</div>
</div>
</div>
<SearchBar v-model="searchQuery" placeholder="Search your stores..." class="mb-4" />
<div v-if="loading" class="mt-2 text-center">
<StoreListSkeleton :count="4" />
</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 yet</h5>
<p class="text-muted">You don't have any stores assigned to you.</p>
<button @click="goToBrowseStores" class="btn btn-primary rounded-pill px-4 mt-2">
Browse All Stores
</button>
</div>
<!-- Grid View -->
<div v-else-if="viewMode === 'grid'" class="row g-3 store-grid">
<div v-for="store in filteredStores" :key="store.hashkey" class="col-6 col-md-4">
<div class="store-card-wrapper" @click="viewStore(store)">
<StoreCard
:name="store.name"
:category="store.category"
:image="store.photourl || ''"
/>
<span class="role-badge" :class="getRoleBadgeClass(store.role)">
{{ getRoleLabel(store.role) }}
</span>
<div class="store-card-actions" @click.stop>
<button @click="viewStore(store)" class="btn btn-sm btn-icon btn-outline-info" title="View Store">
<i class="fas fa-eye"></i>
</button>
<button v-if="store.role === 'owner' || isBig3" @click="assignProducts(store)" class="btn btn-sm btn-icon btn-outline-success" title="Add Products">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
</div>
<!-- List View -->
<div v-else class="store-list-view">
<div v-for="store in filteredStores" :key="store.hashkey" class="store-list-item" @click="viewStore(store)">
<div class="store-list-avatar">
<FileImage
:src="store.photourl || ''"
class="avatar-img"
fallback="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/85605eacd4c8.bin"
/>
</div>
<div class="store-list-info">
<h6 class="fw_6 mb-0">{{ store.name }}</h6>
<p class="text-muted small mb-0">{{ store.category || 'General' }}</p>
</div>
<div class="store-list-actions">
<span class="role-badge-sm" :class="getRoleBadgeClass(store.role)">
{{ getRoleLabel(store.role) }}
</span>
<button @click.stop="viewStore(store)" class="btn btn-sm btn-icon btn-outline-info" title="View Store">
<i class="fas fa-eye"></i>
</button>
<button v-if="store.role === 'owner' || isBig3" @click.stop="assignProducts(store)" class="btn btn-sm btn-icon btn-outline-success" title="Add Products">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
<!-- Browse All Stores Link -->
<div v-if="!loading && filteredStores.length > 0" class="text-center mt-4">
<button @click="goToBrowseStores" class="btn btn-outline-primary rounded-pill px-4">
<i class="fas fa-store me-2"></i> Browse All Stores
</button>
</div>
</div>
</div>
</template>
<style scoped>
.my-stores-page {
background: var(--bg-primary);
min-height: 100vh;
}
.badge.bg-soft-primary {
background-color: rgba(66, 185, 131, 0.1);
}
.store-grid {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.store-card-wrapper {
position: relative;
cursor: pointer;
}
.store-card-actions {
display: flex;
justify-content: flex-end;
gap: 6px;
padding: 6px 4px 2px;
}
.role-badge {
position: absolute;
top: 8px;
left: 8px;
padding: 3px 10px;
border-radius: 20px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
z-index: 5;
backdrop-filter: blur(4px);
}
.badge-owner {
background: rgba(16, 185, 129, 0.85);
color: #fff;
}
.badge-manager {
background: rgba(99, 102, 241, 0.85);
color: #fff;
}
.badge-viewer {
background: rgba(107, 114, 128, 0.85);
color: #fff;
}
.role-badge-sm {
padding: 2px 8px;
border-radius: 12px;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
}
.no-results {
background: #f8f9fa;
border-radius: 20px;
border: 2px dashed #dee2e6;
}
/* List View */
.store-list-view {
animation: fadeIn 0.5s ease-out;
}
.store-list-item {
display: flex;
align-items: center;
padding: 12px 16px;
background: var(--bg-card);
border-radius: 12px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid var(--border-color);
}
.store-list-item:hover {
background: var(--bg-tertiary);
transform: translateX(4px);
}
.store-list-avatar {
width: 48px;
height: 48px;
border-radius: 12px;
overflow: hidden;
flex-shrink: 0;
background: #f0f3f6;
}
.store-list-avatar .avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.store-list-info {
flex: 1;
margin-left: 12px;
min-width: 0;
}
.store-list-info h6 {
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.store-list-actions {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
margin-left: 8px;
}
.view-toggle .btn {
padding: 4px 10px;
font-size: 0.8rem;
}
.btn-icon {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border-radius: 8px;
font-size: 0.75rem;
}
:global(.dark-mode) .no-results {
background: #1a1c20;
border-color: #2c3e50;
}
:global(.dark-mode) .store-list-item {
background: var(--bg-card);
border-color: var(--border-color);
}
:global(.dark-mode) .store-list-item:hover {
background: var(--bg-tertiary);
}
</style>