monacousa-portal/components/GlassDuesCard.vue

229 lines
6.3 KiB
Vue

<template>
<div
:class="[
'glass rounded-glass p-5 transition-all duration-300',
'hover:shadow-glass-lg hover:-translate-y-0.5 group'
]"
>
<!-- Header Section -->
<div class="flex items-start justify-between mb-4">
<!-- Member Info -->
<div class="flex items-center gap-3">
<div class="relative">
<img
:src="member.avatar || '/default-avatar.png'"
:alt="member.name"
class="w-12 h-12 rounded-full object-cover ring-2 ring-white/60"
>
<!-- Country Flag Badge -->
<div
v-if="member.countryCode"
class="absolute -bottom-1 -right-1 w-6 h-6 rounded-full overflow-hidden ring-2 ring-white"
>
<CountryFlag :country="member.countryCode" size="small" />
</div>
</div>
<div>
<h4 class="font-semibold text-gray-800">{{ member.name }}</h4>
<p class="text-sm text-gray-500">Member #{{ member.id }}</p>
</div>
</div>
<!-- Status Badge -->
<span
:class="[
'px-3 py-1 rounded-full text-xs font-medium',
status === 'overdue'
? 'bg-red-50 text-red-700 border border-red-200'
: status === 'upcoming'
? 'bg-amber-50 text-amber-700 border border-amber-200'
: 'bg-green-50 text-green-700 border border-green-200'
]"
>
{{ statusLabel }}
</span>
</div>
<!-- Dues Information -->
<div class="space-y-3 mb-4">
<!-- Amount -->
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600">Amount Due</span>
<span class="text-lg font-bold text-monaco-600">
${{ member.dueAmount }}
</span>
</div>
<!-- Due Date -->
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600">Due Date</span>
<span class="text-sm font-medium text-gray-800">
{{ formatDate(member.dueDate) }}
</span>
</div>
<!-- Days Status -->
<div
v-if="daysUntilDue !== null"
class="flex items-center justify-between"
>
<span class="text-sm text-gray-600">
{{ daysUntilDue > 0 ? 'Days Until Due' : 'Days Overdue' }}
</span>
<span
:class="[
'text-sm font-medium',
daysUntilDue > 7
? 'text-gray-800'
: daysUntilDue > 0
? 'text-amber-600'
: 'text-red-600'
]"
>
{{ Math.abs(daysUntilDue) }} {{ Math.abs(daysUntilDue) === 1 ? 'day' : 'days' }}
</span>
</div>
</div>
<!-- Actions Section -->
<div class="flex gap-2">
<!-- Mark as Paid - Subtle Design -->
<button
@click="$emit('mark-paid', member)"
class="flex-1 px-3 py-2 rounded-lg bg-white/50 border border-monaco-200
text-monaco-600 text-sm font-medium hover:bg-glass-monaco-soft
hover:border-monaco-300 transition-all duration-200
flex items-center justify-center gap-2"
>
<Check class="w-4 h-4" />
Mark Paid
</button>
<!-- More Options Dropdown -->
<div class="relative">
<button
@click="showDropdown = !showDropdown"
class="p-2 rounded-lg bg-white/50 border border-gray-200
text-gray-600 hover:bg-gray-50 hover:border-gray-300
transition-all duration-200"
>
<MoreVertical class="w-4 h-4" />
</button>
<!-- Dropdown Menu -->
<transition name="dropdown">
<div
v-if="showDropdown"
class="absolute right-0 mt-2 w-48 glass-ultra rounded-xl shadow-lg
border border-white/60 overflow-hidden z-50"
>
<button
v-for="action in dropdownActions"
:key="action.label"
@click="handleAction(action)"
class="w-full px-4 py-3 text-left text-sm text-gray-700
hover:bg-glass-monaco-soft hover:text-monaco-600
transition-colors flex items-center gap-3"
>
<component :is="action.icon" class="w-4 h-4" />
{{ action.label }}
</button>
</div>
</transition>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import {
Check,
MoreVertical,
Mail,
Phone,
FileText,
Calendar,
AlertCircle
} from 'lucide-vue-next'
const props = defineProps({
member: {
type: Object,
required: true
},
status: {
type: String,
default: 'upcoming',
validator: (value) => ['overdue', 'upcoming', 'paid'].includes(value)
}
})
const emit = defineEmits(['mark-paid', 'send-reminder', 'view-details', 'schedule-payment'])
const showDropdown = ref(false)
const statusLabel = computed(() => {
const labels = {
overdue: 'Overdue',
upcoming: 'Upcoming',
paid: 'Paid'
}
return labels[props.status] || 'Upcoming'
})
const daysUntilDue = computed(() => {
if (!props.member.dueDate) return null
const today = new Date()
const dueDate = new Date(props.member.dueDate)
const diffTime = dueDate - today
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
})
const dropdownActions = [
{ icon: Mail, label: 'Send Reminder', action: 'send-reminder' },
{ icon: Calendar, label: 'Schedule Payment', action: 'schedule-payment' },
{ icon: FileText, label: 'View Details', action: 'view-details' },
{ icon: Phone, label: 'Contact Member', action: 'contact' },
{ icon: AlertCircle, label: 'Report Issue', action: 'report' }
]
const handleAction = (action) => {
showDropdown.value = false
emit(action.action, props.member)
}
const formatDate = (date) => {
if (!date) return 'Not set'
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
}
// Placeholder for CountryFlag component
const CountryFlag = {
name: 'CountryFlag',
props: ['country', 'size'],
template: '<span></span>'
}
</script>
<style scoped>
/* Dropdown animation */
.dropdown-enter-active,
.dropdown-leave-active {
transition: all 0.2s ease;
}
.dropdown-enter-from,
.dropdown-leave-to {
opacity: 0;
transform: translateY(-10px);
}
</style>