324 lines
11 KiB
Vue
324 lines
11 KiB
Vue
<script setup>
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
usePageTitle('Buy View Product Market');
|
|
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import axios from 'axios';
|
|
import { useNavigate } from '../composables/Core/useNavigate';
|
|
import LoadingSpinner from '../Components/LoadingSpinner.vue';
|
|
import FileImage from '../Components/Core/FileImage.vue';
|
|
import { useAuth } from '../composables/Core/useAuth';
|
|
import { useModal } from '../composables/Core/useModal';
|
|
|
|
const props = defineProps({
|
|
target: { type: String, required: false },
|
|
data: { type: Object, default: () => ({}) },
|
|
payload: { type: Object, default: null }
|
|
});
|
|
|
|
const { navigate } = useNavigate();
|
|
const { role } = useAuth();
|
|
const modal = useModal();
|
|
|
|
import { useProductStore } from '../stores/product';
|
|
const productStore = useProductStore();
|
|
|
|
const product = computed(() => productStore.currentProduct);
|
|
const loading = computed(() => productStore.loading);
|
|
const error = computed(() => productStore.error);
|
|
|
|
const fetchProductDetails = async () => {
|
|
const targetHash = props.payload?.product_hashkey || props.payload?.product_hash || props.target;
|
|
const storeHash = props.payload?.store_hashkey || props.payload?.store_hash || props.data?.store_hash;
|
|
|
|
await productStore.fetchProductById(targetHash, storeHash);
|
|
};
|
|
|
|
const goBack = () => {
|
|
const storeHash = props.payload?.store_hash || product.value?.store_hash;
|
|
if (storeHash) {
|
|
navigate({ page: 'ViewStoreMarket', props: { target: storeHash } });
|
|
} else {
|
|
navigate({ page: 'ListProductsMarket' });
|
|
}
|
|
};
|
|
|
|
const manageProduct = () => {
|
|
const storeHash = props.payload?.store_hash || product.value?.store_hash;
|
|
const productHash = props.payload?.product_hash || props.target;
|
|
|
|
if (product.value.is_from_store && storeHash) {
|
|
navigate({
|
|
page: 'ManageProductAdmin',
|
|
props: {
|
|
payload: {
|
|
product_hashkey: productHash,
|
|
store_hashkey: storeHash
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
navigate({ page: 'ManageProductAdmin', props: { target: productHash } });
|
|
}
|
|
};
|
|
|
|
const displayPrice = computed(() => {
|
|
if (!product.value) return '';
|
|
const price = product.value.store_price || product.value.price;
|
|
const prefix = product.value.is_from_store ? 'Store Price ' : '';
|
|
return `${prefix}₱${price} / ${product.value.unitname}`;
|
|
});
|
|
|
|
const addToCart = async () => {
|
|
try {
|
|
const productHash = props.payload?.product_hash || props.target;
|
|
const response = await axios.get(`/cart/add/one/${productHash}`);
|
|
if (response.data === true || response.data?.success) {
|
|
modal.open({
|
|
title: 'Success',
|
|
body: 'Added to cart!'
|
|
});
|
|
} else {
|
|
modal.open({
|
|
title: 'Error',
|
|
body: 'Failed to add to cart.'
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error('Add to cart failed:', e);
|
|
modal.open({
|
|
title: 'Error',
|
|
body: 'Error adding to cart.'
|
|
});
|
|
}
|
|
};
|
|
|
|
const buyNow = () => {
|
|
// Navigate to a (yet to be created) checkout or confirmation page
|
|
modal.open({
|
|
title: 'Info',
|
|
body: 'Buy Now clicked! This would typically go to checkout.'
|
|
});
|
|
};
|
|
|
|
const printPosCode = () => {
|
|
const w = window.open('', '_blank', 'width=400,height=500');
|
|
w.document.write(`<html><body style="text-align:center;font-family:sans-serif">
|
|
<h3>${product.value.name}</h3>
|
|
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(product.value.pos_qrcode)}" />
|
|
<p style="font-size:12px">${product.value.pos_qrcode}</p>
|
|
<script>window.onload=()=>{window.print();window.close();}<\/script>
|
|
</body></html>`);
|
|
w.document.close();
|
|
};
|
|
|
|
onMounted(() => {
|
|
fetchProductDetails();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="product-details-page pb-5">
|
|
<div v-if="loading" class="text-center py-5">
|
|
<LoadingSpinner />
|
|
<p class="mt-3 text-muted">Loading product details...</p>
|
|
</div>
|
|
|
|
<div v-else-if="error" class="tf-container mt-5 text-center">
|
|
<div class="alert alert-danger">{{ error }}</div>
|
|
<button @click="goBack" class="btn btn-outline-secondary mt-3 rounded-pill">
|
|
Go Back
|
|
</button>
|
|
</div>
|
|
|
|
<template v-else-if="product">
|
|
<!-- Hero Image Section -->
|
|
<div class="product-hero">
|
|
<button @click="goBack" class="back-btn shadow">
|
|
<i class="fas fa-chevron-left"></i>
|
|
</button>
|
|
<div class="hero-image-container">
|
|
<FileImage :src="product.photourl && product.photourl.length > 0 ? product.photourl[0] : ''"
|
|
class="hero-img" fallback="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/146710fe9ece.bin" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tf-container product-content">
|
|
<div class="info-card shadow-sm">
|
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
<div>
|
|
<h2 class="fw_7 mb-1">{{ product.name }}</h2>
|
|
<span class="badge bg-soft-success text-success rounded-pill px-3">
|
|
{{ product.category }}
|
|
</span>
|
|
</div>
|
|
<div class="text-end">
|
|
<h3 class="price-tag text-primary fw_7 mb-0">{{ displayPrice }}</h3>
|
|
<small class="text-muted" v-if="product.available !== null">
|
|
{{ product.available }} available
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="description-section mt-4">
|
|
<h5 class="fw_6 mb-2">Description</h5>
|
|
<p class="text-muted line-height-16">
|
|
{{ product.store_description || product.description }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- POS QR Code Section -->
|
|
<div v-if="product.pos_qrcode" class="pos-qr-section mt-4 p-3 rounded-xl text-center border">
|
|
<h6 class="fw_7 mb-2"><i class="fas fa-barcode me-2"></i> POS Scan Code</h6>
|
|
<div class="qr-container p-2 d-inline-block rounded shadow-sm mb-2 qr-container-bg">
|
|
<img :src="`https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(product.pos_qrcode)}`"
|
|
:alt="product.pos_qrcode" style="width: 150px; height: 150px;">
|
|
</div>
|
|
<div class="small text-muted fw_6 mb-2">{{ product.is_from_store ? 'Store Exclusive Code' : 'Product Identification' }}</div>
|
|
<div>
|
|
<button @click="printPosCode" class="btn btn-outline-primary btn-sm rounded-pill px-3">
|
|
<i class="fas fa-print me-2"></i> Print
|
|
</button>
|
|
</div>
|
|
<div class="stats-row d-flex justify-content-around mt-3 pt-3 border-top">
|
|
<div class="stat-item">
|
|
<div class="small text-muted">Sold Today</div>
|
|
<div class="fw_7 stat-value">
|
|
{{ product.is_from_store ? (product.store_sold_today ?? product.sold_today ?? 0) : (product.sold_today ?? 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="small text-muted">Total Sold</div>
|
|
<div class="fw_7 stat-value">
|
|
{{ product.is_from_store ? (product.store_sold ?? product.sold ?? 0) : (product.sold ?? 0) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions-grid mt-4 pt-4 border-top">
|
|
<div class="row g-2">
|
|
<div class="col-6">
|
|
<button @click="addToCart"
|
|
class="btn btn-light w-100 py-3 rounded-xl fw_6 shadow-sm border">
|
|
<img src="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/d36eb6a17e27.bin" class="me-2" style="width: 20px;">
|
|
Add Cart
|
|
</button>
|
|
</div>
|
|
<div class="col-6">
|
|
<button @click="buyNow" class="btn btn-primary w-100 py-3 rounded-xl fw_6 shadow-sm">
|
|
<img src="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/6446fb001e8b.bin" class="me-2"
|
|
style="width: 20px; filter: brightness(0) invert(1);">
|
|
Buy Now
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="['ult', 'superoperator', 'operator'].includes(role)" class="admin-actions mt-3">
|
|
<button @click="manageProduct" class="btn btn-soft-dark w-100 py-3 rounded-xl fw_6">
|
|
<i class="fas fa-cog me-2"></i> Manage Product
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pos-qr-section {
|
|
background-color: var(--bg-card);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.qr-container-bg {
|
|
background-color: var(--bg-card);
|
|
}
|
|
|
|
.stat-value {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.product-hero {
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
|
|
.hero-image-container {
|
|
width: 100%;
|
|
height: 350px;
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.hero-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.back-btn {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
z-index: 10;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #333;
|
|
}
|
|
|
|
.product-content {
|
|
margin-top: -30px;
|
|
position: relative;
|
|
z-index: 20;
|
|
}
|
|
|
|
.info-card {
|
|
background: white;
|
|
border-radius: 30px;
|
|
padding: 25px;
|
|
}
|
|
|
|
.price-tag {
|
|
color: #42b983 !important;
|
|
}
|
|
|
|
.bg-soft-success {
|
|
background: rgba(66, 185, 131, 0.1);
|
|
}
|
|
|
|
.rounded-xl {
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.btn-soft-dark {
|
|
background: #f1f2f6;
|
|
color: #2c3e50;
|
|
border: none;
|
|
}
|
|
|
|
.line-height-16 {
|
|
line-height: 1.6;
|
|
}
|
|
|
|
:global(.dark-mode) .info-card {
|
|
background: #24272c;
|
|
}
|
|
|
|
:global(.dark-mode) .back-btn {
|
|
background: #24272c;
|
|
color: #fff;
|
|
}
|
|
|
|
:global(.dark-mode) .btn-soft-dark {
|
|
background: #1a1c20;
|
|
color: #e0e0e0;
|
|
}
|
|
</style>
|