- Parte 3 : Viaggi
- Chat
This commit is contained in:
312
src/modules/viaggi/components/ride/MyRideCard.vue
Normal file
312
src/modules/viaggi/components/ride/MyRideCard.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<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')"
|
||||
/>
|
||||
</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')"
|
||||
/>
|
||||
</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')"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canComplete"
|
||||
flat
|
||||
dense
|
||||
no-caps
|
||||
color="positive"
|
||||
label="Completa"
|
||||
icon="check_circle"
|
||||
@click.stop="$emit('complete')"
|
||||
/>
|
||||
<q-space />
|
||||
<q-btn
|
||||
v-if="canCancel"
|
||||
flat
|
||||
dense
|
||||
no-caps
|
||||
color="negative"
|
||||
label="Cancella"
|
||||
icon="cancel"
|
||||
@click.stop="$emit('cancel')"
|
||||
/>
|
||||
</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.dateTime);
|
||||
return date.toLocaleDateString('it-IT', {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short'
|
||||
});
|
||||
});
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
const date = new Date(props.ride.dateTime);
|
||||
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>
|
||||
Reference in New Issue
Block a user