356 lines
11 KiB
Vue
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>
|