Files
myprojplanet_vite/src/modules/viaggi/components/ride/MyRideCard.vue
2025-12-30 11:36:37 +01:00

313 lines
7.2 KiB
Vue

<template>
<q-card class="my-ride-card" flat bordered @click="$emit('click')">
<!-- Status indicator -->
<div
:class="[
'my-ride-card__status-bar',
`my-ride-card__status-bar--${ride.status}`
]"
></div>
<q-card-section class="my-ride-card__content">
<!-- Header -->
<div class="my-ride-card__header">
<div class="my-ride-card__type">
<q-chip
:color="ride.type === 'offer' ? 'positive' : 'negative'"
text-color="white"
size="sm"
dense
>
{{ ride.type === 'offer' ? '🟢 Offerta' : '🔴 Richiesta' }}
</q-chip>
<q-chip
:color="getStatusColor(ride.status)"
text-color="white"
size="sm"
dense
>
{{ getStatusLabel(ride.status) }}
</q-chip>
</div>
<div class="my-ride-card__role">
<q-icon :name="isDriver ? 'directions_car' : 'person'" size="18px" />
<span>{{ isDriver ? 'Conducente' : 'Passeggero' }}</span>
</div>
</div>
<!-- Route -->
<div class="my-ride-card__route">
<div class="my-ride-card__city my-ride-card__city--start">
<span class="my-ride-card__dot my-ride-card__dot--start"></span>
<span>{{ ride.departure.city }}</span>
</div>
<q-icon name="arrow_forward" size="16px" color="grey" />
<div class="my-ride-card__city my-ride-card__city--end">
<span class="my-ride-card__dot my-ride-card__dot--end"></span>
<span>{{ ride.destination.city }}</span>
</div>
</div>
<!-- Date & Info -->
<div class="my-ride-card__info">
<div class="my-ride-card__date">
<q-icon name="event" size="16px" />
<span>{{ formattedDate }}</span>
</div>
<div class="my-ride-card__time">
<q-icon name="schedule" size="16px" />
<span>{{ formattedTime }}</span>
</div>
<div v-if="ride.type === 'offer'" class="my-ride-card__seats">
<q-icon name="airline_seat_recline_normal" size="16px" />
<span>{{ ride.passengers?.available }}/{{ ride.passengers?.max }}</span>
</div>
</div>
<!-- Pending requests badge -->
<div v-if="pendingRequests > 0" class="my-ride-card__pending">
<q-btn
color="warning"
text-color="dark"
:label="`${pendingRequests} richieste in attesa`"
icon="notifications_active"
size="sm"
unelevated
@click.stop="$emit('manage-requests', ride)"
/>
</div>
<!-- Feedback prompt -->
<div v-if="showFeedbackPrompt" class="my-ride-card__feedback-prompt">
<q-btn
color="amber"
text-color="dark"
label="Lascia una recensione"
icon="star"
size="sm"
unelevated
@click.stop="$emit('leave-feedback', ride)"
/>
</div>
</q-card-section>
<!-- Actions -->
<q-card-actions v-if="showActions" class="my-ride-card__actions">
<q-btn
v-if="canEdit"
flat
dense
no-caps
color="primary"
label="Modifica"
icon="edit"
@click.stop="$emit('edit', ride._id)"
/>
<q-btn
v-if="canComplete"
flat
dense
no-caps
color="positive"
label="Completa"
icon="check_circle"
@click.stop="$emit('complete', ride)"
/>
<q-space />
<q-btn
v-if="canCancel"
flat
dense
no-caps
color="negative"
label="Cancella"
icon="cancel"
@click.stop="$emit('cancel', ride)"
/>
</q-card-actions>
</q-card>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import { useRides } from '../../composables/useRides';
import type { Ride, RideStatus } from '../../types';
export default defineComponent({
name: 'MyRideCard',
props: {
ride: {
type: Object as PropType<Ride>,
required: true
},
isDriver: {
type: Boolean,
default: false
},
pendingRequests: {
type: Number,
default: 0
},
showFeedbackPrompt: {
type: Boolean,
default: false
}
},
emits: ['click', 'edit', 'cancel', 'complete', 'manage-requests', 'leave-feedback'],
setup(props) {
const { formatRideDate, getStatusColor, getStatusLabel } = useRides();
const formattedDate = computed(() => {
const date = new Date(props.ride.departureDate);
return date.toLocaleDateString('it-IT', {
weekday: 'short',
day: 'numeric',
month: 'short'
});
});
const formattedTime = computed(() => {
const date = new Date(props.ride.departureDate);
return date.toLocaleTimeString('it-IT', {
hour: '2-digit',
minute: '2-digit'
});
});
const showActions = computed(() => {
return props.isDriver && ['active', 'full'].includes(props.ride.status);
});
const canEdit = computed(() => {
return props.isDriver && props.ride.status === 'active';
});
const canComplete = computed(() => {
return props.isDriver && ['active', 'full'].includes(props.ride.status);
});
const canCancel = computed(() => {
return ['active', 'full'].includes(props.ride.status);
});
return {
formattedDate,
formattedTime,
showActions,
canEdit,
canComplete,
canCancel,
getStatusColor,
getStatusLabel
};
}
});
</script>
<style lang="scss">
.my-ride-card {
border-radius: 16px !important;
overflow: hidden;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
&__status-bar {
height: 4px;
&--active { background: var(--q-positive); }
&--full { background: var(--q-warning); }
&--completed { background: var(--q-info); }
&--cancelled { background: var(--q-negative); }
&--expired { background: var(--q-grey); }
}
&__content {
padding: 16px;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
&__type {
display: flex;
gap: 8px;
}
&__role {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--q-grey-7);
}
&__route {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
&__city {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
font-size: 16px;
}
&__dot {
width: 10px;
height: 10px;
border-radius: 50%;
&--start { background: #4caf50; }
&--end { background: #f44336; }
}
&__info {
display: flex;
gap: 16px;
font-size: 14px;
color: var(--q-grey-7);
> div {
display: flex;
align-items: center;
gap: 4px;
}
}
&__pending,
&__feedback-prompt {
margin-top: 12px;
}
&__actions {
border-top: 1px solid rgba(0, 0, 0, 0.08);
padding: 8px 16px;
}
}
.body--dark {
.my-ride-card {
&__actions {
border-color: rgba(255, 255, 255, 0.08);
}
}
}
</style>