2548 lines
106 KiB
HTML
2548 lines
106 KiB
HTML
<!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> |