From 11c17bdd8e9e6025681978b2239b7f69be07ea0d Mon Sep 17 00:00:00 2001 From: Surya Paolo Date: Wed, 24 Dec 2025 00:26:29 +0100 Subject: [PATCH] - Parte 3 : Viaggi - Chat --- _LIMBO/risospiegazione.html | 30 +- .../CMyCardService/CMyCardService.scss | 2 +- src/components/CMyUser/CMyUser.scss | 4 +- .../Riso_Home_Modern/Riso_Home_Modern.ts | 7 +- .../Riso_Home_Modern/Riso_Home_Modern.vue | 8 +- .../Riso_Home_ParteFinale.ts | 26 - .../Riso_Home_ParteFinale.vue | 69 -- src/css/app.scss | 1 - .../components/ride/CityAutocomplete.vue | 376 --------- .../trasporti/components/ride/RideMap.ts | 440 ---------- .../components/ride/RideTypeToggle.scss | 132 --- .../components/ride/RideTypeToggle.ts | 59 -- .../components/ride/RideTypeToggle.vue | 66 -- .../components/ride/WaypointsEditor.ts | 139 --- src/modules/trasporti/composables/useChat.ts | 638 -------------- .../components/chat/ChatInput.scss | 0 .../components/chat/ChatInput.ts | 0 .../components/chat/ChatInput.vue | 0 .../components/chat/ChatList.scss | 0 .../components/chat/ChatList.ts | 0 .../components/chat/ChatList.vue | 0 .../components/chat/ChatWindow.scss | 0 .../components/chat/ChatWindow.ts | 0 .../components/chat/ChatWindow.vue | 0 .../components/chat/MessageBubble.scss | 0 .../components/chat/MessageBubble.ts | 0 .../components/chat/MessageBubble.vue | 0 .../components/chat/index.ts | 0 .../components/feedback/FeedbackCard.scss | 0 .../components/feedback/FeedbackCard.ts | 0 .../components/feedback/FeedbackCard.vue | 0 .../components/feedback/FeedbackForm.scss | 0 .../components/feedback/FeedbackForm.ts | 0 .../components/feedback/FeedbackForm.vue | 0 .../components/feedback/FeedbackList.scss | 0 .../components/feedback/FeedbackList.ts | 0 .../components/feedback/FeedbackList.vue | 0 .../components/feedback/FeedbackSummary.vue | 0 .../components/feedback/index.ts | 0 .../components/ride/CityAutocomplete.vue | 331 ++++++++ .../components/ride/ContribTypeSelector.scss | 0 .../components/ride/ContribTypeSelector.ts | 0 .../components/ride/ContribTypeSelector.vue | 0 .../components/ride/MyRideCard.vue | 0 .../components/ride/PreferencesSelector.scss | 0 .../components/ride/PreferencesSelector.ts | 0 .../components/ride/PreferencesSelector.vue | 0 .../components/ride/RecurrenceSelector.scss | 0 .../components/ride/RecurrenceSelector.ts | 0 .../components/ride/RecurrenceSelector.vue | 0 .../components/ride/RequestCard.vue | 0 .../components/ride/RideCard.scss | 0 .../components/ride/RideCard.ts | 0 .../components/ride/RideCard.vue | 0 .../components/ride/RideFilters.scss | 25 +- .../components/ride/RideFilters.ts | 0 .../components/ride/RideFilters.vue | 6 +- .../components/ride/RideMap.scss | 14 + src/modules/viaggi/components/ride/RideMap.ts | 568 +++++++++++++ .../components/ride/RideMap.vue | 0 .../components/ride/RideTypeToggle.scss | 631 ++++++++++++++ .../viaggi/components/ride/RideTypeToggle.ts | 64 ++ .../viaggi/components/ride/RideTypeToggle.vue | 136 +++ .../components/ride/StarRating.scss | 0 .../components/ride/StarRating.ts | 0 .../components/ride/StarRating.vue | 0 .../components/ride/VehicleSelector.scss | 0 .../components/ride/VehicleSelector.ts | 0 .../components/ride/VehicleSelector.vue | 0 .../components/ride/WaypointsEditor.scss | 0 .../viaggi/components/ride/WaypointsEditor.ts | 193 +++++ .../components/ride/WaypointsEditor.vue | 2 +- .../components/ride/index.ts | 0 .../components/widgets/RideWidget.scss | 0 .../components/widgets/RideWidget.ts | 22 +- .../components/widgets/RideWidget.vue | 2 +- .../composables/index.ts | 2 +- .../composables/useAuth.ts | 0 src/modules/viaggi/composables/useChat.ts | 791 ++++++++++++++++++ .../composables/useCitySuggestions.ts | 2 +- .../composables/useContribTypes.ts | 2 +- .../composables/useDriverProfile.ts | 15 +- .../composables/useFeedback.ts | 20 +- .../viaggi/composables/useGeocoding.ts | 399 +++++++++ .../composables/useGeocoding_OLD.ts} | 0 .../composables/useRealtimeChat.ts | 2 +- .../composables/useRecentCities.ts | 0 .../composables/useRideRequests.ts | 16 +- .../composables/useRides.ts | 18 +- .../pages/ChatListPage.scss | 0 .../pages/ChatListPage.ts | 181 ++-- .../pages/ChatListPage.vue | 2 +- .../{trasporti => viaggi}/pages/ChatPage.scss | 0 .../{trasporti => viaggi}/pages/ChatPage.ts | 21 +- .../{trasporti => viaggi}/pages/ChatPage.vue | 0 .../pages/DriverProfilePage.scss | 0 .../pages/DriverProfilePage.ts | 10 +- .../pages/DriverProfilePage.vue | 0 .../{trasporti => viaggi}/pages/Helppage.vue | 8 +- .../pages/MyRidesPage.scss | 0 .../pages/MyRidesPage.ts | 8 +- .../pages/MyRidesPage.vue | 0 .../pages/Myfeedbackpage.vue | 16 +- .../pages/Requestspage.vue | 22 +- .../pages/RideCreatePage.scss | 0 .../pages/RideCreatePage.ts | 5 +- .../pages/RideCreatePage.vue | 0 .../pages/RideDetailPage.scss | 0 .../pages/RideDetailPage.ts | 10 +- .../pages/RideDetailPage.vue | 0 .../pages/RideSearchPage.scss | 0 .../pages/RideSearchPage.ts | 87 +- .../pages/RideSearchPage.vue | 3 +- .../pages/RidesListPage.scss | 0 .../pages/RidesListPage.ts | 8 +- .../pages/RidesListPage.vue | 4 +- .../pages/Settingspage.vue | 21 +- .../pages/Vehicleeditpage.vue | 12 +- .../pages/Vehiclespage.vue | 12 +- .../{trasporti => viaggi}/pages/index.ts | 0 .../{trasporti => viaggi}/types/index.ts | 2 +- .../types/viaggi.types.ts} | 1 + .../{routesTrasporti.ts => routesViaggi.ts} | 136 +-- src/statics/lang/it.js | 6 +- src/store/Api/index.ts | 2 +- src/store/globalStore.ts | 4 +- 126 files changed, 3580 insertions(+), 2259 deletions(-) delete mode 100644 src/modules/trasporti/components/ride/CityAutocomplete.vue delete mode 100644 src/modules/trasporti/components/ride/RideMap.ts delete mode 100644 src/modules/trasporti/components/ride/RideTypeToggle.scss delete mode 100644 src/modules/trasporti/components/ride/RideTypeToggle.ts delete mode 100644 src/modules/trasporti/components/ride/RideTypeToggle.vue delete mode 100644 src/modules/trasporti/components/ride/WaypointsEditor.ts delete mode 100644 src/modules/trasporti/composables/useChat.ts rename src/modules/{trasporti => viaggi}/components/chat/ChatInput.scss (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatInput.ts (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatInput.vue (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatList.scss (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatList.ts (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatList.vue (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatWindow.scss (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatWindow.ts (100%) rename src/modules/{trasporti => viaggi}/components/chat/ChatWindow.vue (100%) rename src/modules/{trasporti => viaggi}/components/chat/MessageBubble.scss (100%) rename src/modules/{trasporti => viaggi}/components/chat/MessageBubble.ts (100%) rename src/modules/{trasporti => viaggi}/components/chat/MessageBubble.vue (100%) rename src/modules/{trasporti => viaggi}/components/chat/index.ts (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackCard.scss (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackCard.ts (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackCard.vue (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackForm.scss (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackForm.ts (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackForm.vue (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackList.scss (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackList.ts (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackList.vue (100%) rename src/modules/{trasporti => viaggi}/components/feedback/FeedbackSummary.vue (100%) rename src/modules/{trasporti => viaggi}/components/feedback/index.ts (100%) create mode 100644 src/modules/viaggi/components/ride/CityAutocomplete.vue rename src/modules/{trasporti => viaggi}/components/ride/ContribTypeSelector.scss (100%) rename src/modules/{trasporti => viaggi}/components/ride/ContribTypeSelector.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/ContribTypeSelector.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/MyRideCard.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/PreferencesSelector.scss (100%) rename src/modules/{trasporti => viaggi}/components/ride/PreferencesSelector.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/PreferencesSelector.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/RecurrenceSelector.scss (100%) rename src/modules/{trasporti => viaggi}/components/ride/RecurrenceSelector.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/RecurrenceSelector.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/RequestCard.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/RideCard.scss (100%) rename src/modules/{trasporti => viaggi}/components/ride/RideCard.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/RideCard.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/RideFilters.scss (64%) rename src/modules/{trasporti => viaggi}/components/ride/RideFilters.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/RideFilters.vue (98%) rename src/modules/{trasporti => viaggi}/components/ride/RideMap.scss (91%) create mode 100644 src/modules/viaggi/components/ride/RideMap.ts rename src/modules/{trasporti => viaggi}/components/ride/RideMap.vue (100%) create mode 100644 src/modules/viaggi/components/ride/RideTypeToggle.scss create mode 100644 src/modules/viaggi/components/ride/RideTypeToggle.ts create mode 100644 src/modules/viaggi/components/ride/RideTypeToggle.vue rename src/modules/{trasporti => viaggi}/components/ride/StarRating.scss (100%) rename src/modules/{trasporti => viaggi}/components/ride/StarRating.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/StarRating.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/VehicleSelector.scss (100%) rename src/modules/{trasporti => viaggi}/components/ride/VehicleSelector.ts (100%) rename src/modules/{trasporti => viaggi}/components/ride/VehicleSelector.vue (100%) rename src/modules/{trasporti => viaggi}/components/ride/WaypointsEditor.scss (100%) create mode 100644 src/modules/viaggi/components/ride/WaypointsEditor.ts rename src/modules/{trasporti => viaggi}/components/ride/WaypointsEditor.vue (98%) rename src/modules/{trasporti => viaggi}/components/ride/index.ts (100%) rename src/modules/{trasporti => viaggi}/components/widgets/RideWidget.scss (100%) rename src/modules/{trasporti => viaggi}/components/widgets/RideWidget.ts (91%) rename src/modules/{trasporti => viaggi}/components/widgets/RideWidget.vue (99%) rename src/modules/{trasporti => viaggi}/composables/index.ts (86%) rename src/modules/{trasporti => viaggi}/composables/useAuth.ts (100%) create mode 100644 src/modules/viaggi/composables/useChat.ts rename src/modules/{trasporti => viaggi}/composables/useCitySuggestions.ts (97%) rename src/modules/{trasporti => viaggi}/composables/useContribTypes.ts (99%) rename src/modules/{trasporti => viaggi}/composables/useDriverProfile.ts (96%) rename src/modules/{trasporti => viaggi}/composables/useFeedback.ts (95%) create mode 100644 src/modules/viaggi/composables/useGeocoding.ts rename src/modules/{trasporti/composables/useGeocoding.ts => viaggi/composables/useGeocoding_OLD.ts} (100%) rename src/modules/{trasporti => viaggi}/composables/useRealtimeChat.ts (98%) rename src/modules/{trasporti => viaggi}/composables/useRecentCities.ts (100%) rename src/modules/{trasporti => viaggi}/composables/useRideRequests.ts (95%) rename src/modules/{trasporti => viaggi}/composables/useRides.ts (96%) rename src/modules/{trasporti => viaggi}/pages/ChatListPage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/ChatListPage.ts (65%) rename src/modules/{trasporti => viaggi}/pages/ChatListPage.vue (99%) rename src/modules/{trasporti => viaggi}/pages/ChatPage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/ChatPage.ts (95%) rename src/modules/{trasporti => viaggi}/pages/ChatPage.vue (100%) rename src/modules/{trasporti => viaggi}/pages/DriverProfilePage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/DriverProfilePage.ts (93%) rename src/modules/{trasporti => viaggi}/pages/DriverProfilePage.vue (100%) rename src/modules/{trasporti => viaggi}/pages/Helppage.vue (98%) rename src/modules/{trasporti => viaggi}/pages/MyRidesPage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/MyRidesPage.ts (97%) rename src/modules/{trasporti => viaggi}/pages/MyRidesPage.vue (100%) rename src/modules/{trasporti => viaggi}/pages/Myfeedbackpage.vue (97%) rename src/modules/{trasporti => viaggi}/pages/Requestspage.vue (96%) rename src/modules/{trasporti => viaggi}/pages/RideCreatePage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/RideCreatePage.ts (98%) rename src/modules/{trasporti => viaggi}/pages/RideCreatePage.vue (100%) rename src/modules/{trasporti => viaggi}/pages/RideDetailPage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/RideDetailPage.ts (97%) rename src/modules/{trasporti => viaggi}/pages/RideDetailPage.vue (100%) rename src/modules/{trasporti => viaggi}/pages/RideSearchPage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/RideSearchPage.ts (80%) rename src/modules/{trasporti => viaggi}/pages/RideSearchPage.vue (98%) rename src/modules/{trasporti => viaggi}/pages/RidesListPage.scss (100%) rename src/modules/{trasporti => viaggi}/pages/RidesListPage.ts (95%) rename src/modules/{trasporti => viaggi}/pages/RidesListPage.vue (97%) rename src/modules/{trasporti => viaggi}/pages/Settingspage.vue (96%) rename src/modules/{trasporti => viaggi}/pages/Vehicleeditpage.vue (98%) rename src/modules/{trasporti => viaggi}/pages/Vehiclespage.vue (97%) rename src/modules/{trasporti => viaggi}/pages/index.ts (100%) rename src/modules/{trasporti => viaggi}/types/index.ts (58%) rename src/modules/{trasporti/types/trasporti.types.ts => viaggi/types/viaggi.types.ts} (99%) rename src/router/{routesTrasporti.ts => routesViaggi.ts} (78%) diff --git a/_LIMBO/risospiegazione.html b/_LIMBO/risospiegazione.html index a5fb8763..b39c0550 100644 --- a/_LIMBO/risospiegazione.html +++ b/_LIMBO/risospiegazione.html @@ -2141,13 +2141,33 @@
  • Trasporti
  • + +
    +
    + + Viaggi +
    +
    +
    + + Condivisione passaggi +
    +
    + + Trasporto merci e pacchi +
    +
    + + Trasporto animali domestici +
    +
    +
    + +

    Come decidere il prezzo? Usa il riferimento euro (20€ → 20 RIS). Il mercato + ti + darà feedback: se nessuno compra, abbassa; se vendi tutto subito, alza leggermente.

    - - -

    Come decidere il prezzo? Usa il riferimento euro (20€ → 20 RIS). Il mercato ti - darà feedback: se nessuno compra, abbassa; se vendi tutto subito, alza leggermente.

    - diff --git a/src/components/CMyCardService/CMyCardService.scss b/src/components/CMyCardService/CMyCardService.scss index 6a59d20f..7ffab0b7 100644 --- a/src/components/CMyCardService/CMyCardService.scss +++ b/src/components/CMyCardService/CMyCardService.scss @@ -715,7 +715,7 @@ } &.q-btn--unelevated { - background: linear-gradient(135deg, currentColor, color-darken($primary-color, 5%)); + background: linear-gradient(135deg, currentCoalor, color.adjust($primary-color, $lightness: -5%)); } } diff --git a/src/components/CMyUser/CMyUser.scss b/src/components/CMyUser/CMyUser.scss index 306c5a8a..43a9b85b 100755 --- a/src/components/CMyUser/CMyUser.scss +++ b/src/components/CMyUser/CMyUser.scss @@ -110,7 +110,7 @@ } .q-item:hover & { - background: linear-gradient(135deg, color-darken($primary-color, 5%), $primary-color); + background: linear-gradient(135deg, color-adjust($primary-color, lightness, 5%), $primary-color); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -163,7 +163,7 @@ strong { font-weight: 600; - color: color-darken($grey-text, 10%); + color: color.adjust($grey-text, $lightness: -10%); } em { diff --git a/src/components/Riso_Home_Modern/Riso_Home_Modern.ts b/src/components/Riso_Home_Modern/Riso_Home_Modern.ts index d7864a5f..bd6b043a 100644 --- a/src/components/Riso_Home_Modern/Riso_Home_Modern.ts +++ b/src/components/Riso_Home_Modern/Riso_Home_Modern.ts @@ -100,10 +100,9 @@ export default defineComponent({ $router.push('/hosps') }; - const goToTransport = () => { + const goToViaggi = () => { showAnnunciDialog.value = false; - // TODO: navigare a /transport (da creare?) - // $router.push('/transport') + $router.push('/viaggi') }; // Hero Cards @@ -275,7 +274,7 @@ export default defineComponent({ goToGoods, goToServices, goToHospitality, - goToTransport, + goToViaggi, goToWallet, goToEvents, goToProfile, diff --git a/src/components/Riso_Home_Modern/Riso_Home_Modern.vue b/src/components/Riso_Home_Modern/Riso_Home_Modern.vue index 3e158149..e05f0c88 100644 --- a/src/components/Riso_Home_Modern/Riso_Home_Modern.vue +++ b/src/components/Riso_Home_Modern/Riso_Home_Modern.vue @@ -221,14 +221,14 @@ Ospitare · Viaggi · Accoglienza -
    +
    - Trasporti - Condivisione viaggi - ⚠️ (IN ARRIVO...) + Viaggi + Condivisione passaggi e trasporti
    diff --git a/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.ts b/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.ts index 0abd45c7..3906b020 100644 --- a/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.ts +++ b/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.ts @@ -86,28 +86,6 @@ export default defineComponent({ showAnnunciDialog.value = true; }; - // Navigazione Annunci - const goToGoods = () => { - showAnnunciDialog.value = false; - $router.push('/goods') - }; - - const goToServices = () => { - showAnnunciDialog.value = false; - $router.push('/services') - }; - - const goToHospitality = () => { - showAnnunciDialog.value = false; - $router.push('/hosps') - }; - - const goToTransport = () => { - showAnnunciDialog.value = false; - // TODO: navigare a /transport (da creare?) - // $router.push('/transport') - }; - // Hero Cards const goToWallet = () => { // TODO: navigare al portafoglio dettagliato @@ -570,10 +548,6 @@ export default defineComponent({ // Methods openAnnunciDialog, - goToGoods, - goToServices, - goToHospitality, - goToTransport, goToWallet, goToEvents, goToProfile, diff --git a/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.vue b/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.vue index 897288ef..6afdd256 100644 --- a/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.vue +++ b/src/components/Riso_Home_ParteFinale/Riso_Home_ParteFinale.vue @@ -256,75 +256,6 @@ /> - - - - -

    Scegli Categoria

    - -
    - - -
    -
    - - Beni - Autoproduzioni, cibo -
    - -
    - - Servizi - Competenze, aiuti -
    - -
    - - Ospitalità - Ospitare viaggiatori -
    - -
    - - Trasporti - Condivisione viaggi - ⚠️ (IN ARRIVO...) -
    -
    -
    -
    -
    diff --git a/src/css/app.scss b/src/css/app.scss index e28b0f04..0348a606 100755 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -65,7 +65,6 @@ h1 { font-weight: $heading-primary-weight; letter-spacing: $heading-primary-letter-spacing; line-height: $heading-primary-line-height; - color: var(--q-primary, #1976d2); margin-bottom: 1rem; transition: color 0.3s ease; } diff --git a/src/modules/trasporti/components/ride/CityAutocomplete.vue b/src/modules/trasporti/components/ride/CityAutocomplete.vue deleted file mode 100644 index e64f0447..00000000 --- a/src/modules/trasporti/components/ride/CityAutocomplete.vue +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - diff --git a/src/modules/trasporti/components/ride/RideMap.ts b/src/modules/trasporti/components/ride/RideMap.ts deleted file mode 100644 index 1e33a3fc..00000000 --- a/src/modules/trasporti/components/ride/RideMap.ts +++ /dev/null @@ -1,440 +0,0 @@ -import { - ref, - computed, - watch, - onMounted, - onBeforeUnmount, - defineComponent, - PropType, -} from 'vue'; -import L from 'leaflet'; -import 'leaflet/dist/leaflet.css'; -import { useGeocoding } from '../../composables/useGeocoding'; -import type { Location, Waypoint, Coordinates, RouteResult } from '../../types'; - -// Fix per icone Leaflet con Vite -import iconUrl from 'leaflet/dist/images/marker-icon.png'; -import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png'; -import shadowUrl from 'leaflet/dist/images/marker-shadow.png'; - -// @ts-ignore -delete L.Icon.Default.prototype._getIconUrl; -L.Icon.Default.mergeOptions({ - iconUrl, - iconRetinaUrl, - shadowUrl, -}); - -export default defineComponent({ - name: 'RideMap', - - props: { - departure: { - type: Object as PropType, - default: null, - }, - destination: { - type: Object as PropType, - default: null, - }, - waypoints: { - type: Array as PropType, - default: () => [], - }, - mapHeight: { - type: String, - default: '400px', - }, - showRoute: { - type: Boolean, - default: true, - }, - showRouteInfo: { - type: Boolean, - default: true, - }, - showLegend: { - type: Boolean, - default: true, - }, - showFullscreenButton: { - type: Boolean, - default: true, - }, - interactive: { - type: Boolean, - default: true, - }, - autoFit: { - type: Boolean, - default: true, - }, - }, - - emits: ['route-calculated', 'marker-click'], - - setup(props, { emit }) { - const { calculateRoute } = useGeocoding(); - - // State - const mapContainer = ref(null); - const loading = ref(false); - const isFullscreen = ref(false); - const routeInfo = ref(null); - - // Map instance - let map: L.Map | null = null; - let routeLayer: L.Polyline | null = null; - let markersLayer: L.LayerGroup | null = null; - - // Custom icons - const startIcon = L.divIcon({ - className: 'ride-map-marker ride-map-marker--start', - html: '
    🟢
    ', - iconSize: [32, 32], - iconAnchor: [16, 32], - }); - - const endIcon = L.divIcon({ - className: 'ride-map-marker ride-map-marker--end', - html: '
    🔴
    ', - iconSize: [32, 32], - iconAnchor: [16, 32], - }); - - const waypointIcon = L.divIcon({ - className: 'ride-map-marker ride-map-marker--waypoint', - html: '
    📍
    ', - iconSize: [28, 28], - iconAnchor: [14, 28], - }); - - // Computed - const formattedDuration = computed(() => { - if (!routeInfo.value) return ''; - const mins = routeInfo.value.duration; - const hours = Math.floor(mins / 60); - const minutes = mins % 60; - if (hours === 0) return `${minutes} min`; - if (minutes === 0) return `${hours} h`; - return `${hours} h ${minutes} min`; - }); - - // Initialize map - const initMap = () => { - if (!mapContainer.value || map) return; - - map = L.map(mapContainer.value, { - center: [41.9028, 12.4964], // Centro Italia - zoom: 6, - zoomControl: true, - attributionControl: true, - dragging: props.interactive, - touchZoom: props.interactive, - scrollWheelZoom: props.interactive, - doubleClickZoom: props.interactive, - }); - - // Tile layer OpenStreetMap - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: - '© OpenStreetMap contributors', - maxZoom: 19, - }).addTo(map); - - // Layer per markers - markersLayer = L.layerGroup().addTo(map); - - // Update markers iniziale - updateMarkers(); - }; - - // Update markers on map - const updateMarkers = () => { - if (!map || !markersLayer) return; - - markersLayer.clearLayers(); - - // Marker partenza - if (props.departure?.coordinates) { - const marker = L.marker( - [props.departure.coordinates.lat, props.departure.coordinates.lng], - { icon: startIcon } - ); - marker.bindPopup(`Partenza
    ${props.departure.city}`); - marker.on('click', () => emit('marker-click', 'departure', props.departure)); - markersLayer.addLayer(marker); - } - - // Marker waypoints - props.waypoints.forEach((wp, index) => { - if (wp.location?.coordinates) { - const marker = L.marker( - [wp.location.coordinates.lat, wp.location.coordinates.lng], - { icon: waypointIcon } - ); - marker.bindPopup(`Tappa ${index + 1}
    ${wp.location.city}`); - marker.on('click', () => emit('marker-click', 'waypoint', wp, index)); - markersLayer.addLayer(marker); - } - }); - - // Marker destinazione - if (props.destination?.coordinates) { - const marker = L.marker( - [props.destination.coordinates.lat, props.destination.coordinates.lng], - { icon: endIcon } - ); - marker.bindPopup(`Arrivo
    ${props.destination.city}`); - marker.on('click', () => emit('marker-click', 'destination', props.destination)); - markersLayer.addLayer(marker); - } - - // Calcola e mostra percorso - if (props.showRoute) { - calculateAndShowRoute(); - } else if (props.autoFit) { - fitBounds(); - } - }; - - // Calculate and show route - // Calculate and show route - const calculateAndShowRoute = async () => { - if (!map || !props.departure?.coordinates || !props.destination?.coordinates) { - return; - } - - loading.value = true; - - try { - const waypointCoords: Coordinates[] = props.waypoints - .filter((wp) => wp.location?.coordinates) - .sort((a, b) => a.order - b.order) - .map((wp) => wp.location!.coordinates); - - const result = await calculateRoute( - props.departure.coordinates, - props.destination.coordinates, - waypointCoords - ); - - console.log('Route result:', result); // Debug - - if (result) { - // ✅ Convert to proper format for routeInfo - routeInfo.value = { - distance: Math.round((result.distance / 1000) * 10) / 10, // meters to km - duration: Math.round(result.duration / 60), // seconds to minutes - polyline: null, - }; - - emit('route-calculated', routeInfo.value); - - // Remove previous route - if (routeLayer) { - map.removeLayer(routeLayer); - } - - // ✅ Handle GeoJSON geometry (what OSRM returns with geometries=geojson) - if (result.geometry) { - let coordinates: [number, number][]; - - if (result.geometry.type === 'LineString') { - // GeoJSON format: [lng, lat] -> need to swap to [lat, lng] for Leaflet - coordinates = result.geometry.coordinates.map( - (coord: [number, number]) => [coord[1], coord[0]] as [number, number] - ); - } else if (Array.isArray(result.geometry.coordinates)) { - // Already an array of coordinates - coordinates = result.geometry.coordinates.map( - (coord: [number, number]) => [coord[1], coord[0]] as [number, number] - ); - } else { - console.warn('Unknown geometry format:', result.geometry); - coordinates = []; - } - - if (coordinates.length > 0) { - routeLayer = L.polyline(coordinates, { - color: '#1976D2', - weight: 5, - opacity: 0.8, - smoothFactor: 1, - }).addTo(map); - } - } - // ✅ Fallback: Handle encoded polyline (if format changes) - else if (result.polyline) { - const decodedPath = decodePolyline(result.polyline); - routeLayer = L.polyline(decodedPath, { - color: '#1976D2', - weight: 5, - opacity: 0.8, - smoothFactor: 1, - }).addTo(map); - } - // ✅ Last fallback: straight line - else { - console.warn('No route geometry, drawing straight line'); - const points: [number, number][] = [ - [props.departure.coordinates.lat, props.departure.coordinates.lng], - ...waypointCoords.map((c) => [c.lat, c.lng] as [number, number]), - [props.destination.coordinates.lat, props.destination.coordinates.lng], - ]; - routeLayer = L.polyline(points, { - color: '#1976D2', - weight: 5, - opacity: 0.6, - dashArray: '10, 10', - }).addTo(map); - } - - if (props.autoFit) { - fitBounds(); - } - } - } catch (error) { - console.error('Errore calcolo percorso:', error); - } finally { - loading.value = false; - } - }; - - // Decode Google Polyline format - const decodePolyline = (encoded: string): [number, number][] => { - const points: [number, number][] = []; - let index = 0; - let lat = 0; - let lng = 0; - - while (index < encoded.length) { - let shift = 0; - let result = 0; - let byte: number; - - do { - byte = encoded.charCodeAt(index++) - 63; - result |= (byte & 0x1f) << shift; - shift += 5; - } while (byte >= 0x20); - - const deltaLat = result & 1 ? ~(result >> 1) : result >> 1; - lat += deltaLat; - - shift = 0; - result = 0; - - do { - byte = encoded.charCodeAt(index++) - 63; - result |= (byte & 0x1f) << shift; - shift += 5; - } while (byte >= 0x20); - - const deltaLng = result & 1 ? ~(result >> 1) : result >> 1; - lng += deltaLng; - - points.push([lat / 1e5, lng / 1e5]); - } - - return points; - }; - - // Fit map to show all markers - const fitBounds = () => { - if (!map) return; - - const bounds: L.LatLngBoundsExpression = []; - - if (props.departure?.coordinates) { - bounds.push([props.departure.coordinates.lat, props.departure.coordinates.lng]); - } - - props.waypoints.forEach((wp) => { - if (wp.location?.coordinates) { - bounds.push([wp.location.coordinates.lat, wp.location.coordinates.lng]); - } - }); - - if (props.destination?.coordinates) { - bounds.push([ - props.destination.coordinates.lat, - props.destination.coordinates.lng, - ]); - } - - if (bounds.length > 0) { - map.fitBounds(bounds as L.LatLngBoundsExpression, { - padding: [50, 50], - maxZoom: 14, - }); - } - }; - - // Center on user location - const centerOnUser = () => { - if (!map) return; - - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - (position) => { - map!.setView([position.coords.latitude, position.coords.longitude], 13); - }, - (error) => { - console.error('Errore geolocalizzazione:', error); - } - ); - } - }; - - // Toggle fullscreen - const toggleFullscreen = () => { - if (!mapContainer.value) return; - - if (!document.fullscreenElement) { - mapContainer.value.parentElement?.requestFullscreen(); - isFullscreen.value = true; - } else { - document.exitFullscreen(); - isFullscreen.value = false; - } - - // Resize map after fullscreen change - setTimeout(() => { - map?.invalidateSize(); - }, 100); - }; - - // Watch for changes - watch( - [() => props.departure, () => props.destination, () => props.waypoints], - () => { - updateMarkers(); - }, - { deep: true } - ); - - // Lifecycle - onMounted(() => { - initMap(); - }); - - onBeforeUnmount(() => { - if (map) { - map.remove(); - map = null; - } - }); - - return { - mapContainer, - loading, - isFullscreen, - routeInfo, - formattedDuration, - fitBounds, - centerOnUser, - toggleFullscreen, - }; - }, -}); diff --git a/src/modules/trasporti/components/ride/RideTypeToggle.scss b/src/modules/trasporti/components/ride/RideTypeToggle.scss deleted file mode 100644 index 6ee3d87d..00000000 --- a/src/modules/trasporti/components/ride/RideTypeToggle.scss +++ /dev/null @@ -1,132 +0,0 @@ -.ride-type-toggle { - width: 100%; - - &--vertical { - .ride-type-toggle__buttons { - flex-direction: column; - - .q-btn { - margin-bottom: 8px; - - &:last-child { - margin-bottom: 0; - } - } - } - } - - &__buttons { - width: 100%; - border-radius: 16px; - background: rgba(0, 0, 0, 0.05); - padding: 4px; - - .q-btn { - flex: 1; - padding: 12px 16px; - border-radius: 12px !important; - transition: all 0.3s ease; - - &.q-btn--active { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - } - - &.selected-offer .q-btn--active { - background: linear-gradient(135deg, #4caf50, #66bb6a) !important; - color: white !important; - } - - &.selected-request .q-btn--active { - background: linear-gradient(135deg, #f44336, #ef5350) !important; - color: white !important; - } - } -} - -.ride-type-option { - display: flex; - align-items: center; - gap: 12px; - - &__icon { - font-size: 24px; - } - - &__content { - display: flex; - flex-direction: column; - align-items: flex-start; - text-align: left; - } - - &__label { - font-weight: 600; - font-size: 14px; - } - - &__desc { - font-size: 11px; - opacity: 0.8; - margin-top: 2px; - } -} - -// Card Mode -.ride-type-cards { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 12px; -} - -.ride-type-card { - cursor: pointer; - transition: all 0.3s ease; - border-radius: 16px !important; - - &:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - } - - &--selected { - border-color: var(--q-primary) !important; - border-width: 2px; - background: rgba(var(--q-primary-rgb), 0.05); - - .ride-type-card__icon { - transform: scale(1.2); - } - } - - &__icon { - font-size: 48px; - margin-bottom: 8px; - transition: transform 0.3s ease; - } - - &__label { - font-weight: 600; - font-size: 16px; - color: var(--q-dark); - } - - &__desc { - font-size: 12px; - color: var(--q-grey); - margin-top: 4px; - } -} - -// Dark mode -.body--dark { - .ride-type-toggle__buttons { - background: rgba(255, 255, 255, 0.1); - } - - .ride-type-card { - &__label { - color: #fff; - } - } -} \ No newline at end of file diff --git a/src/modules/trasporti/components/ride/RideTypeToggle.ts b/src/modules/trasporti/components/ride/RideTypeToggle.ts deleted file mode 100644 index 5bb92a0d..00000000 --- a/src/modules/trasporti/components/ride/RideTypeToggle.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { computed, defineComponent, PropType } from 'vue'; -import type { RideType } from '../../types'; - -export default defineComponent({ - name: 'RideTypeToggle', - - props: { - modelValue: { - type: String as PropType, - default: 'offer' - }, - vertical: { - type: Boolean, - default: false - }, - showDescription: { - type: Boolean, - default: true - }, - cardMode: { - type: Boolean, - default: false - }, - disabled: { - type: Boolean, - default: false - } - }, - - emits: ['update:modelValue', 'change'], - - setup(props, { emit }) { - const selectedType = computed({ - get: () => props.modelValue, - set: (value: RideType) => { - emit('update:modelValue', value); - emit('change', value); - } - }); - - const typeOptions = [ - { - label: 'Offro Passaggio', - value: 'offer' as RideType, - slot: 'offer' - }, - { - label: 'Cerco Passaggio', - value: 'request' as RideType, - slot: 'request' - } - ]; - - return { - selectedType, - typeOptions - }; - } -}); diff --git a/src/modules/trasporti/components/ride/RideTypeToggle.vue b/src/modules/trasporti/components/ride/RideTypeToggle.vue deleted file mode 100644 index 2f471c17..00000000 --- a/src/modules/trasporti/components/ride/RideTypeToggle.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - + + diff --git a/src/modules/trasporti/components/ride/ContribTypeSelector.scss b/src/modules/viaggi/components/ride/ContribTypeSelector.scss similarity index 100% rename from src/modules/trasporti/components/ride/ContribTypeSelector.scss rename to src/modules/viaggi/components/ride/ContribTypeSelector.scss diff --git a/src/modules/trasporti/components/ride/ContribTypeSelector.ts b/src/modules/viaggi/components/ride/ContribTypeSelector.ts similarity index 100% rename from src/modules/trasporti/components/ride/ContribTypeSelector.ts rename to src/modules/viaggi/components/ride/ContribTypeSelector.ts diff --git a/src/modules/trasporti/components/ride/ContribTypeSelector.vue b/src/modules/viaggi/components/ride/ContribTypeSelector.vue similarity index 100% rename from src/modules/trasporti/components/ride/ContribTypeSelector.vue rename to src/modules/viaggi/components/ride/ContribTypeSelector.vue diff --git a/src/modules/trasporti/components/ride/MyRideCard.vue b/src/modules/viaggi/components/ride/MyRideCard.vue similarity index 100% rename from src/modules/trasporti/components/ride/MyRideCard.vue rename to src/modules/viaggi/components/ride/MyRideCard.vue diff --git a/src/modules/trasporti/components/ride/PreferencesSelector.scss b/src/modules/viaggi/components/ride/PreferencesSelector.scss similarity index 100% rename from src/modules/trasporti/components/ride/PreferencesSelector.scss rename to src/modules/viaggi/components/ride/PreferencesSelector.scss diff --git a/src/modules/trasporti/components/ride/PreferencesSelector.ts b/src/modules/viaggi/components/ride/PreferencesSelector.ts similarity index 100% rename from src/modules/trasporti/components/ride/PreferencesSelector.ts rename to src/modules/viaggi/components/ride/PreferencesSelector.ts diff --git a/src/modules/trasporti/components/ride/PreferencesSelector.vue b/src/modules/viaggi/components/ride/PreferencesSelector.vue similarity index 100% rename from src/modules/trasporti/components/ride/PreferencesSelector.vue rename to src/modules/viaggi/components/ride/PreferencesSelector.vue diff --git a/src/modules/trasporti/components/ride/RecurrenceSelector.scss b/src/modules/viaggi/components/ride/RecurrenceSelector.scss similarity index 100% rename from src/modules/trasporti/components/ride/RecurrenceSelector.scss rename to src/modules/viaggi/components/ride/RecurrenceSelector.scss diff --git a/src/modules/trasporti/components/ride/RecurrenceSelector.ts b/src/modules/viaggi/components/ride/RecurrenceSelector.ts similarity index 100% rename from src/modules/trasporti/components/ride/RecurrenceSelector.ts rename to src/modules/viaggi/components/ride/RecurrenceSelector.ts diff --git a/src/modules/trasporti/components/ride/RecurrenceSelector.vue b/src/modules/viaggi/components/ride/RecurrenceSelector.vue similarity index 100% rename from src/modules/trasporti/components/ride/RecurrenceSelector.vue rename to src/modules/viaggi/components/ride/RecurrenceSelector.vue diff --git a/src/modules/trasporti/components/ride/RequestCard.vue b/src/modules/viaggi/components/ride/RequestCard.vue similarity index 100% rename from src/modules/trasporti/components/ride/RequestCard.vue rename to src/modules/viaggi/components/ride/RequestCard.vue diff --git a/src/modules/trasporti/components/ride/RideCard.scss b/src/modules/viaggi/components/ride/RideCard.scss similarity index 100% rename from src/modules/trasporti/components/ride/RideCard.scss rename to src/modules/viaggi/components/ride/RideCard.scss diff --git a/src/modules/trasporti/components/ride/RideCard.ts b/src/modules/viaggi/components/ride/RideCard.ts similarity index 100% rename from src/modules/trasporti/components/ride/RideCard.ts rename to src/modules/viaggi/components/ride/RideCard.ts diff --git a/src/modules/trasporti/components/ride/RideCard.vue b/src/modules/viaggi/components/ride/RideCard.vue similarity index 100% rename from src/modules/trasporti/components/ride/RideCard.vue rename to src/modules/viaggi/components/ride/RideCard.vue diff --git a/src/modules/trasporti/components/ride/RideFilters.scss b/src/modules/viaggi/components/ride/RideFilters.scss similarity index 64% rename from src/modules/trasporti/components/ride/RideFilters.scss rename to src/modules/viaggi/components/ride/RideFilters.scss index 5ffa0041..0f19103c 100644 --- a/src/modules/trasporti/components/ride/RideFilters.scss +++ b/src/modules/viaggi/components/ride/RideFilters.scss @@ -7,6 +7,18 @@ .q-card__section { padding: 12px 16px; } + + // FIX: Assicura che gli input abbiano spazio per le icone + .q-field { + &__prepend { + padding-right: 8px; + padding-left: 8px; + } + + &__control { + padding-left: 0; + } + } } &__full { @@ -15,6 +27,12 @@ &__date-input { max-width: 150px; + + // FIX: Anche per il date input + .q-field__prepend { + padding-right: 8px; + padding-left: 8px; + } } &__section { @@ -48,13 +66,16 @@ .q-card__section { flex-wrap: wrap; - .col { - min-width: 100%; + // FIX: Usa flex-basis invece di min-width per evitare problemi + > .col { + flex: 1 1 100%; + min-width: 0; // Previene overflow } .ride-filters__date-input { max-width: 100%; width: 100%; + flex: 1 1 100%; } } } diff --git a/src/modules/trasporti/components/ride/RideFilters.ts b/src/modules/viaggi/components/ride/RideFilters.ts similarity index 100% rename from src/modules/trasporti/components/ride/RideFilters.ts rename to src/modules/viaggi/components/ride/RideFilters.ts diff --git a/src/modules/trasporti/components/ride/RideFilters.vue b/src/modules/viaggi/components/ride/RideFilters.vue similarity index 98% rename from src/modules/trasporti/components/ride/RideFilters.vue rename to src/modules/viaggi/components/ride/RideFilters.vue index 75abcec4..38f15d18 100644 --- a/src/modules/trasporti/components/ride/RideFilters.vue +++ b/src/modules/viaggi/components/ride/RideFilters.vue @@ -5,7 +5,7 @@ - + , + default: null, + }, + destination: { + type: Object as PropType, + default: null, + }, + waypoints: { + type: Array as PropType, + default: () => [], + }, + mapHeight: { + type: String, + default: '400px', + }, + showRoute: { + type: Boolean, + default: true, + }, + showRouteInfo: { + type: Boolean, + default: true, + }, + showLegend: { + type: Boolean, + default: true, + }, + showFullscreenButton: { + type: Boolean, + default: true, + }, + interactive: { + type: Boolean, + default: true, + }, + autoFit: { + type: Boolean, + default: true, + }, + }, + + emits: ['route-calculated', 'marker-click'], + + setup(props, { emit }) { + const { calculateRoute } = useGeocoding(); + + // State + const mapContainer = ref(null); + const loading = ref(false); + const isFullscreen = ref(false); + const routeInfo = shallowRef(null); + + // NON reactive - evita problemi con Leaflet + let map: L.Map | null = null; + let routeLayer: L.Polyline | null = null; + let markersLayer: L.LayerGroup | null = null; + + // Flags per prevenire loop + let isUpdating = false; + let lastRouteKey = ''; + + // Custom icons + const createIcon = (emoji: string, size: number = 32) => { + return L.divIcon({ + className: 'ride-map-marker', + html: `
    ${emoji}
    `, + iconSize: [size, size], + iconAnchor: [size / 2, size], + popupAnchor: [0, -size], + }); + }; + + const startIcon = createIcon('🟢', 36); + const endIcon = createIcon('🔴', 36); + const waypointIcon = createIcon('📍', 30); + + // Computed + const formattedDuration = computed(() => { + if (!routeInfo.value) return ''; + return routeInfo.value.durationFormatted; + }); + + // Helper: format duration + const formatDuration = (seconds: number): string => { + const totalMinutes = Math.round(seconds / 60); + const hours = Math.floor(totalMinutes / 60); + const mins = totalMinutes % 60; + if (hours === 0) return `${mins} min`; + if (mins === 0) return `${hours} h`; + return `${hours} h ${mins} min`; + }; + + // Helper: genera chiave unica per il percorso + const getRouteKey = (): string => { + const dep = props.departure?.coordinates; + const dest = props.destination?.coordinates; + if (!dep || !dest) return ''; + + const wps = props.waypoints + .filter(wp => wp.location?.coordinates) + .map(wp => `${wp.location!.coordinates.lat.toFixed(4)},${wp.location!.coordinates.lng.toFixed(4)}`) + .join('|'); + + return `${dep.lat.toFixed(4)},${dep.lng.toFixed(4)}-${wps}-${dest.lat.toFixed(4)},${dest.lng.toFixed(4)}`; + }; + + // Initialize map + const initMap = () => { + if (!mapContainer.value || map) return; + + console.log('🗺️ Initializing map...'); + + map = L.map(mapContainer.value, { + center: [41.9028, 12.4964], + zoom: 6, + zoomControl: true, + attributionControl: true, + dragging: props.interactive, + touchZoom: props.interactive, + scrollWheelZoom: props.interactive, + doubleClickZoom: props.interactive, + }); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', + maxZoom: 19, + }).addTo(map); + + markersLayer = L.layerGroup().addTo(map); + + // Update iniziale + updateMapContent(); + }; + + // Separato updateMarkers da calculateRoute + const updateMapContent = () => { + if (isUpdating) return; + isUpdating = true; + + console.log('📍 Updating map content...', { + hasDeparture: !!props.departure?.coordinates, + hasDestination: !!props.destination?.coordinates, + waypointsCount: props.waypoints.length + }); + + try { + updateMarkersOnly(); + + const currentKey = getRouteKey(); + if (props.showRoute && currentKey && currentKey !== lastRouteKey) { + console.log('🛣️ Route key changed, calculating new route...'); + lastRouteKey = currentKey; + setTimeout(() => { + calculateAndShowRoute(); + }, 0); + } else if (props.autoFit) { + fitBounds(); + } + } finally { + isUpdating = false; + } + }; + + // Solo aggiornamento markers + const updateMarkersOnly = () => { + if (!map || !markersLayer) return; + + markersLayer.clearLayers(); + + if (props.departure?.coordinates) { + const { lat, lng } = props.departure.coordinates; + const marker = L.marker([lat, lng], { icon: startIcon }); + marker.bindPopup(`Partenza
    ${props.departure.city}`); + marker.on('click', () => emit('marker-click', 'departure', props.departure)); + markersLayer.addLayer(marker); + console.log('✅ Added departure marker:', lat, lng); + } + + props.waypoints.forEach((wp, index) => { + if (wp.location?.coordinates) { + const { lat, lng } = wp.location.coordinates; + const marker = L.marker([lat, lng], { icon: waypointIcon }); + marker.bindPopup(`Tappa ${index + 1}
    ${wp.location.city}`); + marker.on('click', () => emit('marker-click', 'waypoint', wp, index)); + markersLayer.addLayer(marker); + } + }); + + if (props.destination?.coordinates) { + const { lat, lng } = props.destination.coordinates; + const marker = L.marker([lat, lng], { icon: endIcon }); + marker.bindPopup(`Arrivo
    ${props.destination.city}`); + marker.on('click', () => emit('marker-click', 'destination', props.destination)); + markersLayer.addLayer(marker); + console.log('✅ Added destination marker:', lat, lng); + } + }; + + // Calculate and show route + const calculateAndShowRoute = async () => { + if (!map) { + console.warn('❌ Map not initialized'); + return; + } + if (!props.departure?.coordinates || !props.destination?.coordinates) { + console.warn('❌ Missing coordinates'); + return; + } + + loading.value = true; + console.log('🚗 Calculating route...'); + + try { + const waypointCoords: Coordinates[] = props.waypoints + .filter((wp) => wp.location?.coordinates) + .sort((a, b) => a.order - b.order) + .map((wp) => wp.location!.coordinates); + + console.log('📍 Route params:', { + from: props.departure.coordinates, + to: props.destination.coordinates, + waypoints: waypointCoords + }); + + const result = await calculateRoute( + props.departure.coordinates, + props.destination.coordinates, + waypointCoords.length > 0 ? waypointCoords : undefined + ); + + // console.log('📦 Route API result:', JSON.stringify(result, null, 2)); + + if (!result) { + console.warn('❌ No route result, drawing fallback line'); + drawFallbackLine(waypointCoords); + return; + } + + // Estrai distance e duration dal risultato + // Il backend restituisce: { distance: km, duration: minuti, geometry: {...} } + const distanceKm = result.distance || 0; + const durationMinutes = result.duration || 0; + + const newRouteInfo: LocalRouteInfo = { + distance: Math.round(distanceKm * 10) / 10, + duration: durationMinutes, + durationFormatted: formatDuration(durationMinutes * 60), // Converti in secondi per formatDuration + }; + + console.log('📊 Route info:', newRouteInfo); + + routeInfo.value = newRouteInfo; + emit('route-calculated', { ...newRouteInfo }); + + // Rimuovi percorso precedente + if (routeLayer && map) { + map.removeLayer(routeLayer); + routeLayer = null; + } + + // Disegna il percorso + const coordinates = extractCoordinates(result); + console.log(`📐 Extracted ${coordinates.length} coordinates`); + + if (coordinates.length > 0) { + routeLayer = L.polyline(coordinates, { + color: '#1976D2', + weight: 5, + opacity: 0.8, + smoothFactor: 1, + lineJoin: 'round', + lineCap: 'round', + }).addTo(map); + + console.log('✅ Route drawn successfully'); + + if (props.autoFit) { + fitBounds(); + } + } else { + console.warn('❌ No coordinates extracted, drawing fallback'); + drawFallbackLine(waypointCoords); + } + + } catch (error) { + console.error('❌ Errore calcolo percorso:', error); + drawFallbackLine([]); + } finally { + loading.value = false; + } + }; + + // Extract coordinates from various API response formats + const extractCoordinates = (result: any): L.LatLngTuple[] => { + console.log('🔍 Extracting coordinates from:', Object.keys(result)); + + // Formato 1: geometry.coordinates (GeoJSON LineString) + if (result.geometry?.coordinates && Array.isArray(result.geometry.coordinates)) { + console.log('📐 Format: GeoJSON LineString'); + return result.geometry.coordinates.map( + (coord: number[]) => [coord[1], coord[0]] as L.LatLngTuple // [lng, lat] -> [lat, lng] + ); + } + + // Formato 2: geometry è una stringa (polyline encoded) + if (typeof result.geometry === 'string') { + console.log('📐 Format: Encoded polyline string'); + return decodePolyline(result.geometry); + } + + // Formato 3: polyline separato + if (typeof result.polyline === 'string' && result.polyline.length > 0) { + console.log('📐 Format: Separate polyline field'); + return decodePolyline(result.polyline); + } + + // Formato 4: coordinates array diretto + if (Array.isArray(result.coordinates)) { + console.log('📐 Format: Direct coordinates array'); + return result.coordinates.map((coord: number[]) => { + if (Math.abs(coord[0]) <= 90) { + return [coord[0], coord[1]] as L.LatLngTuple; + } + return [coord[1], coord[0]] as L.LatLngTuple; + }); + } + + // Formato 5: routes array (OSRM format) + if (result.routes?.[0]?.geometry) { + const geom = result.routes[0].geometry; + console.log('📐 Format: OSRM routes array'); + + if (typeof geom === 'string') { + return decodePolyline(geom); + } + if (geom.coordinates) { + return geom.coordinates.map( + (coord: number[]) => [coord[1], coord[0]] as L.LatLngTuple + ); + } + } + + // Formato 6: segments con steps (OpenRouteService format) + if (result.segments && Array.isArray(result.segments)) { + console.log('📐 Format: ORS segments'); + // Se abbiamo segments ma non geometry, probabilmente manca qualcosa + console.warn('⚠️ Segments found but no geometry'); + } + + console.warn('❌ Could not extract coordinates, keys:', Object.keys(result)); + return []; + }; + + // Draw fallback straight line + const drawFallbackLine = (waypointCoords: Coordinates[]) => { + if (!map || !props.departure?.coordinates || !props.destination?.coordinates) return; + + console.log('📏 Drawing fallback straight line'); + + if (routeLayer) { + map.removeLayer(routeLayer); + routeLayer = null; + } + + const points: L.LatLngTuple[] = [ + [props.departure.coordinates.lat, props.departure.coordinates.lng], + ...waypointCoords.map((c) => [c.lat, c.lng] as L.LatLngTuple), + [props.destination.coordinates.lat, props.destination.coordinates.lng], + ]; + + routeLayer = L.polyline(points, { + color: '#FF9800', + weight: 3, + opacity: 0.7, + dashArray: '10, 10', + }).addTo(map); + + if (props.autoFit) { + fitBounds(); + } + }; + + // Decode Google/OSRM polyline format + const decodePolyline = (encoded: string): L.LatLngTuple[] => { + const points: L.LatLngTuple[] = []; + let index = 0; + let lat = 0; + let lng = 0; + + while (index < encoded.length) { + let shift = 0; + let result = 0; + let byte: number; + + do { + byte = encoded.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + lat += result & 1 ? ~(result >> 1) : result >> 1; + + shift = 0; + result = 0; + + do { + byte = encoded.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + lng += result & 1 ? ~(result >> 1) : result >> 1; + + points.push([lat / 1e5, lng / 1e5]); + } + + console.log(`📐 Decoded ${points.length} points from polyline`); + return points; + }; + + // Fit bounds + const fitBounds = () => { + if (!map) return; + + const allPoints: L.LatLngTuple[] = []; + + if (props.departure?.coordinates) { + allPoints.push([props.departure.coordinates.lat, props.departure.coordinates.lng]); + } + + props.waypoints.forEach((wp) => { + if (wp.location?.coordinates) { + allPoints.push([wp.location.coordinates.lat, wp.location.coordinates.lng]); + } + }); + + if (props.destination?.coordinates) { + allPoints.push([props.destination.coordinates.lat, props.destination.coordinates.lng]); + } + + if (allPoints.length === 0) return; + + if (allPoints.length === 1) { + map.setView(allPoints[0], 13); + } else { + map.fitBounds(L.latLngBounds(allPoints), { + padding: [50, 50], + maxZoom: 14, + animate: true, + }); + } + }; + + // Center on user + const centerOnUser = () => { + if (!map || !navigator.geolocation) return; + + navigator.geolocation.getCurrentPosition( + (pos) => map!.setView([pos.coords.latitude, pos.coords.longitude], 13), + (err) => console.error('Geolocation error:', err) + ); + }; + + // Toggle fullscreen + const toggleFullscreen = () => { + const container = mapContainer.value?.parentElement; + if (!container) return; + + if (!document.fullscreenElement) { + container.requestFullscreen().then(() => { + isFullscreen.value = true; + setTimeout(() => map?.invalidateSize(), 100); + }); + } else { + document.exitFullscreen().then(() => { + isFullscreen.value = false; + setTimeout(() => map?.invalidateSize(), 100); + }); + } + }; + + // Watch con debounce + let watchTimeout: ReturnType | null = null; + + watch( + () => [ + props.departure?.coordinates?.lat, + props.departure?.coordinates?.lng, + props.destination?.coordinates?.lat, + props.destination?.coordinates?.lng, + props.waypoints.length, + props.waypoints.map(w => w.location?.coordinates?.lat).join(','), + ], + () => { + if (watchTimeout) { + clearTimeout(watchTimeout); + } + watchTimeout = setTimeout(() => { + updateMapContent(); + }, 200); + }, + { flush: 'post' } + ); + + // Lifecycle + onMounted(() => { + setTimeout(initMap, 100); + }); + + onBeforeUnmount(() => { + if (watchTimeout) clearTimeout(watchTimeout); + if (map) { + map.remove(); + map = null; + } + }); + + return { + mapContainer, + loading, + isFullscreen, + routeInfo, + formattedDuration, + fitBounds, + centerOnUser, + toggleFullscreen, + }; + }, +}); diff --git a/src/modules/trasporti/components/ride/RideMap.vue b/src/modules/viaggi/components/ride/RideMap.vue similarity index 100% rename from src/modules/trasporti/components/ride/RideMap.vue rename to src/modules/viaggi/components/ride/RideMap.vue diff --git a/src/modules/viaggi/components/ride/RideTypeToggle.scss b/src/modules/viaggi/components/ride/RideTypeToggle.scss new file mode 100644 index 00000000..8f6f7062 --- /dev/null +++ b/src/modules/viaggi/components/ride/RideTypeToggle.scss @@ -0,0 +1,631 @@ +// ================================ +// Variables +// ================================ +$offer-color: #10b981; +$offer-light: #d1fae5; +$offer-gradient: linear-gradient(135deg, #10b981, #059669); + +$request-color: #ef4444; +$request-light: #fee2e2; +$request-gradient: linear-gradient(135deg, #ef4444, #dc2626); +$transition-fast: 0.2s ease; +$transition-smooth: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + +// ================================ +// Base +// ================================ +.ride-type-toggle { + width: 100%; +} + +// ================================ +// VARIANT: Cards +// ================================ +.toggle-cards { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; // 16px → 1rem +} + +.toggle-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 1.5rem 1rem; // 24px → 1.5rem, 16px → 1rem + background: #fff; + border-radius: 1.25rem; // 20px → 1.25rem + cursor: pointer; + transition: all $transition-smooth; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + inset: 0; + border: 2px solid #e5e7eb; + border-radius: 1.25rem; + transition: all $transition-smooth; + } + + &:hover:not(&--disabled) { + transform: translateY(-0.25rem); // -4px → -0.25rem + box-shadow: 0 0.75rem 1.5rem rgba(0, 0, 0, 0.1); // 12px → 0.75rem, 24px → 1.5rem + } + + &--disabled { + opacity: 0.5; + cursor: not-allowed; + } + + // Offer styles + &--offer { + &.toggle-card--selected { + &::before { + border-color: $offer-color; + border-width: 2px; + } + + .toggle-card__icon-wrapper { + .q-icon { + color: $offer-color; + } + } + + .toggle-card__icon-bg { + background: $offer-light; + transform: scale(1); + } + + .toggle-card__check { + background: $offer-gradient; + } + } + + &:hover:not(.toggle-card--disabled) { + .toggle-card__icon-bg { + background: $offer-light; + transform: scale(1); + } + } + } + + // Request styles + &--request { + &.toggle-card--selected { + &::before { + border-color: $request-color; + border-width: 2px; + } + + .toggle-card__icon-wrapper { + .q-icon { + color: $request-color; + } + } + + .toggle-card__icon-bg { + background: $request-light; + transform: scale(1); + } + + .toggle-card__check { + background: $request-gradient; + } + } + + &:hover:not(.toggle-card--disabled) { + .toggle-card__icon-bg { + background: $request-light; + transform: scale(1); + } + } + } + + &__icon-wrapper { + position: relative; + width: 4rem; // 64px → 4rem + height: 4rem; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1rem; // 16px → 1rem + + .q-icon { + position: relative; + z-index: 1; + color: #6b7280; + transition: color $transition-smooth; + } + } + + &__icon-bg { + position: absolute; + inset: 0; + border-radius: 1rem; // 16px → 1rem + background: #f3f4f6; + transform: scale(0.9); + transition: all $transition-smooth; + } + + &__content { + text-align: center; + } + + &__label { + display: block; + font-size: 1.2rem; // 16px → 1rem + font-weight: 600; + color: #111827; + margin-bottom: 0.25rem; // 4px → 0.25rem + } + + &__desc { + display: block; + font-size: 1.1rem; // 13px → 0.8125rem + color: #6b7280; + } + + &__check { + position: absolute; + top: 0.75rem; // 12px → 0.75rem + right: 0.75rem; + width: 1.5rem; // 24px → 1.5rem + height: 1.5rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.2); // 2px → 0.125rem, 8px → 0.5rem + } +} + +// Scale transition +.scale-enter-active, +.scale-leave-active { + transition: all 0.2s ease; +} + +.scale-enter-from, +.scale-leave-to { + transform: scale(0); + opacity: 0; +} + +// ================================ +// VARIANT: Pills +// ================================ +.toggle-pills { + &__track { + position: relative; + display: flex; + background: #f3f4f6; + border-radius: 1rem; // 16px → 1rem + padding: 0.375rem; // 6px → 0.375rem + } + + &__slider { + position: absolute; + top: 0.375rem; + bottom: 0.375rem; + width: calc(50% - 0.375rem); + border-radius: 0.75rem; // 12px → 0.75rem + transition: all $transition-smooth; + box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.15); + + &--offer { + left: 0.375rem; + background: $offer-gradient; + } + + &--request { + left: calc(50%); + background: $request-gradient; + } + } + + &__btn { + flex: 1; + position: relative; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; // 8px → 0.5rem + padding: 0.875rem 1.25rem; // 14px → 0.875rem, 20px → 1.25rem + border: none; + background: transparent; + font-size: 1rem; // 14px → 0.875rem + font-weight: 600; + color: #6b7280; + cursor: pointer; + transition: color $transition-fast; + + &--active { + color: white; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } + } +} + +// ================================ +// VARIANT: Minimal +// ================================ +.toggle-minimal { + display: flex; + gap: 0.75rem; // 12px → 0.75rem + + &__btn { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 0.625rem; // 10px → 0.625rem + padding: 1rem 1.5rem; // 16px → 1rem, 24px → 1.5rem + border: 2px solid #e5e7eb; + border-radius: 0.875rem; // 14px → 0.875rem + background: white; + cursor: pointer; + transition: all $transition-smooth; + + &:hover:not(:disabled) { + border-color: #d1d5db; + background: #f9fafb; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &--offer.toggle-minimal__btn--active { + border-color: $offer-color; + background: $offer-light; + + .toggle-minimal__icon { + background: $offer-gradient; + color: white; + } + + .toggle-minimal__label { + color: color.adjust($offer-color, $lightness: -10%); + } + } + + &--request.toggle-minimal__btn--active { + border-color: $request-color; + background: $request-light; + + .toggle-minimal__icon { + background: $request-gradient; + color: white; + } + + .toggle-minimal__label { + color: color.adjust($request-color, $lightness: -10%); + } + } + } + + &__icon { + width: 2.5rem; // 40px → 2.5rem + height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0.625rem; // 10px → 0.625rem + background: #f3f4f6; + color: #6b7280; + transition: all $transition-smooth; + } + + &__label { + font-size: 0.9375rem; // 15px → 0.9375rem + font-weight: 600; + color: #374151; + transition: color $transition-smooth; + } +} + +// ================================ +// VARIANT: Elegant +// ================================ +.toggle-elegant { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.25rem; // 20px → 1.25rem + + &__option { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem 1.5rem; // 32px → 2rem, 24px → 1.5rem + background: white; + border: 2px solid #e5e7eb; + border-radius: 1.5rem; // 24px → 1.5rem + cursor: pointer; + transition: all $transition-smooth; + + &:hover:not(&--disabled) { + border-color: #d1d5db; + box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.08); // 8px → 0.5rem, 24px → 1.5rem + } + + &--disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &--offer.toggle-elegant__option--selected { + border-color: $offer-color; + background: linear-gradient(180deg, $offer-light 0%, white 100%); + + .toggle-elegant__circle { + background: $offer-gradient; + color: white; + box-shadow: 0 0.5rem 1.5rem rgba($offer-color, 0.4); + } + + .toggle-elegant__decoration { + background: $offer-color; + } + + .toggle-elegant__radio-inner { + transform: scale(1); + background: $offer-gradient; + } + } + + &--request.toggle-elegant__option--selected { + border-color: $request-color; + background: linear-gradient(180deg, $request-light 0%, white 100%); + + .toggle-elegant__circle { + background: $request-gradient; + color: white; + box-shadow: 0 0.5rem 1.5rem rgba($request-color, 0.4); + } + + .toggle-elegant__decoration { + background: $request-color; + } + + .toggle-elegant__radio-inner { + transform: scale(1); + background: $request-gradient; + } + } + } + + &__visual { + position: relative; + margin-bottom: 1.25rem; // 20px → 1.25rem + } + + &__circle { + width: 5rem; // 80px → 5rem + height: 5rem; + display: flex; + align-items: center; + justify-content: center; + background: #f3f4f6; + border-radius: 50%; + color: #6b7280; + transition: all $transition-smooth; + } + + &__decoration { + position: absolute; + bottom: -0.25rem; // -4px → -0.25rem + right: -0.25rem; + width: 1.75rem; // 28px → 1.75rem + height: 1.75rem; + display: flex; + align-items: center; + justify-content: center; + background: #d1d5db; + border-radius: 50%; + color: white; + border: 3px solid white; + transition: background $transition-smooth; + } + + &__title { + margin: 0 0 0.25rem; // 4px → 0.25rem + font-size: 1.125rem; // 18px → 1.125rem + font-weight: 700; + color: #111827; + } + + &__subtitle { + margin: 0; + font-size: 0.8125rem; // 13px → 0.8125rem + color: #6b7280; + text-align: center; + } + + &__radio { + position: absolute; + top: 1rem; // 16px → 1rem + right: 1rem; + width: 1.375rem; // 22px → 1.375rem + height: 1.375rem; + border: 2px solid #d1d5db; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: border-color $transition-smooth; + } + + &__radio-inner { + width: 0.75rem; // 12px → 0.75rem + height: 0.75rem; + border-radius: 50%; + transform: scale(0); + transition: transform $transition-smooth; + } +} + +// ================================ +// Responsive +// ================================ +@media (max-width: 37.4375rem) { // 599px → 599/16 = 37.4375rem + .toggle-cards { + gap: 0.75rem; // 12px → 0.75rem + } + + .toggle-card { + padding: 1.25rem 0.75rem; // 20px → 1.25rem, 12px → 0.75rem + + &__icon-wrapper { + width: 3.5rem; // 56px → 3.5rem + height: 3.5rem; + margin-bottom: 0.75rem; // 12px → 0.75rem + + .q-icon { + font-size: 1.75rem !important; // 28px → 1.75rem + } + } + + &__label { + font-size: 1.1rem; // 14px → 0.875rem + } + + &__desc { + font-size: 0.85rem; // 11px → 0.6875rem + } + } + + .toggle-pills { + &__btn { + padding: 0.75rem 1rem; // 12px → 0.75rem, 16px → 1rem + font-size: 0.8125rem; // 13px → 0.8125rem + + .q-icon { + font-size: 1.125rem !important; // 18px → 1.125rem + } + } + } + + .toggle-minimal { + flex-direction: column; + } + + .toggle-elegant { + gap: 0.75rem; // 12px → 0.75rem + + &__option { + padding: 1.5rem 1rem; // 24px → 1.5rem, 16px → 1rem + } + + &__circle { + width: 4rem; // 64px → 4rem + height: 4rem; + + .q-icon { + font-size: 2rem !important; // 32px → 2rem + } + } + + &__title { + font-size: 0.9375rem; // 15px → 0.9375rem + } + + &__subtitle { + font-size: 0.75rem; // 12px → 0.75rem + } + } +} + +// ================================ +// Dark Mode +// ================================ +.body--dark { + .toggle-card { + background: #1f2937; + + &::before { + border-color: #374151; + } + + &__label { + color: #f9fafb; + } + + &__desc { + color: #9ca3af; + } + + &__icon-bg { + background: #374151; + } + + &--offer.toggle-card--selected .toggle-card__icon-bg { + background: rgba($offer-color, 0.2); + } + + &--request.toggle-card--selected .toggle-card__icon-bg { + background: rgba($request-color, 0.2); + } + } + + .toggle-pills__track { + background: #374151; + } + + .toggle-pills__btn { + color: #9ca3af; + } + + .toggle-minimal__btn { + background: #1f2937; + border-color: #374151; + + &:hover:not(:disabled) { + background: #374151; + } + } + + .toggle-minimal__icon { + background: #374151; + } + + .toggle-minimal__label { + color: #f9fafb; + } + + .toggle-elegant__option { + background: #1f2937; + border-color: #374151; + + &--offer.toggle-elegant__option--selected { + background: linear-gradient(180deg, rgba($offer-color, 0.15) 0%, #1f2937 100%); + } + + &--request.toggle-elegant__option--selected { + background: linear-gradient(180deg, rgba($request-color, 0.15) 0%, #1f2937 100%); + } + } + + .toggle-elegant__circle { + background: #374151; + } + + .toggle-elegant__title { + color: #f9fafb; + } + + .toggle-elegant__subtitle { + color: #9ca3af; + } + + .toggle-elegant__radio { + border-color: #4b5563; + } +} \ No newline at end of file diff --git a/src/modules/viaggi/components/ride/RideTypeToggle.ts b/src/modules/viaggi/components/ride/RideTypeToggle.ts new file mode 100644 index 00000000..7e2f1873 --- /dev/null +++ b/src/modules/viaggi/components/ride/RideTypeToggle.ts @@ -0,0 +1,64 @@ +import { defineComponent, PropType, computed } from 'vue'; +import type { RideType } from '../../types/viaggi.types'; + +type ToggleVariant = 'cards' | 'pills' | 'minimal' | 'elegant'; + +interface ToggleOption { + value: RideType; + label: string; + description: string; + icon: string; + decorIcon: string; +} + +export default defineComponent({ + name: 'RideTypeToggle', + + props: { + modelValue: { + type: String as PropType, + default: 'offer' + }, + variant: { + type: String as PropType, + default: 'cards' + }, + disabled: { + type: Boolean, + default: false + } + }, + + emits: ['update:modelValue', 'change'], + + setup(props, { emit }) { + const options = computed(() => [ + { + value: 'offer', + label: 'Offro passaggio', + description: 'Ho posti liberi in auto', + icon: 'directions_car', + decorIcon: 'person_add' + }, + { + value: 'request', + label: 'Cerco passaggio', + description: 'Ho bisogno di un passaggio', + icon: 'hail', + decorIcon: 'search' + } + ]); + + const select = (value: RideType) => { + if (!props.disabled) { + emit('update:modelValue', value); + emit('change', value); + } + }; + + return { + options, + select + }; + } +}); diff --git a/src/modules/viaggi/components/ride/RideTypeToggle.vue b/src/modules/viaggi/components/ride/RideTypeToggle.vue new file mode 100644 index 00000000..2dc23ac1 --- /dev/null +++ b/src/modules/viaggi/components/ride/RideTypeToggle.vue @@ -0,0 +1,136 @@ + + +