260 lines
8.0 KiB
Vue
260 lines
8.0 KiB
Vue
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
|
||
const props = defineProps({
|
||
session: {
|
||
type: Object,
|
||
required: true
|
||
}
|
||
});
|
||
|
||
const isExpanded = ref(false);
|
||
|
||
const toggleExpand = () => {
|
||
isExpanded.value = !isExpanded.value;
|
||
};
|
||
|
||
const transactions = computed(() => {
|
||
return props.session.transactions || [];
|
||
});
|
||
|
||
const formattedDate = computed(() => {
|
||
if (!props.session.created_at) return 'N/A';
|
||
const date = new Date(props.session.created_at);
|
||
return date.toLocaleString('en-PH', {
|
||
month: 'short',
|
||
day: '2-digit',
|
||
year: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
hour12: true
|
||
}).replace(',', ' •');
|
||
});
|
||
|
||
const statusClass = computed(() => {
|
||
switch (props.session.status) {
|
||
case 'completed': return 'badge-soft-success';
|
||
case 'active': return 'badge-soft-primary';
|
||
case 'voided': return 'badge-soft-danger';
|
||
default: return 'badge-soft-secondary';
|
||
}
|
||
});
|
||
|
||
const paymentIcon = computed(() => {
|
||
switch (props.session.payment_method?.toLowerCase()) {
|
||
case 'cash': return 'fas fa-money-bill-wave';
|
||
case 'credit': return 'fas fa-credit-card';
|
||
case 'online': return 'fas fa-mobile-alt';
|
||
default: return 'fas fa-receipt';
|
||
}
|
||
});
|
||
|
||
const formatCurrency = (amount) => {
|
||
return new Intl.NumberFormat('en-PH', {
|
||
style: 'currency',
|
||
currency: 'PHP'
|
||
}).format(amount);
|
||
};
|
||
|
||
const getProductInfo = (transaction) => {
|
||
return {
|
||
name: transaction.product?.name || 'Unknown Product',
|
||
quantity: transaction.quantity || 0,
|
||
unitPrice: transaction.price_at_sale || 0,
|
||
totalPrice: transaction.total_price || 0
|
||
};
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<div class="card mb-3 border-0 shadow-sm rounded-4 overflow-hidden pos-history-card">
|
||
<div class="card-body p-3">
|
||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||
<div>
|
||
<span :class="['badge rounded-pill px-3 py-2 text-uppercase fw-bold', statusClass]">
|
||
{{ session.status }}
|
||
</span>
|
||
<h6 class="mb-0 mt-2 text-primary fw-bold">
|
||
{{ session.customer_name || 'Walk-in Customer' }}
|
||
</h6>
|
||
<small class="text-muted d-block mt-1">
|
||
<i class="far fa-clock me-1"></i> {{ formattedDate }}
|
||
</small>
|
||
</div>
|
||
<div class="text-end">
|
||
<h5 class="mb-0 fw-black text-dark">
|
||
{{ formatCurrency(session.total_amount) }}
|
||
</h5>
|
||
<small class="text-muted">
|
||
{{ session.items_count }} {{ session.items_count === 1 ? 'item' : 'items' }}
|
||
</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex align-items-center justify-content-between mt-3 pt-3 border-top border-light">
|
||
<div class="d-flex align-items-center">
|
||
<div class="payment-icon-wrapper bg-light rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 32px; height: 32px;">
|
||
<i :class="[paymentIcon, 'text-muted sm']"></i>
|
||
</div>
|
||
<span class="text-muted small text-capitalize">{{ session.payment_method || 'N/A' }}</span>
|
||
</div>
|
||
|
||
<div v-if="session.hashkey" class="text-muted small">
|
||
<span class="badge bg-light text-muted fw-normal rounded-pill">#{{ session.hashkey.substring(0, 8) }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Expandable Items Section -->
|
||
<div v-if="isExpanded && transactions.length > 0" class="items-section mt-3 pt-3 border-top border-light">
|
||
<div class="items-header d-flex align-items-center mb-2">
|
||
<i class="fas fa-box-open text-muted me-2"></i>
|
||
<span class="text-muted small fw-bold">Transaction Items</span>
|
||
</div>
|
||
<div class="items-list">
|
||
<div v-for="item in transactions" :key="item.id" class="item-row d-flex align-items-center justify-content-between py-2 border-bottom border-light">
|
||
<div class="item-info flex-grow-1">
|
||
<div class="item-name text-dark fw-semibold small">
|
||
{{ getProductInfo(item).name }}
|
||
</div>
|
||
<div class="item-qty text-muted small">
|
||
{{ getProductInfo(item).quantity }} × {{ formatCurrency(getProductInfo(item).unitPrice) }}
|
||
</div>
|
||
</div>
|
||
<div class="item-total text-end">
|
||
<span class="fw-bold text-dark small">
|
||
{{ formatCurrency(getProductInfo(item).totalPrice) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Footer with Toggle Button -->
|
||
<div
|
||
v-if="transactions.length > 0"
|
||
class="card-footer-toggle d-flex align-items-center justify-content-center mt-3 pt-3 border-top border-light cursor-pointer"
|
||
@click="toggleExpand"
|
||
>
|
||
<span class="text-muted small fw-bold me-2">
|
||
{{ isExpanded ? 'Hide Items' : 'View Items' }}
|
||
</span>
|
||
<i :class="['fas text-muted small', isExpanded ? 'fa-chevron-up' : 'fa-chevron-down']"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.pos-history-card {
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
background: var(--bg-card);
|
||
}
|
||
|
||
.pos-history-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 10px 20px rgba(0,0,0,0.05) !important;
|
||
}
|
||
|
||
:global(.dark-mode) .pos-history-card {
|
||
background: rgba(var(--bg-card-rgb), 0.7);
|
||
backdrop-filter: blur(15px);
|
||
}
|
||
|
||
.badge-soft-success {
|
||
background-color: rgba(40, 167, 69, 0.1);
|
||
color: #28a745;
|
||
}
|
||
|
||
.badge-soft-primary {
|
||
background-color: rgba(0, 123, 255, 0.1);
|
||
color: #007bff;
|
||
}
|
||
|
||
.badge-soft-danger {
|
||
background-color: rgba(220, 53, 69, 0.1);
|
||
color: #dc3545;
|
||
}
|
||
|
||
.badge-soft-secondary {
|
||
background-color: rgba(108, 117, 125, 0.1);
|
||
color: #6c757d;
|
||
}
|
||
|
||
:global(.dark-mode) .badge-soft-success { background-color: rgba(40, 167, 69, 0.2); }
|
||
:global(.dark-mode) .badge-soft-primary { background-color: rgba(0, 123, 255, 0.2); }
|
||
:global(.dark-mode) .badge-soft-danger { background-color: rgba(220, 53, 69, 0.2); }
|
||
:global(.dark-mode) .border-light { border-color: rgba(255,255,255,0.05) !important; }
|
||
|
||
/* Font Awesome standard sizes for visual hierarchy as per dictionary */
|
||
.sm { font-size: 0.875rem; }
|
||
|
||
/* Items section styling */
|
||
.items-section {
|
||
animation: slideDown 0.3s ease-out;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
max-height: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
max-height: 500px;
|
||
}
|
||
}
|
||
|
||
.item-row:last-child {
|
||
border-bottom: none !important;
|
||
}
|
||
|
||
.item-row {
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.item-row:hover {
|
||
background-color: rgba(0, 0, 0, 0.02);
|
||
}
|
||
|
||
:global(.dark-mode) .item-row:hover {
|
||
background-color: rgba(255, 255, 255, 0.02);
|
||
}
|
||
|
||
:global(.dark-mode) .item-name,
|
||
:global(.dark-mode) .item-total span {
|
||
color: var(--text-primary) !important;
|
||
}
|
||
|
||
/* Toggle button styling */
|
||
.card-footer-toggle {
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.cursor-pointer {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.card-footer-toggle:hover {
|
||
background-color: rgba(0, 0, 0, 0.02);
|
||
}
|
||
|
||
:global(.dark-mode) .card-footer-toggle:hover {
|
||
background-color: rgba(255, 255, 255, 0.02);
|
||
}
|
||
|
||
/* Mobile responsiveness */
|
||
@media (max-width: 576px) {
|
||
.item-name {
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.item-qty {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.item-total span {
|
||
font-size: 0.75rem;
|
||
}
|
||
}
|
||
</style>
|