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

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>