Files
Mods/TimeSync/railroad-manager.html
2025-12-01 19:28:03 -06:00

2548 lines
106 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Railroader Operations Manager</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', monospace;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #eee;
padding: 20px;
min-height: 100vh;
}
.header {
text-align: center;
padding: 20px;
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
margin-bottom: 20px;
border: 2px solid #f39c12;
}
.header h1 {
color: #f39c12;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.header .subtitle {
color: #bbb;
margin-top: 10px;
}
.nav-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.nav-tab {
padding: 12px 24px;
background: rgba(0, 0, 0, 0.4);
border: 2px solid #555;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
font-weight: bold;
}
.nav-tab:hover {
background: rgba(243, 156, 18, 0.3);
border-color: #f39c12;
}
.nav-tab.active {
background: #f39c12;
color: #1a1a2e;
border-color: #f39c12;
}
.tab-content {
display: none;
animation: fadeIn 0.3s;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.section {
background: rgba(0, 0, 0, 0.4);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
border: 1px solid #444;
}
.section h2 {
color: #f39c12;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #444;
}
.section h3 {
color: #3498db;
margin: 15px 0 10px 0;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #bbb;
font-weight: bold;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px;
background: rgba(0, 0, 0, 0.5);
border: 1px solid #555;
border-radius: 5px;
color: #eee;
font-family: 'Courier New', monospace;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.btn {
padding: 10px 20px;
background: #27ae60;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
margin-right: 10px;
margin-top: 10px;
}
.btn:hover {
background: #2ecc71;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.btn-danger {
background: #e74c3c;
}
.btn-danger:hover {
background: #c0392b;
}
.btn-warning {
background: #f39c12;
}
.btn-warning:hover {
background: #e67e22;
}
.btn-info {
background: #3498db;
}
.btn-info:hover {
background: #2980b9;
}
.list-item {
background: rgba(0, 0, 0, 0.3);
padding: 15px;
margin-bottom: 10px;
border-radius: 5px;
border-left: 4px solid #f39c12;
position: relative;
}
.list-item.completed {
opacity: 0.6;
border-left-color: #27ae60;
}
.list-item.priority-high {
border-left-color: #e74c3c;
}
.list-item.priority-medium {
border-left-color: #f39c12;
}
.list-item.priority-low {
border-left-color: #3498db;
}
.list-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.list-item-title {
font-weight: bold;
font-size: 1.1em;
color: #f39c12;
}
.list-item-actions {
display: flex;
gap: 8px;
}
.list-item-actions button {
padding: 5px 10px;
font-size: 0.9em;
}
.list-item-details {
color: #bbb;
font-size: 0.95em;
line-height: 1.6;
}
.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.stat-card {
background: linear-gradient(135deg, rgba(52, 152, 219, 0.3) 0%, rgba(41, 128, 185, 0.3) 100%);
padding: 20px;
border-radius: 10px;
border: 1px solid #3498db;
text-align: center;
}
.stat-card.danger {
background: linear-gradient(135deg, rgba(231, 76, 60, 0.3) 0%, rgba(192, 57, 43, 0.3) 100%);
border-color: #e74c3c;
}
.stat-card.success {
background: linear-gradient(135deg, rgba(39, 174, 96, 0.3) 0%, rgba(46, 204, 113, 0.3) 100%);
border-color: #27ae60;
}
.stat-card.warning {
background: linear-gradient(135deg, rgba(243, 156, 18, 0.3) 0%, rgba(230, 126, 34, 0.3) 100%);
border-color: #f39c12;
}
.stat-value {
font-size: 3em;
font-weight: bold;
color: #fff;
margin: 10px 0;
}
.stat-label {
color: #bbb;
font-size: 1.1em;
}
.empty-state {
text-align: center;
padding: 40px;
color: #777;
font-style: italic;
}
.timetable-train {
background: rgba(52, 152, 219, 0.1);
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
border-left: 4px solid #3498db;
}
.timetable-train strong {
color: #3498db;
font-size: 1.1em;
}
.timetable-train-compact {
background: rgba(52, 152, 219, 0.2);
padding: 12px;
margin-bottom: 8px;
border-radius: 5px;
text-align: center;
border: 1px solid rgba(52, 152, 219, 0.3);
}
.train-name {
color: #3498db;
font-weight: bold;
font-size: 1.1em;
letter-spacing: 1px;
}
details summary {
transition: all 0.3s;
}
details summary:hover {
background: rgba(52, 152, 219, 0.2) !important;
}
details[open] summary {
margin-bottom: 10px;
}
.timetable-grid {
width: 100%;
border-collapse: collapse;
font-size: 0.95em;
}
.timetable-grid th, .timetable-grid td {
padding: 8px;
text-align: center;
border: 1px solid #444;
}
.timetable-grid thead th {
background: rgba(0, 0, 0, 0.5);
font-weight: bold;
color: #ccc;
}
.header-westbound {
background: rgba(243, 156, 18, 0.3) !important;
color: #f39c12 !important;
font-size: 1.2em;
}
.header-eastbound {
background: rgba(243, 156, 18, 0.3) !important;
color: #f39c12 !important;
font-size: 1.2em;
}
.header-stations {
background: rgba(100, 100, 100, 0.3) !important;
color: #999 !important;
font-size: 1.1em;
vertical-align: middle;
}
.header-first {
background: rgba(243, 156, 18, 0.2) !important;
color: #f39c12 !important;
}
.header-second {
background: rgba(100, 100, 100, 0.2) !important;
color: #888 !important;
}
.train-header {
background: rgba(52, 152, 219, 0.2) !important;
color: #3498db !important;
font-weight: bold;
font-size: 1em;
}
.station-name {
background: rgba(50, 50, 50, 0.5);
color: #ccc;
font-weight: normal;
text-align: left !important;
padding-left: 15px !important;
}
.timetable-grid tbody td {
background: rgba(20, 20, 20, 0.5);
color: #ddd;
}
.timetable-grid tbody td:empty {
background: rgba(10, 10, 10, 0.3);
}
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: 3px;
font-size: 0.85em;
font-weight: bold;
margin-right: 5px;
}
.badge-priority-high {
background: #e74c3c;
}
.badge-priority-medium {
background: #f39c12;
}
.badge-priority-low {
background: #3498db;
}
.badge-status {
background: #27ae60;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
width: auto;
cursor: pointer;
}
.time-display {
background: rgba(0, 0, 0, 0.5);
padding: 15px;
border-radius: 5px;
text-align: center;
margin-bottom: 20px;
border: 1px solid #444;
}
.time-display .time {
font-size: 2em;
color: #f39c12;
font-weight: bold;
}
.export-import-section {
display: flex;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
}
#fileInput {
display: none;
}
.car-card {
background: rgba(0, 0, 0, 0.3);
padding: 12px;
border-radius: 5px;
border: 1px solid #555;
margin-bottom: 10px;
}
.car-card-header {
font-weight: bold;
color: #3498db;
margin-bottom: 5px;
}
.train-card {
background: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 5px;
border-left: 4px solid #3498db;
margin-bottom: 15px;
}
.train-card h4 {
color: #3498db;
margin-bottom: 10px;
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.8em;
}
.grid-2 {
grid-template-columns: 1fr;
}
.nav-tabs {
flex-direction: column;
}
.nav-tab {
width: 100%;
}
}
</style>
</head>
<body>
<div class="header">
<h1>🚂 RAILROADER OPERATIONS MANAGER</h1>
<p class="subtitle">Professional Railroad Management System</p>
<!-- Profile Selector -->
<div style="margin-top: 10px; padding: 10px; background: rgba(52, 152, 219, 0.1); border-radius: 5px; display: flex; align-items: center; gap: 10px;">
<label style="color: #3498db; font-weight: bold;">📁 Profile:</label>
<select id="profileSelect" onchange="switchProfile()" style="flex: 1; padding: 8px; background: rgba(0,0,0,0.5); border: 1px solid #3498db; color: #fff; border-radius: 3px; font-size: 1em;">
<option value="default">Default</option>
</select>
<button onclick="createNewProfile()" style="background: #27ae60; border: none; color: #fff; padding: 8px 15px; border-radius: 3px; cursor: pointer; font-weight: bold; font-size: 0.9em;"> New</button>
<button onclick="renameProfile()" style="background: #f39c12; border: none; color: #1a1a2e; padding: 8px 15px; border-radius: 3px; cursor: pointer; font-weight: bold; font-size: 0.9em;">✏️ Rename</button>
<button onclick="deleteProfile()" style="background: #e74c3c; border: none; color: #fff; padding: 8px 15px; border-radius: 3px; cursor: pointer; font-weight: bold; font-size: 0.9em;">🗑️ Delete</button>
<button onclick="uploadTimetable()" style="background: #9b59b6; border: none; color: #fff; padding: 8px 15px; border-radius: 3px; cursor: pointer; font-weight: bold; font-size: 0.9em;">📅 Upload Timetable</button>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 15px;">
<div class="time-display">
<div class="time" id="currentTime">--:--:--</div>
<div id="currentDate"></div>
<div id="syncMessage" style="margin-top: 10px; padding: 8px; background: rgba(231, 76, 60, 0.2); border-radius: 3px; border-left: 3px solid #e74c3c; font-size: 0.85em; color: #e74c3c; display: none;">
⚠️ Not connected to game. Run Railroader or load a save.
</div>
<button id="selectSyncBtn" onclick="selectSyncFile()" style="margin-top: 10px; padding: 10px 20px; background: #3498db; color: #fff; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 1em;">
📂 Connect to Game Time Sync
</button>
<div style="margin-top: 8px; padding: 6px; background: rgba(52, 152, 219, 0.1); border-radius: 3px; font-size: 0.75em; color: #888;">
💡 Click button and select: <code style="color: #3498db;">(GameDirectory)\Mods\TimeSync\railroad-time-sync.json</code>
</div>
</div>
<div id="nextDeparture" style="padding: 12px; background: rgba(243, 156, 18, 0.2); border-radius: 5px; border-left: 4px solid #f39c12;">
<div style="font-size: 0.9em; color: #bbb;">NEXT DEPARTURE</div>
<div style="font-size: 1.3em; color: #f39c12; font-weight: bold; margin-top: 5px;" id="nextDepartureTrain">--</div>
<div style="font-size: 1em; color: #ddd; margin-top: 3px;" id="nextDepartureTime">--</div>
</div>
</div>
</div>
<div class="nav-tabs">
<button class="nav-tab active" onclick="switchTab('dashboard')">📊 Dashboard</button>
<button class="nav-tab" onclick="switchTab('timetable')">🕐 Timetable</button>
<button class="nav-tab" onclick="switchTab('switchlist')">📋 Switch List</button>
<button class="nav-tab" onclick="switchTab('tasks')">✓ Tasks</button>
<button class="nav-tab" onclick="switchTab('trains')">🚂 Train Management</button>
<button class="nav-tab" onclick="switchTab('driving')">🎮 Driving Log</button>
<button class="nav-tab" onclick="switchTab('ai')">🤖 AI Crew</button>
<button class="nav-tab" onclick="switchTab('cargo')">📦 Cargo Tracking</button>
<button class="nav-tab" onclick="switchTab('notes')">📝 Notes</button>
<button class="nav-tab" onclick="switchTab('settings')">⚙️ Settings</button>
</div>
<!-- Dashboard Tab -->
<div id="dashboard" class="tab-content active">
<div class="section">
<h2>📊 Operations Dashboard</h2>
<div class="grid-2">
<div class="stat-card">
<div class="stat-label">Active Tasks</div>
<div class="stat-value" id="statActiveTasks">0</div>
</div>
<div class="stat-card warning">
<div class="stat-label">Switch List Items</div>
<div class="stat-value" id="statSwitchItems">0</div>
</div>
<div class="stat-card success">
<div class="stat-label">Active Trains</div>
<div class="stat-value" id="statActiveTrains">0</div>
</div>
<div class="stat-card danger">
<div class="stat-label">AI Crew Members</div>
<div class="stat-value" id="statAICrew">0</div>
</div>
</div>
</div>
<div class="section">
<h2>🚨 Priority Items</h2>
<div id="priorityItems"></div>
</div>
</div>
<!-- Timetable Tab -->
<div id="timetable" class="tab-content">
<div class="section">
<h2>🕐 Railroad Timetable</h2>
<!-- Dynamic Timetable Container -->
<div id="timetableContainer" style="background: rgba(0, 0, 0, 0.5); padding: 15px; border-radius: 10px; margin-top: 20px; overflow-x: auto;">
<table class="timetable-grid">
<thead>
<tr>
<th colspan="3" class="header-westbound">Westbound<br><span style="font-size: 0.8em; font-weight: normal;">Head Down</span></th>
<th rowspan="2" class="header-stations">Stations</th>
<th colspan="3" class="header-eastbound">Eastbound<br><span style="font-size: 0.8em; font-weight: normal;">Head Up</span></th>
</tr>
<tr>
<th class="header-second">Second</th>
<th colspan="2" class="header-first">First Class</th>
<th colspan="2" class="header-first">First Class</th>
<th class="header-second">Second</th>
</tr>
<tr>
<th class="train-header">COALW</th>
<th class="train-header">WPD</th>
<th class="train-header">FTW</th>
<th></th>
<th class="train-header">EPD</th>
<th class="train-header">FTE</th>
<th class="train-header">COALE</th>
</tr>
</thead>
<tbody>
<tr><td>16:15</td><td>7:00</td><td>6:00</td><td class="station-name">Sylva</td><td>16:31</td><td>20:36</td><td>20:05</td></tr>
<tr><td></td><td>7:34</td><td>6:34</td><td class="station-name">Dillsboro</td><td>15:57</td><td>20:02</td><td></td></tr>
<tr><td></td><td>8:06</td><td></td><td class="station-name">Wilmot</td><td>15:15</td><td></td><td></td></tr>
<tr><td></td><td>8:38</td><td></td><td class="station-name">Thomas Valley Station</td><td>14:47</td><td></td><td></td></tr>
<tr><td></td><td>9:16</td><td>7:23</td><td class="station-name">Whittier</td><td>14:11</td><td>18:43</td><td></td></tr>
<tr><td>17:12</td><td>9:52</td><td>7:59</td><td class="station-name">Ela</td><td>13:35</td><td>17:37</td><td>18:30</td></tr>
<tr><td></td><td>10:26</td><td></td><td class="station-name">Gov Island City Station</td><td>12:56</td><td>16:41</td><td></td></tr>
<tr><td></td><td>13:27</td><td>8:38</td><td class="station-name">Bryson</td><td>12:21</td><td>15:36</td><td></td></tr>
<tr><td></td><td>14:06</td><td></td><td class="station-name">Hemingway</td><td>11:12</td><td></td><td></td></tr>
<tr><td></td><td>14:39</td><td>9:20</td><td class="station-name">Alarka Jct</td><td>10:39</td><td>13:47</td><td></td></tr>
<tr><td></td><td>15:25</td><td></td><td class="station-name">Almond</td><td>9:53</td><td></td><td></td></tr>
<tr><td></td><td>16:18</td><td>10:40</td><td class="station-name">Nantahala</td><td>9:00</td><td>12:08</td><td></td></tr>
<tr><td></td><td>16:59</td><td></td><td class="station-name">Topton</td><td>8:19</td><td></td><td></td></tr>
<tr><td></td><td>17:37</td><td></td><td class="station-name">Rhodo</td><td>7:41</td><td></td><td></td></tr>
<tr><td></td><td>18:18</td><td>11:39</td><td class="station-name">Andrews</td><td>7:00</td><td>11:39</td><td></td></tr>
</tbody>
</table>
</div>
<!-- Alarka Branch Timetable -->
<div style="background: rgba(0, 0, 0, 0.5); padding: 15px; border-radius: 10px; margin-top: 30px; overflow-x: auto;">
<h3 style="color: #27ae60; margin-bottom: 15px;">Alarka Branch</h3>
<table class="timetable-grid">
<thead>
<tr>
<th colspan="3" class="header-westbound">Westbound<br><span style="font-size: 0.8em; font-weight: normal;">Head Down</span></th>
<th rowspan="2" class="header-stations">Stations</th>
<th colspan="3" class="header-eastbound">Eastbound<br><span style="font-size: 0.8em; font-weight: normal;">Head Up</span></th>
</tr>
<tr>
<th colspan="3" class="header-first">First Class</th>
<th colspan="3" class="header-first">First Class</th>
</tr>
<tr>
<th class="train-header">AP3</th>
<th class="train-header">AFW</th>
<th class="train-header">AP1</th>
<th></th>
<th class="train-header">AP2</th>
<th class="train-header">AFE</th>
<th class="train-header">AP4</th>
</tr>
</thead>
<tbody>
<tr><td>13:39</td><td>9:00</td><td>8:39</td><td class="station-name">Alarka Jct</td><td>10:50</td><td>14:09</td><td>14:50</td></tr>
<tr><td>13:06</td><td>9:04</td><td>8:06</td><td class="station-name">Cochran</td><td>10:54</td><td>13:36</td><td>14:54</td></tr>
<tr><td>13:00</td><td>9:41</td><td>8:00</td><td class="station-name">Alarka</td><td>11:31</td><td>13:30</td><td>15:31</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Switch List Tab -->
<div id="switchlist" class="tab-content">
<div class="section">
<h2>📋 Switch List</h2>
<div class="form-group">
<label>Car Number/ID</label>
<input type="text" id="switchCarNumber" placeholder="e.g., ATSF 12345">
</div>
<div class="form-group">
<label>Car Type</label>
<select id="switchCarType">
<option value="Boxcar">Boxcar</option>
<option value="Flatcar">Flatcar</option>
<option value="Gondola">Gondola</option>
<option value="Hopper">Hopper</option>
<option value="Tank Car">Tank Car</option>
<option value="Refrigerator">Refrigerator</option>
<option value="Caboose">Caboose</option>
<option value="Other">Other</option>
</select>
</div>
<div class="form-group">
<label>Origin</label>
<input type="text" id="switchOrigin" placeholder="Current location">
</div>
<div class="form-group">
<label>Destination</label>
<input type="text" id="switchDestination" placeholder="Where it needs to go">
</div>
<div class="form-group">
<label>Cargo/Contents</label>
<input type="text" id="switchCargo" placeholder="What's loaded">
</div>
<div class="form-group">
<label>Track/Spot</label>
<input type="text" id="switchTrack" placeholder="Track number or spot">
</div>
<div class="form-group">
<label>Priority</label>
<select id="switchPriority">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="switchNotes" placeholder="Special instructions, hazmat, etc."></textarea>
</div>
<button class="btn" onclick="addSwitchItem()">Add to Switch List</button>
</div>
<div class="section">
<h2>Current Switch List</h2>
<div id="switchListItems"></div>
</div>
</div>
<!-- Tasks Tab -->
<div id="tasks" class="tab-content">
<div class="section">
<h2>✓ Task Management</h2>
<div class="form-group">
<label>Task Title</label>
<input type="text" id="taskTitle" placeholder="What needs to be done">
</div>
<div class="form-group">
<label>Description</label>
<textarea id="taskDescription" placeholder="Detailed description"></textarea>
</div>
<div class="form-group">
<label>Priority</label>
<select id="taskPriority">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
<div class="form-group">
<label>Category</label>
<select id="taskCategory">
<option value="switching">Switching</option>
<option value="maintenance">Maintenance</option>
<option value="delivery">Delivery</option>
<option value="pickup">Pickup</option>
<option value="inspection">Inspection</option>
<option value="other">Other</option>
</select>
</div>
<button class="btn" onclick="addTask()">Add Task</button>
</div>
<div class="section">
<h2>Active Tasks</h2>
<div id="taskList"></div>
</div>
</div>
<!-- Train Management Tab -->
<div id="trains" class="tab-content">
<div class="section">
<h2>🚂 Train Management</h2>
<div class="form-group">
<label>Train ID/Number</label>
<input type="text" id="trainNumber" placeholder="e.g., Train 101">
</div>
<div class="form-group">
<label>Locomotive(s)</label>
<input type="text" id="trainLocos" placeholder="Locomotive numbers">
</div>
<div class="form-group">
<label>Current Location</label>
<input type="text" id="trainLocation" placeholder="Current position">
</div>
<div class="form-group">
<label>Destination</label>
<input type="text" id="trainDestination" placeholder="Where it's headed">
</div>
<div class="form-group">
<label>Number of Cars</label>
<input type="number" id="trainCars" placeholder="0" min="0">
</div>
<div class="form-group">
<label>Status</label>
<select id="trainStatus">
<option value="ready">Ready</option>
<option value="in-transit">In Transit</option>
<option value="switching">Switching</option>
<option value="waiting">Waiting</option>
<option value="maintenance">Maintenance</option>
</select>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="trainNotes" placeholder="Crew, schedule, special instructions"></textarea>
</div>
<button class="btn" onclick="addTrain()">Add/Update Train</button>
</div>
<div class="section">
<h2>Active Trains</h2>
<div id="trainList"></div>
</div>
</div>
<!-- Driving Log Tab -->
<div id="driving" class="tab-content">
<div class="section">
<h2>🎮 Driving Log</h2>
<div class="form-group">
<label>Locomotive Number</label>
<input type="text" id="driveLocoNumber" placeholder="Which loco are you operating">
</div>
<div class="form-group">
<label>Route/Run</label>
<input type="text" id="driveRoute" placeholder="Where you're going">
</div>
<div class="form-group">
<label>Start Time</label>
<input type="time" id="driveStartTime">
</div>
<div class="form-group">
<label>End Time</label>
<input type="time" id="driveEndTime">
</div>
<div class="form-group">
<label>Distance (optional)</label>
<input type="text" id="driveDistance" placeholder="Miles or km">
</div>
<div class="form-group">
<label>Fuel Used (optional)</label>
<input type="text" id="driveFuel" placeholder="Gallons or liters">
</div>
<div class="form-group">
<label>Log Notes</label>
<textarea id="driveNotes" placeholder="Events, incidents, observations during the run"></textarea>
</div>
<button class="btn" onclick="addDrivingLog()">Log Drive</button>
</div>
<div class="section">
<h2>Driving History</h2>
<div id="drivingLogList"></div>
</div>
</div>
<!-- AI Crew Tab -->
<div id="ai" class="tab-content">
<div class="section">
<h2>🤖 AI Crew Management</h2>
<div class="form-group">
<label>AI Name/ID</label>
<input type="text" id="aiName" placeholder="AI crew member identifier">
</div>
<div class="form-group">
<label>Role</label>
<select id="aiRole">
<option value="engineer">Engineer</option>
<option value="conductor">Conductor</option>
<option value="brakeman">Brakeman</option>
<option value="switcher">Switcher</option>
</select>
</div>
<div class="form-group">
<label>Assigned Train/Loco</label>
<input type="text" id="aiAssignment" placeholder="What they're operating">
</div>
<div class="form-group">
<label>Current Task</label>
<input type="text" id="aiTask" placeholder="What they're doing now">
</div>
<div class="form-group">
<label>Status</label>
<select id="aiStatus">
<option value="active">Active</option>
<option value="idle">Idle</option>
<option value="off-duty">Off Duty</option>
</select>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="aiNotes" placeholder="AI behavior, issues, instructions"></textarea>
</div>
<button class="btn" onclick="addAICrew()">Add AI Crew</button>
</div>
<div class="section">
<h2>AI Crew Roster</h2>
<div id="aiCrewList"></div>
</div>
</div>
<!-- Cargo Tracking Tab -->
<div id="cargo" class="tab-content">
<div class="section">
<h2>📦 Cargo Tracking</h2>
<div class="form-group">
<label>Cargo ID/Waybill</label>
<input type="text" id="cargoId" placeholder="Tracking number">
</div>
<div class="form-group">
<label>Cargo Type</label>
<input type="text" id="cargoType" placeholder="e.g., Coal, Grain, Steel">
</div>
<div class="form-group">
<label>Weight/Quantity</label>
<input type="text" id="cargoWeight" placeholder="Tons or units">
</div>
<div class="form-group">
<label>Shipper</label>
<input type="text" id="cargoShipper" placeholder="Who's sending it">
</div>
<div class="form-group">
<label>Consignee</label>
<input type="text" id="cargoConsignee" placeholder="Who's receiving it">
</div>
<div class="form-group">
<label>Origin</label>
<input type="text" id="cargoOrigin" placeholder="Starting point">
</div>
<div class="form-group">
<label>Destination</label>
<input type="text" id="cargoDestination" placeholder="Final destination">
</div>
<div class="form-group">
<label>Status</label>
<select id="cargoStatus">
<option value="awaiting-pickup">Awaiting Pickup</option>
<option value="in-transit">In Transit</option>
<option value="delivered">Delivered</option>
<option value="delayed">Delayed</option>
</select>
</div>
<div class="form-group">
<label>Notes</label>
<textarea id="cargoNotes" placeholder="Special handling, hazmat info, etc."></textarea>
</div>
<button class="btn" onclick="addCargo()">Add Cargo</button>
</div>
<div class="section">
<h2>Cargo Manifest</h2>
<div id="cargoList"></div>
</div>
</div>
<!-- Notes Tab -->
<div id="notes" class="tab-content">
<div class="section">
<h2>📝 Operations Notes</h2>
<div class="form-group">
<label>Note Title</label>
<input type="text" id="noteTitle" placeholder="Subject">
</div>
<div class="form-group">
<label>Note Content</label>
<textarea id="noteContent" style="min-height: 200px;" placeholder="Write your notes here..."></textarea>
</div>
<button class="btn" onclick="addNote()">Save Note</button>
</div>
<div class="section">
<h2>Saved Notes</h2>
<div id="notesList"></div>
</div>
</div>
<!-- Settings Tab -->
<div id="settings" class="tab-content">
<div class="section">
<h2>⚙️ Settings & Data Management</h2>
<h3>Game Time Sync</h3>
<div style="padding: 15px; background: rgba(52, 152, 219, 0.1); border-radius: 5px; margin-bottom: 20px; border-left: 4px solid #3498db;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<span style="color: #ccc;">Mod Sync Status:</span>
<span id="syncStatus" style="color: #888;">⚪ Not Connected</span>
</div>
<p style="color: #888; font-size: 0.9em; margin: 0;">
🎮 Install the TimeSync mod to automatically sync the website clock with your game time.<br>
Place <strong>railroad-time-sync.json</strong> in the same folder as this HTML file.
</p>
</div>
<h3>Data File Management</h3>
<p style="color: #bbb; margin: 10px 0;">💾 Data automatically saves to <strong>railroad-data.json</strong> after changes.</p>
<div class="export-import-section">
<button class="btn btn-info" onclick="saveToFile()"><EFBFBD> Save to File Now</button>
<button class="btn btn-warning" onclick="document.getElementById('fileInput').click()"><EFBFBD> Load from File</button>
<input type="file" id="fileInput" accept=".json" onchange="loadFromFile(event)">
</div>
<p style="color: #888; font-size: 0.9em; margin-top: 10px;">
Auto-save triggers 2 seconds after your last change. Click "Save to File Now" to save immediately.
</p>
<h3>Clear Data</h3>
<p style="color: #e74c3c; margin: 10px 0;">⚠️ Warning: These actions cannot be undone!</p>
<div class="export-import-section">
<button class="btn btn-danger" onclick="clearSection('switchlist')">Clear Switch List</button>
<button class="btn btn-danger" onclick="clearSection('tasks')">Clear Tasks</button>
<button class="btn btn-danger" onclick="clearSection('trains')">Clear Trains</button>
<button class="btn btn-danger" onclick="clearSection('driving')">Clear Driving Logs</button>
<button class="btn btn-danger" onclick="clearSection('ai')">Clear AI Crew</button>
<button class="btn btn-danger" onclick="clearSection('cargo')">Clear Cargo</button>
<button class="btn btn-danger" onclick="clearSection('notes')">Clear Notes</button>
<button class="btn btn-danger" onclick="clearAllData()">Clear ALL Data</button>
</div>
<h3>About</h3>
<p style="margin-top: 10px; color: #bbb;">
<strong>Railroader Operations Manager</strong><br>
A comprehensive local storage-based management system for railroad operations.<br>
All data is stored locally in your browser.
</p>
</div>
</div>
<script>
// Initialize data storage
const STORAGE_KEYS = {
switchlist: 'rr_switchlist',
tasks: 'rr_tasks',
trains: 'rr_trains',
driving: 'rr_driving',
ai: 'rr_ai',
cargo: 'rr_cargo',
notes: 'rr_notes'
};
// Profile management
let currentProfile = 'default';
let profiles = {};
// In-memory data store
let railroadData = {
switchlist: [],
tasks: [],
trains: [],
driving: [],
ai: [],
cargo: [],
notes: []
};
// Timetable data
let timetable = [];
// Default timetable
let defaultTimetable = [
{ train: "WPD", departs: "07:00", station: "Whittier", class: "1P", westbound: true },
{ train: "EPD", departs: "07:00", station: "Andrews", class: "1P", westbound: false },
{ train: "FTW", departs: "04:00", station: "Sylva Interchange", class: "1F", westbound: true },
{ train: "FTE", departs: "04:00", station: "Sylva Interchange", class: "1F", westbound: false },
{ train: "AP1", departs: "08:00", station: "Alarka Jct.", class: "1P", westbound: false },
{ train: "AP2", departs: "11:00", station: "Alarka", class: "1P", westbound: true },
{ train: "AP3", departs: "13:00", station: "Alarka Jct.", class: "1P", westbound: false },
{ train: "AP4", departs: "16:00", station: "Alarka", class: "1P", westbound: true },
{ train: "AFW", departs: "09:30", station: "Alarka", class: "2F", westbound: true },
{ train: "AFE", departs: "14:30", station: "Alarka Jct.", class: "2F", westbound: false },
{ train: "COALW", departs: "10:00", station: "Cochran", class: "2F", westbound: true },
{ train: "COALE", departs: "15:00", station: "Sylva Yard", class: "2F", westbound: false }
];
let saveTimeout = null;
// Load profiles from localStorage
async function loadProfiles() {
// First try localStorage
const stored = localStorage.getItem('railroad_profiles');
if (stored) {
try {
profiles = JSON.parse(stored);
console.log('Loaded profiles from localStorage');
} catch (e) {
console.error('Failed to parse stored profiles:', e);
}
}
// If still empty, try to fetch from file
if (Object.keys(profiles).length === 0) {
try {
const response = await fetch('railroad-profiles.json?t=' + Date.now());
if (response.ok) {
profiles = await response.json();
console.log('Loaded profiles from file');
}
} catch (e) {
console.log('No existing profiles file, starting fresh');
}
}
if (!profiles['default']) {
profiles['default'] = {
name: 'Default',
data: railroadData,
timetable: defaultTimetable
};
}
// Ensure all profiles have a timetable
Object.keys(profiles).forEach(key => {
if (!profiles[key].timetable) {
profiles[key].timetable = defaultTimetable;
}
});
updateProfileDropdown();
}
// Save profiles to file (writes directly without prompting)
async function saveProfiles() {
// Save to localStorage as backup
localStorage.setItem('railroad_profiles', JSON.stringify(profiles));
// Note: Browser security prevents writing files directly
// Data is saved to localStorage and auto-loads on next visit
console.log('Profiles auto-saved to browser storage');
}
// Manual export to download file
function manualSave() {
// Save current profile first
if (profiles[currentProfile]) {
profiles[currentProfile].data = railroadData;
}
const dataStr = JSON.stringify(profiles, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'railroad-profiles.json';
a.click();
URL.revokeObjectURL(url);
showNotification('Profiles exported to railroad-profiles.json');
}
// Export just the current profile
function exportProfile() {
const profileData = {
name: profiles[currentProfile].name,
data: railroadData,
timetable: timetable
};
const dataStr = JSON.stringify(profileData, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `railroad-profile-${profiles[currentProfile].name}.json`;
a.click();
URL.revokeObjectURL(url);
showNotification('Profile exported: ' + profiles[currentProfile].name);
}
// Update profile dropdown
function updateProfileDropdown() {
const select = document.getElementById('profileSelect');
select.innerHTML = '';
Object.keys(profiles).forEach(key => {
const option = document.createElement('option');
option.value = key;
option.textContent = profiles[key].name;
if (key === currentProfile) option.selected = true;
select.appendChild(option);
});
}
// Switch profile
function switchProfile() {
// Save current profile data
if (profiles[currentProfile]) {
profiles[currentProfile].data = railroadData;
saveProfiles();
}
// Load new profile
currentProfile = document.getElementById('profileSelect').value;
railroadData = JSON.parse(JSON.stringify(profiles[currentProfile].data));
// Load profile's timetable
if (profiles[currentProfile].timetable) {
timetable = profiles[currentProfile].timetable;
}
// Refresh all displays
renderAll();
renderTimetable();
showNotification('Switched to profile: ' + profiles[currentProfile].name);
}
// Create new profile
function createNewProfile() {
const name = prompt('Enter profile name:');
if (!name) return;
const key = 'profile_' + Date.now();
profiles[key] = {
name: name,
data: {
switchlist: [],
tasks: [],
trains: [],
driving: [],
ai: [],
cargo: [],
notes: []
},
timetable: defaultTimetable
};
saveProfiles();
updateProfileDropdown();
showNotification('Created profile: ' + name);
}
// Rename profile
function renameProfile() {
if (currentProfile === 'default') {
alert('Cannot rename the default profile');
return;
}
const newName = prompt('Enter new name:', profiles[currentProfile].name);
if (!newName) return;
profiles[currentProfile].name = newName;
saveProfiles();
updateProfileDropdown();
showNotification('Profile renamed to: ' + newName);
}
// Delete profile
function deleteProfile() {
if (currentProfile === 'default') {
alert('Cannot delete the default profile');
return;
}
if (!confirm('Delete profile "' + profiles[currentProfile].name + '"? This cannot be undone!')) {
return;
}
delete profiles[currentProfile];
currentProfile = 'default';
railroadData = JSON.parse(JSON.stringify(profiles['default'].data));
saveProfiles();
updateProfileDropdown();
renderAll();
showNotification('Profile deleted');
}
// Upload timetable for current profile
function uploadTimetable() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.txt,.json';
input.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = event => {
try {
const content = event.target.result;
// Try parsing as JSON first
try {
const jsonData = JSON.parse(content);
if (Array.isArray(jsonData)) {
timetable = jsonData;
} else {
alert('Invalid timetable JSON format. Expected array of train entries.');
return;
}
} catch {
// Parse as text file
const lines = content.split('\n').filter(l => l.trim());
timetable = [];
for (const line of lines) {
// Check for new format: TRAIN DIR CLASS: STATION TIME, STATION TIME, ...
const colonIndex = line.indexOf(':');
if (colonIndex > 0 && colonIndex < 20) { // Header should be short
const header = line.substring(0, colonIndex).trim();
const stops = line.substring(colonIndex + 1).trim();
const headerParts = header.split(/\s+/);
const train = headerParts[0];
const direction = headerParts[1]; // W or E
const trainClass = headerParts[2]; // 1P, 1F, 2F
const stopPairs = stops.split(',').map(s => s.trim());
for (const stop of stopPairs) {
const parts = stop.split(/\s+/);
if (parts.length >= 2) {
const station = parts[0];
const time = parts[1];
// Normalize time format to HH:MM
const timeParts = time.split(':');
const hours = timeParts[0].padStart(2, '0');
const mins = timeParts[1] || '00';
const normalizedTime = `${hours}:${mins}`;
timetable.push({
train: train,
departs: normalizedTime,
station: station,
class: trainClass,
westbound: direction === 'W'
});
}
}
} else {
// Old format: TRAIN TIME STATION [W/E]
const parts = line.trim().split(/\s+/);
if (parts.length >= 3) {
timetable.push({
train: parts[0],
departs: parts[1],
station: parts.slice(2, -1).join(' '),
class: parts[0].includes('P') ? '1P' : parts[0].includes('F') ? '1F' : '2F',
westbound: parts[parts.length - 1] === 'W'
});
}
}
}
}
// Save to current profile
if (profiles[currentProfile]) {
profiles[currentProfile].timetable = timetable;
saveProfiles();
}
console.log('Timetable parsed:', timetable.length, 'entries');
console.log('Sample entries:', timetable.slice(0, 3));
renderTimetable();
showNotification('Timetable uploaded: ' + timetable.length + ' entries for profile: ' + profiles[currentProfile].name);
} catch (error) {
alert('Error loading timetable: ' + error.message);
}
};
reader.readAsText(file);
};
input.click();
}
// Render timetable dynamically
function renderTimetable() {
const container = document.getElementById('timetableContainer');
if (!container) {
console.error('Timetable container not found!');
return;
}
console.log('Rendering timetable with', timetable ? timetable.length : 0, 'entries');
if (!timetable || timetable.length === 0) {
container.innerHTML = '<p style="color: #999; text-align: center; padding: 40px;">No timetable loaded. Upload a timetable file to display train schedules.</p>';
updateNextDeparture(new Date());
return;
}
// Group by train
const trainData = {};
timetable.forEach(entry => {
if (!trainData[entry.train]) {
trainData[entry.train] = {
class: entry.class,
westbound: entry.westbound,
stops: []
};
}
trainData[entry.train].stops.push({
station: entry.station,
time: entry.departs
});
});
// Build simple list view
let html = '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px;">';
Object.keys(trainData).forEach(trainName => {
const train = trainData[trainName];
const direction = train.westbound ? 'Westbound' : 'Eastbound';
const dirColor = train.westbound ? '#e74c3c' : '#3498db';
html += `
<div style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; border-left: 4px solid ${dirColor};">
<h3 style="color: ${dirColor}; margin-bottom: 10px;">
${trainName} <span style="font-size: 0.7em; color: #999;">${direction} ${train.class}</span>
</h3>
<div style="font-size: 0.85em;">`;
train.stops.forEach(stop => {
html += `<div style="padding: 3px 0; display: flex; justify-content: space-between;">
<span style="color: #bbb;">${stop.station}</span>
<span style="color: #f39c12; font-weight: bold;">${stop.time}</span>
</div>`;
});
html += `</div></div>`;
});
html += '</div>';
container.innerHTML = html;
console.log('Timetable rendered. Updating next departure...');
const now = new Date();
console.log('Current time for next departure:', now.getHours() + ':' + now.getMinutes());
updateNextDeparture(now);
}
// Load data from current profile
function loadData(key) {
return railroadData[key] || [];
}
// Save data to in-memory store and auto-save profile
function saveData(key, data) {
railroadData[key] = data;
updateDashboard();
// Save to current profile
if (profiles[currentProfile]) {
profiles[currentProfile].data = railroadData;
saveProfiles();
}
// Debounce file export
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
showSaveIndicator();
}, 1000);
}
// Export current profile to JSON file
function saveToFile() {
const dataStr = JSON.stringify(railroadData, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `railroad-data-${profiles[currentProfile].name}.json`;
a.click();
URL.revokeObjectURL(url);
showNotification('Exported profile: ' + profiles[currentProfile].name);
}
// Import data from JSON file into current profile
function loadFromFile(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
railroadData = data;
// Save to current profile
if (profiles[currentProfile]) {
profiles[currentProfile].data = railroadData;
saveProfiles();
}
// Refresh all displays
renderSwitchList();
renderTasks();
renderTrains();
renderDrivingLogs();
renderAICrew();
renderCargo();
loadNotes();
updateDashboard();
showNotification('Data imported to profile: ' + profiles[currentProfile].name);
} catch (err) {
alert('Error loading file: ' + err.message);
}
};
reader.readAsText(file);
}
function showSaveIndicator() {
// Create or show save indicator
let indicator = document.getElementById('saveIndicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'saveIndicator';
indicator.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #27ae60; color: #fff; padding: 10px 20px; border-radius: 5px; font-weight: bold; z-index: 10000; box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: opacity 0.3s;';
indicator.innerHTML = '💾 Saved to File';
document.body.appendChild(indicator);
}
indicator.style.opacity = '1';
indicator.style.display = 'block';
// Hide after 2 seconds
setTimeout(() => {
indicator.style.opacity = '0';
setTimeout(() => {
indicator.style.display = 'none';
}, 300);
}, 2000);
}
function showNotification(message) {
// Create or show notification
let notification = document.getElementById('notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'notification';
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #3498db; color: #fff; padding: 10px 20px; border-radius: 5px; font-weight: bold; z-index: 10000; box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: opacity 0.3s;';
document.body.appendChild(notification);
}
notification.textContent = message;
notification.style.opacity = '1';
notification.style.display = 'block';
// Hide after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => {
notification.style.display = 'none';
}, 300);
}, 3000);
}
// Tab switching
function switchTab(tabName) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.nav-tab').forEach(tab => {
tab.classList.remove('active');
});
document.getElementById(tabName).classList.add('active');
event.target.classList.add('active');
}
// Time management
let useSyncFile = false;
// Store the sync file handle for continuous reading
let syncFileHandle = null;
let syncFileWatcher = null;
let autoCheckAttempted = false;
// Try to load time from sync file using File System Access API
async function loadSyncTime() {
// If we don't have a file handle yet, can't read
if (!syncFileHandle) {
useSyncFile = false;
return null;
}
// Use file handle method
try {
const file = await syncFileHandle.getFile();
const text = await file.text();
const syncData = JSON.parse(text);
// Check if data is recent (within last 10 seconds)
const syncTimestamp = new Date(syncData.timestamp);
const now = new Date();
const ageSeconds = (now - syncTimestamp) / 1000;
if (ageSeconds < 10) {
useSyncFile = true;
return syncData;
}
} catch (e) {
console.error('Failed to read sync file:', e.message);
}
useSyncFile = false;
return null;
}
// Try to auto-detect sync file - browsers block this due to CORS
async function tryAutoDetectSyncFile() {
// Unfortunately browsers block file:// access for security
// User must use the file picker once per session
return false;
}
// Select the sync file once
async function selectSyncFile() {
try {
const [fileHandle] = await window.showOpenFilePicker({
types: [{
description: 'Time Sync File (railroad-time-sync.json)',
accept: {'application/json': ['.json']}
}],
multiple: false,
suggestedName: 'railroad-time-sync.json'
});
syncFileHandle = fileHandle;
// Store the file name so we can show it
localStorage.setItem('syncFileName', (await fileHandle.getFile()).name);
showNotification('Sync file connected! Time will update automatically.');
document.getElementById('selectSyncBtn').textContent = '✅ Connected to Game Time';
document.getElementById('selectSyncBtn').style.background = '#27ae60';
document.getElementById('selectSyncBtn').disabled = true;
updateClock();
} catch (e) {
if (e.name !== 'AbortError') {
console.error('File selection failed:', e);
}
}
}
// On page load, check if we had a connection before
async function checkPreviousConnection() {
// Try to auto-connect to sync file in same directory
try {
const response = await fetch('./railroad-time-sync.json');
if (response.ok) {
useSyncFile = true;
showNotification('Auto-connected to sync file!');
document.getElementById('selectSyncBtn').textContent = '✅ Connected to Game Time';
document.getElementById('selectSyncBtn').style.background = '#27ae60';
return;
}
} catch (e) {
// Sync file not in same directory, show reconnect option
}
const fileName = localStorage.getItem('syncFileName');
if (fileName) {
const btn = document.getElementById('selectSyncBtn');
btn.innerHTML = `📂 Reconnect to ${fileName}`;
btn.style.background = '#f39c12';
}
}
// Update clock
async function updateClock() {
let hours, minutes, seconds;
let syncIndicator = '';
// Try to get time from sync file first
const syncData = await loadSyncTime();
if (syncData && useSyncFile) {
// Use game time from sync file
hours = syncData.hours;
minutes = syncData.minutes;
seconds = syncData.seconds;
syncIndicator = ' 🎮';
showSyncMessage(false);
updateSyncStatus(true, syncData.isPaused);
} else {
// Use real time
const now = new Date();
hours = now.getHours();
minutes = now.getMinutes();
seconds = now.getSeconds();
showSyncMessage(true);
updateSyncStatus(false);
}
// Format time as 24-hour with seconds
const timeStr = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}${syncIndicator}`;
const dateStr = new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
document.getElementById('currentTime').textContent = timeStr;
document.getElementById('currentDate').textContent = dateStr;
// Create a Date object with current time for next departure calculation
const timeForCalc = new Date();
timeForCalc.setHours(hours, minutes, seconds);
updateNextDeparture(timeForCalc);
}
function updateSyncStatus(isConnected, isPaused = false) {
const statusEl = document.getElementById('syncStatus');
if (!statusEl) return;
if (isConnected) {
if (isPaused) {
statusEl.innerHTML = '⏸️ <span style="color: #f39c12;">Connected (Paused)</span>';
} else {
statusEl.innerHTML = '🟢 <span style="color: #27ae60;">Connected & Syncing</span>';
}
} else {
statusEl.innerHTML = '⚪ <span style="color: #888;">Not Connected</span>';
}
}
function showSyncMessage(show) {
const messageEl = document.getElementById('syncMessage');
if (messageEl) {
messageEl.style.display = show ? 'block' : 'none';
}
}
function setManualTime() {
const timeInput = document.getElementById('manualTimeInput').value;
if (!timeInput) {
alert('Please enter a time');
return;
}
const now = new Date();
const [hours, minutes] = timeInput.split(':');
const targetTime = new Date(now);
targetTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
timeOffset = targetTime.getTime() - now.getTime();
useManualTime = true;
updateClock();
showNotification('Time synced to ' + timeInput);
}
function resetToRealTime() {
timeOffset = 0;
useManualTime = false;
document.getElementById('manualTimeInput').value = '';
updateClock();
showNotification('Time reset to real time');
}
function updateNextDeparture(currentTime) {
// Use profile's timetable for next departure
let profileTimetable = timetable;
console.log('updateNextDeparture called with timetable length:', profileTimetable ? profileTimetable.length : 0);
if (!profileTimetable || profileTimetable.length === 0) {
document.getElementById('nextDepartureTrain').textContent = 'No timetable';
document.getElementById('nextDepartureTime').textContent = 'Upload timetable in profile settings';
return;
}
// Convert profile timetable to format needed for next departure
const departures = profileTimetable.map(t => ({
train: t.train,
time: t.departs,
station: t.station
}));
const currentMinutes = currentTime.getHours() * 60 + currentTime.getMinutes();
let nextDep = null;
let minDiff = Infinity;
for (const dep of departures) {
if (!dep.time) continue;
const timeParts = dep.time.split(':');
if (timeParts.length < 2) continue;
const hours = parseInt(timeParts[0]);
const minutes = parseInt(timeParts[1]);
if (isNaN(hours) || isNaN(minutes)) continue;
const depMinutes = hours * 60 + minutes;
let diff = depMinutes - currentMinutes;
// Handle wrap around midnight
if (diff < 0) diff += 1440; // 24 hours in minutes
if (diff < minDiff) {
minDiff = diff;
nextDep = dep;
}
}
if (nextDep) {
console.log('Next departure found:', nextDep.train, 'at', nextDep.time, 'from', nextDep.station, 'in', minDiff, 'minutes');
document.getElementById('nextDepartureTrain').textContent = nextDep.train;
const hoursUntil = Math.floor(minDiff / 60);
const minsUntil = minDiff % 60;
let timeUntil = '';
if (hoursUntil > 0) timeUntil += hoursUntil + 'h ';
timeUntil += minsUntil + 'm';
document.getElementById('nextDepartureTime').textContent =
nextDep.time + ' from ' + nextDep.station + ' (in ' + timeUntil + ')';
} else {
console.error('No next departure found. Checked', departures.length, 'departures');
document.getElementById('nextDepartureTrain').textContent = '--';
document.getElementById('nextDepartureTime').textContent = 'No departures found';
}
}
// Switch List Functions
function addSwitchItem() {
const item = {
id: Date.now(),
carNumber: document.getElementById('switchCarNumber').value,
carType: document.getElementById('switchCarType').value,
origin: document.getElementById('switchOrigin').value,
destination: document.getElementById('switchDestination').value,
cargo: document.getElementById('switchCargo').value,
track: document.getElementById('switchTrack').value,
priority: document.getElementById('switchPriority').value,
notes: document.getElementById('switchNotes').value,
completed: false,
timestamp: new Date().toISOString()
};
if (!item.carNumber) {
alert('Please enter a car number');
return;
}
const data = loadData('switchlist');
data.push(item);
saveData('switchlist', data);
renderSwitchList();
// Clear form
document.getElementById('switchCarNumber').value = '';
document.getElementById('switchOrigin').value = '';
document.getElementById('switchDestination').value = '';
document.getElementById('switchCargo').value = '';
document.getElementById('switchTrack').value = '';
document.getElementById('switchNotes').value = '';
}
function renderSwitchList() {
const data = loadData('switchlist');
const container = document.getElementById('switchListItems');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No switch list items yet. Add one above!</div>';
return;
}
container.innerHTML = data.map(item => `
<div class="list-item ${item.completed ? 'completed' : ''} priority-${item.priority}">
<div class="list-item-header">
<div class="list-item-title">
<span class="badge badge-priority-${item.priority}">${item.priority.toUpperCase()}</span>
${item.carNumber} - ${item.carType}
</div>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editSwitchItem(${item.id})">Edit</button>
<button class="btn btn-info" onclick="toggleSwitchComplete(${item.id})">
${item.completed ? 'Undo' : 'Complete'}
</button>
<button class="btn btn-danger" onclick="deleteSwitchItem(${item.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
<strong>From:</strong> ${item.origin || 'N/A'} → <strong>To:</strong> ${item.destination || 'N/A'}<br>
<strong>Cargo:</strong> ${item.cargo || 'Empty'} | <strong>Track:</strong> ${item.track || 'N/A'}<br>
${item.notes ? `<strong>Notes:</strong> ${item.notes}<br>` : ''}
<small>Added: ${new Date(item.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editSwitchItem(id) {
const data = loadData('switchlist');
const item = data.find(i => i.id === id);
if (!item) return;
document.getElementById('carNumber').value = item.carNumber || '';
document.getElementById('carType').value = item.carType || '';
document.getElementById('origin').value = item.origin || '';
document.getElementById('destination').value = item.destination || '';
document.getElementById('cargo').value = item.cargo || '';
document.getElementById('track').value = item.track || '';
document.getElementById('switchPriority').value = item.priority || 'normal';
document.getElementById('switchNotes').value = item.notes || '';
// Remove without confirmation prompt
const filtered = data.filter(i => i.id !== id);
saveData('switchlist', filtered);
renderSwitchList();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing switch list item - make changes and click Add to update');
}
function toggleSwitchComplete(id) {
const data = loadData('switchlist');
const item = data.find(i => i.id === id);
if (item) {
item.completed = !item.completed;
saveData('switchlist', data);
renderSwitchList();
}
}
function deleteSwitchItem(id) {
if (confirm('Delete this switch list item?')) {
const data = loadData('switchlist').filter(i => i.id !== id);
saveData('switchlist', data);
renderSwitchList();
}
}
// Task Functions
function addTask() {
const task = {
id: Date.now(),
title: document.getElementById('taskTitle').value,
description: document.getElementById('taskDescription').value,
priority: document.getElementById('taskPriority').value,
category: document.getElementById('taskCategory').value,
completed: false,
timestamp: new Date().toISOString()
};
if (!task.title) {
alert('Please enter a task title');
return;
}
const data = loadData('tasks');
data.push(task);
saveData('tasks', data);
renderTasks();
document.getElementById('taskTitle').value = '';
document.getElementById('taskDescription').value = '';
}
function renderTasks() {
const data = loadData('tasks');
const container = document.getElementById('taskList');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No tasks yet. Add one above!</div>';
return;
}
container.innerHTML = data.map(task => `
<div class="list-item ${task.completed ? 'completed' : ''} priority-${task.priority}">
<div class="list-item-header">
<div class="list-item-title">
<span class="badge badge-priority-${task.priority}">${task.priority.toUpperCase()}</span>
<span class="badge" style="background: #9b59b6;">${task.category}</span>
${task.title}
</div>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editTask(${task.id})">Edit</button>
<button class="btn btn-info" onclick="toggleTaskComplete(${task.id})">
${task.completed ? 'Undo' : 'Complete'}
</button>
<button class="btn btn-danger" onclick="deleteTask(${task.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
${task.description ? `${task.description}<br>` : ''}
<small>Added: ${new Date(task.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editTask(id) {
const data = loadData('tasks');
const task = data.find(t => t.id === id);
if (!task) return;
document.getElementById('taskTitle').value = task.title || '';
document.getElementById('taskDescription').value = task.description || '';
document.getElementById('taskPriority').value = task.priority || 'normal';
document.getElementById('taskCategory').value = task.category || 'General';
// Remove without confirmation prompt
const filtered = data.filter(t => t.id !== id);
saveData('tasks', filtered);
renderTasks();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing task - make changes and click Add to update');
}
function toggleTaskComplete(id) {
const data = loadData('tasks');
const task = data.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
saveData('tasks', data);
renderTasks();
}
}
function deleteTask(id) {
if (confirm('Delete this task?')) {
const data = loadData('tasks').filter(t => t.id !== id);
saveData('tasks', data);
renderTasks();
}
}
// Train Management Functions
function addTrain() {
const train = {
id: Date.now(),
number: document.getElementById('trainNumber').value,
locos: document.getElementById('trainLocos').value,
location: document.getElementById('trainLocation').value,
destination: document.getElementById('trainDestination').value,
cars: document.getElementById('trainCars').value,
status: document.getElementById('trainStatus').value,
notes: document.getElementById('trainNotes').value,
timestamp: new Date().toISOString()
};
if (!train.number) {
alert('Please enter a train number');
return;
}
const data = loadData('trains');
data.push(train);
saveData('trains', data);
renderTrains();
document.getElementById('trainNumber').value = '';
document.getElementById('trainLocos').value = '';
document.getElementById('trainLocation').value = '';
document.getElementById('trainDestination').value = '';
document.getElementById('trainCars').value = '';
document.getElementById('trainNotes').value = '';
}
function renderTrains() {
const data = loadData('trains');
const container = document.getElementById('trainList');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No trains registered. Add one above!</div>';
return;
}
container.innerHTML = data.map(train => `
<div class="train-card">
<div class="list-item-header">
<h4>🚂 ${train.number}</h4>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editTrain(${train.id})">Edit</button>
<button class="btn btn-danger" onclick="deleteTrain(${train.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
<strong>Locomotive(s):</strong> ${train.locos || 'N/A'}<br>
<strong>Location:</strong> ${train.location || 'Unknown'} → <strong>Destination:</strong> ${train.destination || 'N/A'}<br>
<strong>Cars:</strong> ${train.cars || '0'} | <strong>Status:</strong> <span class="badge badge-status">${train.status}</span><br>
${train.notes ? `<strong>Notes:</strong> ${train.notes}<br>` : ''}
<small>Added: ${new Date(train.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editTrain(id) {
const data = loadData('trains');
const train = data.find(t => t.id === id);
if (!train) return;
document.getElementById('trainNumber').value = train.number || '';
document.getElementById('trainLocos').value = train.locos || '';
document.getElementById('trainLocation').value = train.location || '';
document.getElementById('trainDestination').value = train.destination || '';
document.getElementById('trainCars').value = train.cars || '';
document.getElementById('trainStatus').value = train.status || 'Active';
document.getElementById('trainNotes').value = train.notes || '';
// Remove without confirmation prompt
const filtered = data.filter(t => t.id !== id);
saveData('trains', filtered);
renderTrains();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing train - make changes and click Add to update');
}
function deleteTrain(id) {
if (confirm('Delete this train?')) {
const data = loadData('trains').filter(t => t.id !== id);
saveData('trains', data);
renderTrains();
}
}
// Driving Log Functions
function addDrivingLog() {
const log = {
id: Date.now(),
locoNumber: document.getElementById('driveLocoNumber').value,
route: document.getElementById('driveRoute').value,
startTime: document.getElementById('driveStartTime').value,
endTime: document.getElementById('driveEndTime').value,
distance: document.getElementById('driveDistance').value,
fuel: document.getElementById('driveFuel').value,
notes: document.getElementById('driveNotes').value,
timestamp: new Date().toISOString()
};
if (!log.locoNumber) {
alert('Please enter a locomotive number');
return;
}
const data = loadData('driving');
data.push(log);
saveData('driving', data);
renderDrivingLogs();
document.getElementById('driveLocoNumber').value = '';
document.getElementById('driveRoute').value = '';
document.getElementById('driveStartTime').value = '';
document.getElementById('driveEndTime').value = '';
document.getElementById('driveDistance').value = '';
document.getElementById('driveFuel').value = '';
document.getElementById('driveNotes').value = '';
}
function renderDrivingLogs() {
const data = loadData('driving');
const container = document.getElementById('drivingLogList');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No driving logs yet. Add one above!</div>';
return;
}
container.innerHTML = data.map(log => `
<div class="list-item">
<div class="list-item-header">
<div class="list-item-title">🚂 ${log.locoNumber} - ${log.route || 'N/A'}</div>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editDrivingLog(${log.id})">Edit</button>
<button class="btn btn-danger" onclick="deleteDrivingLog(${log.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
<strong>Time:</strong> ${log.startTime || 'N/A'} - ${log.endTime || 'N/A'}<br>
${log.distance ? `<strong>Distance:</strong> ${log.distance} | ` : ''}
${log.fuel ? `<strong>Fuel:</strong> ${log.fuel}<br>` : '<br>'}
${log.notes ? `<strong>Notes:</strong> ${log.notes}<br>` : ''}
<small>Logged: ${new Date(log.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editDrivingLog(id) {
const data = loadData('driving');
const log = data.find(l => l.id === id);
if (!log) return;
document.getElementById('driveLocoNumber').value = log.locoNumber || '';
document.getElementById('driveRoute').value = log.route || '';
document.getElementById('driveStartTime').value = log.startTime || '';
document.getElementById('driveEndTime').value = log.endTime || '';
document.getElementById('driveDistance').value = log.distance || '';
document.getElementById('driveFuel').value = log.fuel || '';
document.getElementById('driveNotes').value = log.notes || '';
// Remove without confirmation prompt
const filtered = data.filter(l => l.id !== id);
saveData('driving', filtered);
renderDrivingLogs();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing driving log - make changes and click Add to update');
}
function deleteDrivingLog(id) {
if (confirm('Delete this driving log?')) {
const data = loadData('driving').filter(l => l.id !== id);
saveData('driving', data);
renderDrivingLogs();
}
}
// AI Crew Functions
function addAICrew() {
const ai = {
id: Date.now(),
name: document.getElementById('aiName').value,
role: document.getElementById('aiRole').value,
assignment: document.getElementById('aiAssignment').value,
task: document.getElementById('aiTask').value,
status: document.getElementById('aiStatus').value,
notes: document.getElementById('aiNotes').value,
timestamp: new Date().toISOString()
};
if (!ai.name) {
alert('Please enter an AI name/ID');
return;
}
const data = loadData('ai');
data.push(ai);
saveData('ai', data);
renderAICrew();
document.getElementById('aiName').value = '';
document.getElementById('aiAssignment').value = '';
document.getElementById('aiTask').value = '';
document.getElementById('aiNotes').value = '';
}
function renderAICrew() {
const data = loadData('ai');
const container = document.getElementById('aiCrewList');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No AI crew members registered. Add one above!</div>';
return;
}
container.innerHTML = data.map(ai => `
<div class="list-item">
<div class="list-item-header">
<div class="list-item-title">
🤖 ${ai.name}
<span class="badge" style="background: #3498db;">${ai.role}</span>
<span class="badge badge-status">${ai.status}</span>
</div>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editAICrew(${ai.id})">Edit</button>
<button class="btn btn-danger" onclick="deleteAICrew(${ai.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
<strong>Assignment:</strong> ${ai.assignment || 'None'}<br>
<strong>Current Task:</strong> ${ai.task || 'Idle'}<br>
${ai.notes ? `<strong>Notes:</strong> ${ai.notes}<br>` : ''}
<small>Added: ${new Date(ai.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editAICrew(id) {
const data = loadData('ai');
const ai = data.find(a => a.id === id);
if (!ai) return;
document.getElementById('aiName').value = ai.name || '';
document.getElementById('aiRole').value = ai.role || 'Engineer';
document.getElementById('aiAssignment').value = ai.assignment || '';
document.getElementById('aiTask').value = ai.task || '';
document.getElementById('aiStatus').value = ai.status || 'Active';
document.getElementById('aiNotes').value = ai.notes || '';
// Remove without confirmation prompt
const filtered = data.filter(a => a.id !== id);
saveData('ai', filtered);
renderAICrew();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing AI crew - make changes and click Add to update');
}
function deleteAICrew(id) {
if (confirm('Delete this AI crew member?')) {
const data = loadData('ai').filter(a => a.id !== id);
saveData('ai', data);
renderAICrew();
}
}
// Cargo Functions
function addCargo() {
const cargo = {
id: Date.now(),
cargoId: document.getElementById('cargoId').value,
type: document.getElementById('cargoType').value,
weight: document.getElementById('cargoWeight').value,
shipper: document.getElementById('cargoShipper').value,
consignee: document.getElementById('cargoConsignee').value,
origin: document.getElementById('cargoOrigin').value,
destination: document.getElementById('cargoDestination').value,
status: document.getElementById('cargoStatus').value,
notes: document.getElementById('cargoNotes').value,
timestamp: new Date().toISOString()
};
if (!cargo.cargoId) {
alert('Please enter a cargo ID');
return;
}
const data = loadData('cargo');
data.push(cargo);
saveData('cargo', data);
renderCargo();
document.getElementById('cargoId').value = '';
document.getElementById('cargoType').value = '';
document.getElementById('cargoWeight').value = '';
document.getElementById('cargoShipper').value = '';
document.getElementById('cargoConsignee').value = '';
document.getElementById('cargoOrigin').value = '';
document.getElementById('cargoDestination').value = '';
document.getElementById('cargoNotes').value = '';
}
function renderCargo() {
const data = loadData('cargo');
const container = document.getElementById('cargoList');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No cargo tracked yet. Add one above!</div>';
return;
}
container.innerHTML = data.map(cargo => `
<div class="list-item">
<div class="list-item-header">
<div class="list-item-title">
📦 ${cargo.cargoId} - ${cargo.type}
<span class="badge badge-status">${cargo.status}</span>
</div>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editCargo(${cargo.id})">Edit</button>
<button class="btn btn-danger" onclick="deleteCargo(${cargo.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
<strong>Weight:</strong> ${cargo.weight || 'N/A'}<br>
<strong>Shipper:</strong> ${cargo.shipper || 'N/A'} → <strong>Consignee:</strong> ${cargo.consignee || 'N/A'}<br>
<strong>From:</strong> ${cargo.origin || 'N/A'} → <strong>To:</strong> ${cargo.destination || 'N/A'}<br>
${cargo.notes ? `<strong>Notes:</strong> ${cargo.notes}<br>` : ''}
<small>Added: ${new Date(cargo.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editCargo(id) {
const data = loadData('cargo');
const cargo = data.find(c => c.id === id);
if (!cargo) return;
document.getElementById('cargoId').value = cargo.cargoId || '';
document.getElementById('cargoType').value = cargo.type || '';
document.getElementById('cargoWeight').value = cargo.weight || '';
document.getElementById('cargoShipper').value = cargo.shipper || '';
document.getElementById('cargoConsignee').value = cargo.consignee || '';
document.getElementById('cargoOrigin').value = cargo.origin || '';
document.getElementById('cargoDestination').value = cargo.destination || '';
document.getElementById('cargoStatus').value = cargo.status || 'Pending';
document.getElementById('cargoNotes').value = cargo.notes || '';
// Remove without confirmation prompt
const filtered = data.filter(c => c.id !== id);
saveData('cargo', filtered);
renderCargo();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing cargo - make changes and click Add to update');
}
function deleteCargo(id) {
if (confirm('Delete this cargo entry?')) {
const data = loadData('cargo').filter(c => c.id !== id);
saveData('cargo', data);
renderCargo();
}
}
// Notes Functions
function addNote() {
const note = {
id: Date.now(),
title: document.getElementById('noteTitle').value,
content: document.getElementById('noteContent').value,
timestamp: new Date().toISOString()
};
if (!note.title || !note.content) {
alert('Please enter both title and content');
return;
}
const data = loadData('notes');
data.push(note);
saveData('notes', data);
renderNotes();
document.getElementById('noteTitle').value = '';
document.getElementById('noteContent').value = '';
}
function renderNotes() {
const data = loadData('notes');
const container = document.getElementById('notesList');
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">No notes saved yet. Add one above!</div>';
return;
}
container.innerHTML = data.map(note => `
<div class="list-item">
<div class="list-item-header">
<div class="list-item-title">📝 ${note.title}</div>
<div class="list-item-actions">
<button class="btn btn-warning" onclick="editNote(${note.id})">Edit</button>
<button class="btn btn-danger" onclick="deleteNote(${note.id})">Delete</button>
</div>
</div>
<div class="list-item-details">
${note.content.replace(/\n/g, '<br>')}<br>
<small>Saved: ${new Date(note.timestamp).toLocaleString()}</small>
</div>
</div>
`).join('');
}
function editNote(id) {
const data = loadData('notes');
const note = data.find(n => n.id === id);
if (!note) return;
document.getElementById('noteTitle').value = note.title || '';
document.getElementById('noteContent').value = note.content || '';
// Remove without confirmation prompt
const filtered = data.filter(n => n.id !== id);
saveData('notes', filtered);
renderNotes();
window.scrollTo({ top: 0, behavior: 'smooth' });
showNotification('Editing note - make changes and click Save to update');
}
function deleteNote(id) {
if (confirm('Delete this note?')) {
const data = loadData('notes').filter(n => n.id !== id);
saveData('notes', data);
renderNotes();
}
}
// Dashboard Functions
function updateDashboard() {
const tasks = loadData('tasks').filter(t => !t.completed);
const switchItems = loadData('switchlist').filter(s => !s.completed);
const trains = loadData('trains');
const aiCrew = loadData('ai');
document.getElementById('statActiveTasks').textContent = tasks.length;
document.getElementById('statSwitchItems').textContent = switchItems.length;
document.getElementById('statActiveTrains').textContent = trains.length;
document.getElementById('statAICrew').textContent = aiCrew.length;
// Show priority items
const priorityItems = document.getElementById('priorityItems');
const highPriorityTasks = tasks.filter(t => t.priority === 'high');
const highPrioritySwitches = switchItems.filter(s => s.priority === 'high');
if (highPriorityTasks.length === 0 && highPrioritySwitches.length === 0) {
priorityItems.innerHTML = '<div class="empty-state">No high priority items at the moment!</div>';
} else {
let html = '';
if (highPriorityTasks.length > 0) {
html += '<h3>High Priority Tasks</h3>';
html += highPriorityTasks.map(task => `
<div class="list-item priority-high">
<div class="list-item-title">✓ ${task.title}</div>
<div class="list-item-details">${task.description}</div>
</div>
`).join('');
}
if (highPrioritySwitches.length > 0) {
html += '<h3>High Priority Switch Items</h3>';
html += highPrioritySwitches.map(item => `
<div class="list-item priority-high">
<div class="list-item-title">📋 ${item.carNumber} - ${item.carType}</div>
<div class="list-item-details">
${item.origin}${item.destination}
</div>
</div>
`).join('');
}
priorityItems.innerHTML = html;
}
}
function clearSection(section) {
if (confirm(`Clear all ${section} data? This cannot be undone!`)) {
saveData(section, []);
renderAll();
showNotification(`${section} data cleared!`);
}
}
function clearAllData() {
if (confirm('⚠️ CLEAR ALL DATA? This will delete everything and cannot be undone!')) {
if (confirm('Are you ABSOLUTELY sure? This is your last warning!')) {
railroadData = {
switchlist: [],
tasks: [],
trains: [],
driving: [],
ai: [],
cargo: [],
notes: []
};
renderAll();
saveToFile();
showNotification('All data has been cleared!');
}
}
}
// Render all sections
function renderAll() {
renderSwitchList();
renderTasks();
renderTrains();
renderDrivingLogs();
renderAICrew();
renderCargo();
renderNotes();
updateDashboard();
}
// Initialize on page load
async function init() {
await loadProfiles();
if (profiles[currentProfile]) {
railroadData = JSON.parse(JSON.stringify(profiles[currentProfile].data));
if (profiles[currentProfile].timetable) {
timetable = profiles[currentProfile].timetable;
}
}
checkPreviousConnection();
updateClock();
setInterval(updateClock, 1000);
renderAll();
}
init();
</script>
</body>
</html>