Skip to main content

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

1

Select Time Slot

Choose the time of day for your route from the dropdown (12:00 AM - 11:00 PM).
2

Enter Start Location

Input your starting point in the “Start location” field.
3

Enter Destination

Specify your destination in the “Destination” field.
4

Add Route

Click the “Add Route” button to add the route to your schedule.
5

Organize Routes

Drag and drop routes between different time slots to reorganize your day.
6

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.

Time Formatting

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 4px 10px rgba(0,0,0,0.15);
}

.sortable-ghost {
  opacity: 0.5;
  border: 2px 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.

Build docs developers (and LLMs) love