315 lines
11 KiB
Vue
315 lines
11 KiB
Vue
<script setup>
|
|
import { ref, onMounted, computed, h } from 'vue';
|
|
import axios from 'axios';
|
|
import { useNavigate } from '../composables/Core/useNavigate';
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
import { useAuth } from '../composables/Core/useAuth';
|
|
import { useModal } from '../composables/Core/useModal';
|
|
import LoadingSpinner from '../Components/LoadingSpinner.vue';
|
|
import FileImage from '../Components/Core/FileImage.vue';
|
|
import BackButton from '../Components/Core/BackButton.vue';
|
|
import UpdateProductModal from '../Components/Market/UpdateProductModal.vue';
|
|
|
|
const props = defineProps({
|
|
target: { type: String, default: null },
|
|
payload: { type: Object, default: null }
|
|
});
|
|
|
|
const { navigate } = useNavigate();
|
|
const { role } = useAuth();
|
|
const isBig3 = computed(() => ['ULTIMATE', 'SUPER_OPERATOR', 'OPERATOR'].includes(role.value));
|
|
const modal = useModal();
|
|
usePageTitle('Manage Product');
|
|
|
|
const product = ref(null);
|
|
const loading = ref(true);
|
|
const error = ref(null);
|
|
|
|
const productHash = computed(() => props.target || props.payload?.product_hash || props.payload?.product_hashkey);
|
|
const storeHash = computed(() => props.payload?.store_hash || props.payload?.store_hashkey);
|
|
|
|
const fetchDetails = async () => {
|
|
loading.value = true;
|
|
try {
|
|
const response = await axios.post('/View/Product/Details/data', {
|
|
target: productHash.value,
|
|
data: { store_hash: storeHash.value }
|
|
});
|
|
if (response.data && response.data.success) {
|
|
product.value = response.data.data;
|
|
} else {
|
|
error.value = 'Failed to load product details';
|
|
}
|
|
} catch (e) {
|
|
error.value = 'Error fetching product information';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const openUpdateModal = (isStore = false) => {
|
|
modal.open({
|
|
title: isStore ? 'Manage Store Listing' : 'Edit Global Details',
|
|
body: h(UpdateProductModal, {
|
|
productHash: productHash.value,
|
|
storeHash: isStore ? storeHash.value : null,
|
|
onSaved: () => {
|
|
modal.close();
|
|
fetchDetails(); // Refresh the page data
|
|
},
|
|
onClose: () => modal.close()
|
|
})
|
|
});
|
|
};
|
|
|
|
const goToGlobalEdit = () => {
|
|
openUpdateModal(false);
|
|
};
|
|
|
|
const goToStoreEdit = () => {
|
|
openUpdateModal(true);
|
|
};
|
|
|
|
onMounted(fetchDetails);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="manage-product-page pb-5">
|
|
<div class="tf-container mt-4">
|
|
<BackButton to="Home" />
|
|
|
|
<div v-if="loading" class="text-center py-10">
|
|
<LoadingSpinner :show="loading" />
|
|
<p class="mt-3 text-muted">Loading management console...</p>
|
|
</div>
|
|
|
|
<div v-else-if="error" class="alert alert-danger mt-4">{{ error }}</div>
|
|
|
|
<template v-else-if="product">
|
|
<div class="management-header mt-4">
|
|
<div class="d-flex align-items-center gap-4">
|
|
<div class="product-miniature shadow-sm">
|
|
<FileImage :src="product.photourl && product.photourl[0] ? product.photourl[0] : ''"
|
|
class="img-fluid rounded-lg" fallback="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/146710fe9ece.bin" />
|
|
</div>
|
|
<div>
|
|
<h2 class="fw_8 mb-1">{{ product.name }}</h2>
|
|
<div class="badge bg-soft-primary text-primary px-3 rounded-pill">
|
|
{{ product.category }}
|
|
</div>
|
|
<div v-if="storeHash" class="badge bg-soft-info text-info px-3 rounded-pill ms-2">
|
|
<i class="fas fa-store me-1"></i> Store Specific View
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4 mt-4">
|
|
<!-- Global Management Card -->
|
|
<div class="col-md-6">
|
|
<div class="management-card global-card h-100 shadow-sm transition-hover"
|
|
:class="{'locked-card': !isBig3}"
|
|
@click="isBig3 ? goToGlobalEdit() : null">
|
|
<div class="card-body p-4 position-relative">
|
|
<div v-if="!isBig3" class="lock-overlay d-flex flex-column align-items-center justify-content-center">
|
|
<div class="lock-icon mb-2">
|
|
<i class="fas fa-lock fa-2x text-muted"></i>
|
|
</div>
|
|
<span class="badge bg-light text-dark shadow-sm">Admin Only</span>
|
|
</div>
|
|
<div class="icon-circle bg-soft-warning text-warning mb-3">
|
|
<i class="fas fa-globe-asia fa-lg"></i>
|
|
</div>
|
|
<h4 class="fw_7">Global Details</h4>
|
|
<p class="text-muted small">Edit primary product information, photos, categories, and base settings available across all markets.</p>
|
|
<div class="stats-row mt-4">
|
|
<div class="stat-item">
|
|
<div class="label">Base Price</div>
|
|
<div class="value">₱{{ product.price }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="label">Unit</div>
|
|
<div class="value text-truncate" style="max-width: 80px;">{{ product.unitname }}</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn w-100 mt-4 rounded-xl fw_6" :class="isBig3 ? 'btn-outline-warning' : 'btn-light disabled'">
|
|
{{ isBig3 ? 'Edit Global Data' : 'View Only' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Store Context Card -->
|
|
<div v-if="storeHash" class="col-md-6">
|
|
<div class="management-card store-card h-100 shadow-sm border-primary transition-hover" @click="goToStoreEdit">
|
|
<div class="card-body p-4">
|
|
<div class="icon-circle bg-soft-primary text-primary mb-3">
|
|
<i class="fas fa-store-alt fa-lg"></i>
|
|
</div>
|
|
<h4 class="fw_7">Store Listing</h4>
|
|
<p class="text-muted small">Manage how this product appears in the current store. Override pricing and update available stock.</p>
|
|
<div class="stats-row mt-4">
|
|
<div class="stat-item">
|
|
<div class="label">Store Price</div>
|
|
<div class="value text-primary">₱{{ product.store_price || product.price }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="label">Stock</div>
|
|
<div class="value" :class="product.available > 0 ? 'text-success' : 'text-danger'">
|
|
{{ product.available }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary w-100 mt-4 rounded-xl fw_6 shadow-sm">
|
|
Manage Store Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional Actions / Insight Placeholder -->
|
|
<div class="col-12 mt-4">
|
|
<div class="insight-card p-4 rounded-xxl bg-light border">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h5 class="fw_7 mb-1">Product Insights</h5>
|
|
<p class="text-muted small mb-0">Total sales and performance tracking coming soon.</p>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-white text-dark border rounded-pill px-3 py-2">
|
|
<i class="fas fa-chart-line me-2 text-success"></i> Trending
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.manage-product-page {
|
|
background: #fbfcfe;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.management-header {
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 24px;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.03);
|
|
}
|
|
|
|
.product-miniature {
|
|
width: 100px;
|
|
height: 100px;
|
|
}
|
|
|
|
.locked-card {
|
|
cursor: not-allowed;
|
|
background: #f8f9fa;
|
|
border: 1px dashed #dee2e6;
|
|
}
|
|
|
|
.lock-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(255,255,255,0.4);
|
|
z-index: 2;
|
|
border-radius: inherit;
|
|
backdrop-filter: blur(1px);
|
|
}
|
|
|
|
.management-card {
|
|
border-radius: 20px;
|
|
border: 1px solid transparent;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.product-miniature img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.management-card {
|
|
background: white;
|
|
border-radius: 24px;
|
|
border: 1px solid transparent;
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.management-card.global-card {
|
|
border-bottom: 4px solid #f8d05e;
|
|
}
|
|
|
|
.management-card.store-card {
|
|
border-bottom: 4px solid #0085ff;
|
|
}
|
|
|
|
.transition-hover:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 10px 25px rgba(0,0,0,0.08) !important;
|
|
}
|
|
|
|
.icon-circle {
|
|
width: 50px;
|
|
height: 50px;
|
|
border-radius: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.bg-soft-warning { background: rgba(248, 208, 94, 0.1); }
|
|
.bg-soft-primary { background: rgba(0, 133, 255, 0.1); }
|
|
.bg-soft-info { background: rgba(0, 219, 255, 0.1); }
|
|
|
|
.stats-row {
|
|
display: flex;
|
|
gap: 20px;
|
|
padding: 12px;
|
|
background: #f8f9fa;
|
|
border-radius: 14px;
|
|
}
|
|
|
|
.stat-item .label {
|
|
font-size: 0.75rem;
|
|
color: #889;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.stat-item .value {
|
|
font-weight: 700;
|
|
font-size: 1.1rem;
|
|
color: #2e3b4e;
|
|
}
|
|
|
|
.rounded-lg { border-radius: 16px; }
|
|
.rounded-xl { border-radius: 12px; }
|
|
.rounded-xxl { border-radius: 20px; }
|
|
|
|
:global(.dark-mode) .management-header,
|
|
:global(.dark-mode) .management-card {
|
|
background: #1a1c20;
|
|
color: #eee;
|
|
}
|
|
|
|
:global(.dark-mode) .stats-row {
|
|
background: #24272c;
|
|
}
|
|
|
|
:global(.dark-mode) .stat-item .value {
|
|
color: #fff;
|
|
}
|
|
</style>
|