Daily Routine
The Daily Routine feature provides a comprehensive 24-hour schedule planner with drag-and-drop functionality, allowing users to organize their daily routes and visualize them on an interactive map.
Overview
This feature combines schedule management with route visualization, offering:
24-hour time slot organization
Drag-and-drop route scheduling
Real-time route mapping
Persistent local storage
Current hour route highlighting
How to Use
Select Time Slot
Choose the time of day for your route from the dropdown (12:00 AM - 11:00 PM).
Enter Start Location
Input your starting point in the “Start location” field.
Enter Destination
Specify your destination in the “Destination” field.
Add Route
Click the “Add Route” button to add the route to your schedule.
Organize Routes
Drag and drop routes between different time slots to reorganize your day.
View Current Route
Click “Show Current Hour Route” to visualize your current scheduled route on the map.
Schedule Management
Data Persistence
Schedules are stored in browser localStorage:
let scheduleData = JSON . parse ( localStorage . getItem ( 'dailySchedule' )) || {};
function saveSchedule () {
localStorage . setItem ( 'dailySchedule' , JSON . stringify ( scheduleData ));
}
Your schedule persists across browser sessions, so you won’t lose your planned routes when you close the page.
Times are displayed in 12-hour format:
function formatTime ( hour ) {
const suffix = hour >= 12 ? 'PM' : 'AM' ;
const hour12 = hour % 12 || 12 ;
return ` ${ hour12 } :00 ${ suffix } ` ;
}
Adding Routes
Routes are added through the form submission handler:
addItemForm . onsubmit = ( e ) => {
e . preventDefault ();
const hour = timeSelect . value ;
const start = startInput . value . trim ();
const destination = destinationInput . value . trim ();
if ( ! start || ! destination ) return ;
const route = { start , destination };
if ( ! scheduleData [ hour ]) scheduleData [ hour ] = [];
scheduleData [ hour ]. push ( route );
saveSchedule ();
renderSchedule ();
enableDragAndDrop ();
startInput . value = '' ;
destinationInput . value = '' ;
};
Schedule Rendering
The schedule table is dynamically generated for all 24 hours:
function renderSchedule () {
scheduleBody . innerHTML = '' ;
for ( let hour = 0 ; hour < 24 ; hour ++ ) {
const row = document . createElement ( 'tr' );
const timeCell = document . createElement ( 'td' );
const slotCell = document . createElement ( 'td' );
timeCell . textContent = formatTime ( hour );
timeCell . className = 'time-cell' ;
slotCell . className = 'slot' ;
slotCell . id = `slot- ${ hour } ` ;
const ul = document . createElement ( 'ul' );
if ( scheduleData [ hour ]) {
scheduleData [ hour ]. forEach (( route , index ) => {
const li = createRouteElement ( hour , route , index );
ul . appendChild ( li );
});
}
slotCell . appendChild ( ul );
row . appendChild ( timeCell );
row . appendChild ( slotCell );
scheduleBody . appendChild ( row );
}
}
Drag-and-Drop Functionality
Powered by SortableJS for intuitive route organization:
function enableDragAndDrop () {
for ( let hour = 0 ; hour < 24 ; hour ++ ) {
const ul = document . getElementById ( `slot- ${ hour } ` ). querySelector ( 'ul' );
Sortable . create ( ul , {
group: 'shared' ,
animation: 150 ,
onEnd : function ( evt ) {
const fromHour = parseInt ( evt . from . parentElement . id . split ( '-' )[ 1 ]);
const toHour = parseInt ( evt . to . parentElement . id . split ( '-' )[ 1 ]);
const [ movedItem ] = scheduleData [ fromHour ]. splice ( evt . oldIndex , 1 );
if ( ! scheduleData [ toHour ]) scheduleData [ toHour ] = [];
scheduleData [ toHour ]. splice ( evt . newIndex , 0 , movedItem );
if ( scheduleData [ fromHour ]. length === 0 ) delete scheduleData [ fromHour ];
saveSchedule ();
renderSchedule ();
enableDragAndDrop ();
}
});
}
}
Shared Groups All time slots are part of the same drag-and-drop group, allowing movement between any hours
Smooth Animation 150ms animation provides visual feedback during drag operations
Auto-save Changes are automatically saved after each drag operation
Dynamic Rendering Schedule is re-rendered after moves to maintain consistency
Route Actions
Each route includes Edit and Delete buttons:
function createRouteElement ( hour , route , index ) {
const li = document . createElement ( 'li' );
li . textContent = ` ${ route . start } → ${ route . destination } ` ;
const actions = document . createElement ( 'span' );
actions . className = 'route-actions' ;
const editBtn = document . createElement ( 'button' );
editBtn . textContent = 'Edit' ;
editBtn . className = 'edit-btn' ;
editBtn . onclick = () => {
startInput . value = route . start ;
destinationInput . value = route . destination ;
timeSelect . value = hour ;
scheduleData [ hour ]. splice ( index , 1 );
if ( scheduleData [ hour ]. length === 0 ) delete scheduleData [ hour ];
saveSchedule ();
renderSchedule ();
enableDragAndDrop ();
};
const deleteBtn = document . createElement ( 'button' );
deleteBtn . textContent = 'Delete' ;
deleteBtn . className = 'delete-btn' ;
deleteBtn . onclick = () => {
scheduleData [ hour ]. splice ( index , 1 );
if ( scheduleData [ hour ]. length === 0 ) delete scheduleData [ hour ];
saveSchedule ();
renderSchedule ();
enableDragAndDrop ();
};
actions . appendChild ( editBtn );
actions . appendChild ( deleteBtn );
li . appendChild ( actions );
return li ;
}
Editing a route pre-fills the form with existing values and removes the route from the schedule, allowing you to modify and re-add it.
Map Integration
The feature includes Leaflet map with routing capabilities:
Map Initialization
const map = L . map ( 'map' ). setView ([ 37.7749 , - 122.4194 ], 10 );
L . tileLayer ( 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' , {
maxZoom: 19 ,
}). addTo ( map );
let currentRouteControl = null ;
Geocoding
Location names are converted to coordinates using Nominatim:
async function geocodePlace ( place ) {
const response = await fetch ( `https://nominatim.openstreetmap.org/search?format=json&q= ${ encodeURIComponent ( place ) } ` );
const data = await response . json ();
return data [ 0 ];
}
Route Drawing
async function drawRoute ( startName , destName ) {
try {
const [ start , dest ] = await Promise . all ([
geocodePlace ( startName ),
geocodePlace ( destName )
]);
if ( ! start || ! dest ) {
alert ( "Could not find one or both locations." );
return ;
}
if ( currentRouteControl ) map . removeControl ( currentRouteControl );
const startLatLng = L . latLng ( parseFloat ( start . lat ), parseFloat ( start . lon ));
const destLatLng = L . latLng ( parseFloat ( dest . lat ), parseFloat ( dest . lon ));
currentRouteControl = L . Routing . control ({
waypoints: [ startLatLng , destLatLng ],
routeWhileDragging: false ,
addWaypoints: false ,
draggableWaypoints: false ,
show: false
}). addTo ( map );
map . fitBounds ( L . latLngBounds ([ startLatLng , destLatLng ]), { padding: [ 50 , 50 ] });
} catch ( error ) {
console . error ( "Routing error:" , error );
alert ( "An error occurred while displaying the route." );
}
}
Current Hour Route
Automatically displays the route for the current hour:
function updateCurrentHourRoute () {
const now = new Date ();
const currentHour = now . getHours ();
const routes = scheduleData [ currentHour ];
if ( routes && routes . length > 0 ) {
const { start , destination } = routes [ 0 ];
drawRoute ( start , destination );
} else if ( currentRouteControl ) {
map . removeControl ( currentRouteControl );
currentRouteControl = null ;
}
}
updateCurrentHourRoute ();
setInterval ( updateCurrentHourRoute , 60 * 1000 ); // update every minute
The current hour route updates automatically every minute, ensuring the map always shows your active scheduled route.
UI Styling
The interface features:
Purple gradient color scheme (#7C3AED, #9333EA)
Rounded corners and soft shadows
Smooth transitions and hover effects
Visual feedback for drag operations
Special styling for drag-and-drop states: .sortable-chosen {
opacity : 0.8 ;
transform : scale ( 1.02 );
box-shadow : 0 4 px 10 px rgba ( 0 , 0 , 0 , 0.15 );
}
.sortable-ghost {
opacity : 0.5 ;
border : 2 px dashed #7C3AED ;
background-color : #f4ebff ;
}
Dependencies
The feature uses external libraries:
< link rel = "stylesheet" href = "https://unpkg.com/leaflet/dist/leaflet.css" />
< link rel = "stylesheet" href = "https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.css" />
< script src = "https://cdn.jsdelivr.net/npm/[email protected] /Sortable.min.js" ></ script >
< script src = "https://unpkg.com/leaflet/dist/leaflet.js" ></ script >
< script src = "https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.js" ></ script >
Ensure you have an active internet connection as the map tiles, geocoding, and routing services require external API access.