379 lines
9.8 KiB
Vue
379 lines
9.8 KiB
Vue
// ❗ WARNING please use your access token from mapbox.com
|
|
<script setup>
|
|
import mapboxgl from 'mapbox-gl'
|
|
import {
|
|
onMounted,
|
|
ref,
|
|
} from 'vue'
|
|
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
|
import { useDisplay } from 'vuetify'
|
|
import fleetImg from '@images/misc/fleet-car.png'
|
|
|
|
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
|
|
const accessToken = 'pk.eyJ1Ijoic29jaWFsZXhwbG9yZXIiLCJhIjoiREFQbXBISSJ9.dwFTwfSaWsHvktHrRtpydQ'
|
|
const map = ref()
|
|
const vuetifyDisplay = useDisplay()
|
|
|
|
definePage({ meta: { layoutWrapperClasses: 'layout-content-height-fixed' } })
|
|
|
|
const carImgs = ref([
|
|
fleetImg,
|
|
fleetImg,
|
|
fleetImg,
|
|
fleetImg,
|
|
])
|
|
|
|
const refCars = ref([])
|
|
|
|
const showPanel = ref([
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
])
|
|
|
|
const geojson = {
|
|
type: 'FeatureCollection',
|
|
features: [
|
|
{
|
|
type: 'Feature',
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [
|
|
-73.999024,
|
|
40.75249842,
|
|
],
|
|
},
|
|
},
|
|
{
|
|
type: 'Feature',
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [
|
|
-74.03,
|
|
40.75699842,
|
|
],
|
|
},
|
|
},
|
|
{
|
|
type: 'Feature',
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [
|
|
-73.967524,
|
|
40.7599842,
|
|
],
|
|
},
|
|
},
|
|
{
|
|
type: 'Feature',
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [
|
|
-74.0325,
|
|
40.742992,
|
|
],
|
|
},
|
|
},
|
|
],
|
|
}
|
|
|
|
const activeIndex = ref(0)
|
|
|
|
onMounted(() => {
|
|
mapboxgl.accessToken = accessToken
|
|
map.value = new mapboxgl.Map({
|
|
container: 'mapContainer',
|
|
style: 'mapbox://styles/mapbox/light-v9',
|
|
center: [
|
|
-73.999024,
|
|
40.75249842,
|
|
],
|
|
zoom: 12.25,
|
|
})
|
|
for (let index = 0; index < geojson.features.length; index++)
|
|
new mapboxgl.Marker({ element: refCars.value[index] }).setLngLat(geojson.features[index].geometry.coordinates).addTo(map.value)
|
|
refCars.value[activeIndex.value].classList.add('marker-focus')
|
|
})
|
|
|
|
const vehicleTrackingData = [
|
|
{
|
|
name: 'VOL-342808',
|
|
location: 'Chelsea, NY, USA',
|
|
progress: 88,
|
|
driverName: 'Veronica Herman',
|
|
},
|
|
{
|
|
name: 'VOL-954784',
|
|
location: 'Lincoln Harbor, NY, USA',
|
|
progress: 90,
|
|
driverName: 'Myrtle Ullrich',
|
|
},
|
|
{
|
|
name: 'VOL-342808',
|
|
location: 'Midtown East, NY, USA',
|
|
progress: 60,
|
|
driverName: 'Barry Schowalter',
|
|
},
|
|
{
|
|
name: 'VOL-343908',
|
|
location: 'Hoboken, NY, USA',
|
|
progress: 28,
|
|
driverName: 'Helen Jacobs',
|
|
},
|
|
]
|
|
|
|
const flyToLocation = (geolocation, index) => {
|
|
activeIndex.value = index
|
|
showPanel.value.fill(false)
|
|
showPanel.value[index] = !showPanel.value[index]
|
|
if (vuetifyDisplay.mdAndDown.value)
|
|
isLeftSidebarOpen.value = false
|
|
map.value.flyTo({
|
|
center: geolocation,
|
|
zoom: 16,
|
|
})
|
|
}
|
|
|
|
watch(activeIndex, () => {
|
|
refCars.value.forEach((car, index) => {
|
|
if (index === activeIndex.value)
|
|
car.classList.add('marker-focus')
|
|
else
|
|
car.classList.remove('marker-focus')
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<VLayout class="fleet-app-layout">
|
|
<VNavigationDrawer
|
|
v-model="isLeftSidebarOpen"
|
|
width="320"
|
|
absolute
|
|
touchless
|
|
:border="0"
|
|
location="start"
|
|
>
|
|
<VCard
|
|
class="h-100"
|
|
flat
|
|
>
|
|
<VCardItem>
|
|
<VCardTitle>
|
|
Fleet
|
|
</VCardTitle>
|
|
|
|
<template #append>
|
|
<IconBtn
|
|
class="d-lg-none"
|
|
@click="isLeftSidebarOpen = !isLeftSidebarOpen"
|
|
>
|
|
<VIcon icon="ri-close-line" />
|
|
</IconBtn>
|
|
</template>
|
|
</VCardItem>
|
|
|
|
<!-- 👉 Perfect Scrollbar -->
|
|
<PerfectScrollbar
|
|
:options="{ wheelPropagation: false, suppressScrollX: true }"
|
|
style="block-size: calc(100% - 60px);"
|
|
>
|
|
<VCardText class="pt-0">
|
|
<div
|
|
v-for="(vehicle, index) in vehicleTrackingData"
|
|
:key="index"
|
|
class="mb-6"
|
|
>
|
|
<div
|
|
class="d-flex align-center justify-space-between cursor-pointer"
|
|
@click="flyToLocation(geojson.features[index].geometry.coordinates, index)"
|
|
>
|
|
<div class="d-flex gap-x-4">
|
|
<VAvatar
|
|
icon="ri-car-line"
|
|
variant="tonal"
|
|
color="secondary"
|
|
/>
|
|
<div>
|
|
<h6 class="text-h6 font-weight-regular">
|
|
{{ vehicle.name }}
|
|
</h6>
|
|
<div class="text-body-1">
|
|
{{ vehicle.location }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<IconBtn density="comfortable">
|
|
<VIcon :icon="showPanel[index] ? 'ri-arrow-down-s-line' : $vuetify.locale.isRtl ? 'ri-arrow-left-s-line' : 'ri-arrow-right-s-line'" />
|
|
</IconBtn>
|
|
</div>
|
|
<VExpandTransition mode="out-in">
|
|
<div v-show="showPanel[index]">
|
|
<div class="py-4 mb-4">
|
|
<div class="d-flex justify-space-between text-body-1 mb-1">
|
|
<span class="text-high-emphasis ">Delivery Process</span>
|
|
<span>{{ vehicle.progress }}%</span>
|
|
</div>
|
|
<VProgressLinear
|
|
:model-value="vehicle.progress"
|
|
color="primary"
|
|
rounded
|
|
height="6"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<VTimeline
|
|
side="end"
|
|
align="start"
|
|
truncate-line="both"
|
|
density="compact"
|
|
class="v-timeline--variant-outlined"
|
|
>
|
|
<VTimelineItem
|
|
icon="ri-checkbox-circle-line"
|
|
dot-color="rgb(var(--v-theme-surface))"
|
|
icon-color="success"
|
|
fill-dot
|
|
size="20"
|
|
:elevation="0"
|
|
>
|
|
<div class="text-caption text-uppercase text-success">
|
|
TRACKING NUMBER CREATED
|
|
</div>
|
|
<div class="app-timeline-title">
|
|
{{ vehicle.driverName }}
|
|
</div>
|
|
<div class="text-body-2 mb-1">
|
|
Sep 01, 7:53 AM
|
|
</div>
|
|
</VTimelineItem>
|
|
<VTimelineItem
|
|
icon="ri-checkbox-circle-line"
|
|
dot-color="rgb(var(--v-theme-surface))"
|
|
icon-color="success"
|
|
fill-dot
|
|
size="20"
|
|
:elevation="0"
|
|
>
|
|
<div class="text-caption text-uppercase text-success">
|
|
OUT FOR DELIVERY
|
|
</div>
|
|
<div class="app-timeline-title">
|
|
Veronica Herman
|
|
</div>
|
|
<div class="app-timeline-text mb-1">
|
|
Sep 03, 8:02 AM
|
|
</div>
|
|
</VTimelineItem>
|
|
<VTimelineItem
|
|
icon="ri-map-pin-line"
|
|
dot-color="rgb(var(--v-theme-surface))"
|
|
icon-color="primary"
|
|
fill-dot
|
|
size="20"
|
|
:elevation="0"
|
|
>
|
|
<div class="text-caption text-uppercase text-primary">
|
|
ARRIVED
|
|
</div>
|
|
<div class="app-timeline-title">
|
|
Veronica Herman
|
|
</div>
|
|
<div class="app-timeline-text">
|
|
Sep 04, 8:18 AM
|
|
</div>
|
|
</VTimelineItem>
|
|
</VTimeline>
|
|
</div>
|
|
</div>
|
|
</VExpandTransition>
|
|
</div>
|
|
</VCardText>
|
|
</PerfectScrollbar>
|
|
</VCard>
|
|
</VNavigationDrawer>
|
|
|
|
<VMain>
|
|
<div class="h-100">
|
|
<IconBtn
|
|
class="d-lg-none navigation-toggle-btn rounded-sm"
|
|
variant="elevated"
|
|
@click="isLeftSidebarOpen = true"
|
|
>
|
|
<VIcon icon="ri-menu-line" />
|
|
</IconBtn>
|
|
|
|
<!-- 👉 Fleet map -->
|
|
<div
|
|
id="mapContainer"
|
|
class="basemap"
|
|
/>
|
|
|
|
<img
|
|
v-for="(car, index) in carImgs"
|
|
:key="index"
|
|
ref="refCars"
|
|
:src="car"
|
|
alt="car Img marker"
|
|
height="42"
|
|
width="20"
|
|
>
|
|
</div>
|
|
</VMain>
|
|
</VLayout>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@use "@styles/variables/vuetify.scss";
|
|
@use "@core-scss/base/mixins.scss";
|
|
@import "mapbox-gl/dist/mapbox-gl.css";
|
|
|
|
.fleet-app-layout {
|
|
border-radius: vuetify.$card-border-radius;
|
|
|
|
@include mixins.elevation(vuetify.$card-elevation);
|
|
|
|
$sel-fleet-app-layout: &;
|
|
|
|
@at-root {
|
|
.skin--bordered {
|
|
@include mixins.bordered-skin($sel-fleet-app-layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
.navigation-toggle-btn{
|
|
position: absolute;
|
|
z-index: 1;
|
|
inset-block-start: 1rem;
|
|
inset-inline-start: 1rem;
|
|
}
|
|
|
|
.navigation-close-btn{
|
|
position: absolute;
|
|
z-index: 1;
|
|
inset-block-start: 1rem;
|
|
inset-inline-end: 1rem;
|
|
}
|
|
|
|
.basemap {
|
|
block-size: 100%;
|
|
inline-size: 100%;
|
|
}
|
|
|
|
.marker-focus {
|
|
filter: drop-shadow(0 0 7px rgb(var(--v-theme-primary)));
|
|
}
|
|
|
|
.mapboxgl-ctrl-bottom-left,
|
|
.mapboxgl-ctrl-bottom-right {
|
|
display: none;
|
|
}
|
|
|
|
/* stylelint-disable-next-line selector-id-pattern */
|
|
#mapContainer {
|
|
block-size: 100vh !important;
|
|
}
|
|
</style>
|