helg / script.js
Subham9126's picture
Rename mntes.js to script.js
3f9c475 verified
Raw
History Blame Contribute Delete
40.1 kB
// Vehicle Maintenance Management System (VMMS)
// Main JavaScript File
class VMMS {
constructor() {
this.config = this.loadConfig();
this.data = {
vehicles: [],
parts: [],
logs: []
};
this.init();
}
// Initialize the application
init() {
this.setupEventListeners();
this.loadPage('dashboard');
// Load data if config exists
if (this.config.sheetId) {
this.loadAllData();
}
}
// Setup all event listeners
setupEventListeners() {
// Navigation
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const page = e.target.getAttribute('data-page');
this.loadPage(page);
});
});
// Vehicle management
document.getElementById('add-vehicle-btn').addEventListener('click', () => this.openVehicleModal());
document.getElementById('vehicle-form').addEventListener('submit', (e) => this.saveVehicle(e));
document.getElementById('vehicle-cancel-btn').addEventListener('click', () => this.closeModal('vehicle-modal'));
document.getElementById('vehicle-modal-close').addEventListener('click', () => this.closeModal('vehicle-modal'));
// Parts management
document.getElementById('parts-vehicle-select').addEventListener('change', (e) => this.loadPartsByVehicle(e.target.value));
document.getElementById('add-part-btn').addEventListener('click', () => this.openPartModal());
document.getElementById('part-form').addEventListener('submit', (e) => this.savePart(e));
document.getElementById('part-cancel-btn').addEventListener('click', () => this.closeModal('part-modal'));
document.getElementById('part-modal-close').addEventListener('click', () => this.closeModal('part-modal'));
// Logs management
document.getElementById('logs-part-select').addEventListener('change', (e) => this.loadLogsByPart(e.target.value));
document.getElementById('add-log-btn').addEventListener('click', () => this.openLogModal());
document.getElementById('log-form').addEventListener('submit', (e) => this.saveLog(e));
document.getElementById('log-cancel-btn').addEventListener('click', () => this.closeModal('log-modal'));
document.getElementById('log-modal-close').addEventListener('click', () => this.closeModal('log-modal'));
// Configuration
document.getElementById('config-form').addEventListener('submit', (e) => this.saveConfig(e));
document.getElementById('test-connection-btn').addEventListener('click', () => this.testConnection());
// Modal click outside to close
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
this.closeModal(e.target.id);
}
});
}
// Load and display specific page
loadPage(pageId) {
// Hide all pages
document.querySelectorAll('.page').forEach(page => {
page.classList.remove('active');
});
// Remove active class from nav links
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
});
// Show selected page
document.getElementById(pageId).classList.add('active');
document.querySelector(`[data-page="${pageId}"]`).classList.add('active');
// Load page-specific data
switch (pageId) {
case 'dashboard':
this.loadDashboard();
break;
case 'vehicles':
this.loadVehicles();
break;
case 'parts':
this.loadParts();
break;
case 'logs':
this.loadLogs();
break;
case 'config':
this.loadConfigForm();
break;
}
}
// Configuration Management
loadConfig() {
const config = localStorage.getItem('vmms-config');
return config ? JSON.parse(config) : {
sheetId: '',
vehiclesTab: 'Vehicles',
partsTab: 'Parts',
logsTab: 'MaintenanceLogs'
};
}
saveConfig(e) {
e.preventDefault();
const formData = new FormData(e.target);
this.config = {
sheetId: formData.get('sheet-id') || document.getElementById('sheet-id').value,
vehiclesTab: formData.get('vehicles-tab') || document.getElementById('vehicles-tab').value,
partsTab: formData.get('parts-tab') || document.getElementById('parts-tab').value,
logsTab: formData.get('logs-tab') || document.getElementById('logs-tab').value
};
localStorage.setItem('vmms-config', JSON.stringify(this.config));
this.showToast('Configuration saved successfully!', 'success');
// Load data after saving config
this.loadAllData();
}
loadConfigForm() {
document.getElementById('sheet-id').value = this.config.sheetId;
document.getElementById('vehicles-tab').value = this.config.vehiclesTab;
document.getElementById('parts-tab').value = this.config.partsTab;
document.getElementById('logs-tab').value = this.config.logsTab;
}
async testConnection() {
if (!this.config.sheetId) {
this.showToast('Please enter a Sheet ID first', 'error');
return;
}
this.showLoading();
try {
const url = `https://docs.google.com/spreadsheets/d/${this.config.sheetId}/gviz/tq?tqx=out:json&sheet=${this.config.vehiclesTab}`;
const response = await fetch(url);
if (response.ok) {
this.showConnectionStatus('Connection successful! Sheet is accessible.', 'success');
} else {
throw new Error('Failed to connect');
}
} catch (error) {
this.showConnectionStatus('Connection failed. Please check your Sheet ID and permissions.', 'error');
} finally {
this.hideLoading();
}
}
showConnectionStatus(message, type) {
const statusDiv = document.getElementById('connection-status');
statusDiv.textContent = message;
statusDiv.className = `status-message ${type}`;
statusDiv.style.display = 'block';
setTimeout(() => {
statusDiv.style.display = 'none';
}, 5000);
}
// Data Management
async loadAllData() {
if (!this.config.sheetId) return;
this.showLoading();
try {
await Promise.all([
this.loadVehiclesData(),
this.loadPartsData(),
this.loadLogsData()
]);
this.loadDashboard();
this.showToast('Data loaded successfully!', 'success');
} catch (error) {
console.error('Error loading data:', error);
this.showToast('Error loading data. Please check your configuration.', 'error');
} finally {
this.hideLoading();
}
}
async loadVehiclesData() {
try {
const url = `https://docs.google.com/spreadsheets/d/${this.config.sheetId}/gviz/tq?tqx=out:json&sheet=${this.config.vehiclesTab}`;
const response = await fetch(url);
const text = await response.text();
const data = this.parseGoogleSheetsData(text);
this.data.vehicles = data.map(row => ({
id: row[0],
name: row[1],
model: row[2],
year: row[3],
registration: row[4],
odometer: row[5],
fuelType: row[6]
}));
} catch (error) {
console.error('Error loading vehicles:', error);
this.data.vehicles = [];
}
}
async loadPartsData() {
try {
const url = `https://docs.google.com/spreadsheets/d/${this.config.sheetId}/gviz/tq?tqx=out:json&sheet=${this.config.partsTab}`;
const response = await fetch(url);
const text = await response.text();
const data = this.parseGoogleSheetsData(text);
this.data.parts = data.map(row => ({
id: row[0],
vehicleId: row[1],
name: row[2],
type: row[3],
value: row[4],
notes: row[5] || ''
}));
} catch (error) {
console.error('Error loading parts:', error);
this.data.parts = [];
}
}
async loadLogsData() {
try {
const url = `https://docs.google.com/spreadsheets/d/${this.config.sheetId}/gviz/tq?tqx=out:json&sheet=${this.config.logsTab}`;
const response = await fetch(url);
const text = await response.text();
const data = this.parseGoogleSheetsData(text);
this.data.logs = data.map(row => ({
id: row[0],
partId: row[1],
date: row[2],
odometer: row[3],
notes: row[4] || ''
}));
} catch (error) {
console.error('Error loading logs:', error);
this.data.logs = [];
}
}
parseGoogleSheetsData(text) {
try {
// Remove Google Sheets wrapper
const jsonString = text.match(/google\.visualization\.Query\.setResponse\((.*)\);/)[1];
const data = JSON.parse(jsonString);
if (!data.table || !data.table.rows) {
return [];
}
return data.table.rows.map(row =>
row.c ? row.c.map(cell => cell ? cell.v : '') : []
);
} catch (error) {
console.error('Error parsing Google Sheets data:', error);
return [];
}
}
// Dashboard Management
loadDashboard() {
const totalVehicles = this.data.vehicles.length;
const totalParts = this.data.parts.length;
// Calculate overdue and upcoming maintenance
const alerts = this.calculateMaintenanceAlerts();
const overdueCount = alerts.filter(alert => alert.type === 'overdue').length;
const upcomingCount = alerts.filter(alert => alert.type === 'upcoming').length;
// Update dashboard cards
document.getElementById('total-vehicles').textContent = totalVehicles;
document.getElementById('total-parts').textContent = totalParts;
document.getElementById('overdue-parts').textContent = overdueCount;
document.getElementById('upcoming-parts').textContent = upcomingCount;
// Display alerts
this.displayAlerts(alerts);
}
calculateMaintenanceAlerts() {
const alerts = [];
const today = new Date();
const oneMonthFromNow = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
this.data.parts.forEach(part => {
const vehicle = this.data.vehicles.find(v => v.id === part.vehicleId);
if (!vehicle) return;
const partLogs = this.data.logs.filter(log => log.partId === part.id);
const lastLog = partLogs.sort((a, b) => new Date(b.date) - new Date(a.date))[0];
if (!lastLog) {
// No maintenance history - consider it overdue
alerts.push({
type: 'overdue',
vehicle: vehicle.name,
part: part.name,
message: 'No maintenance history'
});
return;
}
const lastLogDate = new Date(lastLog.date);
if (part.type === 'month') {
const nextDueDate = new Date(lastLogDate.getTime() + parseInt(part.value) * 30 * 24 * 60 * 60 * 1000);
if (nextDueDate < today) {
alerts.push({
type: 'overdue',
vehicle: vehicle.name,
part: part.name,
message: `Due ${Math.ceil((today - nextDueDate) / (1000 * 60 * 60 * 24))} days ago`
});
} else if (nextDueDate < oneMonthFromNow) {
alerts.push({
type: 'upcoming',
vehicle: vehicle.name,
part: part.name,
message: `Due in ${Math.ceil((nextDueDate - today) / (1000 * 60 * 60 * 24))} days`
});
}
} else if (part.type === 'km') {
const nextDueKm = parseInt(lastLog.odometer) + parseInt(part.value);
const currentKm = parseInt(vehicle.odometer);
if (currentKm >= nextDueKm) {
alerts.push({
type: 'overdue',
vehicle: vehicle.name,
part: part.name,
message: `Overdue by ${currentKm - nextDueKm} km`
});
} else if ((nextDueKm - currentKm) <= 500) {
alerts.push({
type: 'upcoming',
vehicle: vehicle.name,
part: part.name,
message: `Due in ${nextDueKm - currentKm} km`
});
}
}
});
return alerts;
}
displayAlerts(alerts) {
const container = document.getElementById('alerts-container');
if (alerts.length === 0) {
container.innerHTML = '<p class="no-alerts">No maintenance alerts at this time.</p>';
return;
}
const alertsHtml = alerts.map(alert => `
<div class="alert-item ${alert.type}">
<div class="alert-icon">
${alert.type === 'overdue' ? '🚨' : '⚠️'}
</div>
<div class="alert-content">
<h4>${alert.vehicle} - ${alert.part}</h4>
<p>${alert.message}</p>
</div>
</div>
`).join('');
container.innerHTML = alertsHtml;
}
// Vehicle Management
loadVehicles() {
const tbody = document.querySelector('#vehicles-table tbody');
if (this.data.vehicles.length === 0) {
tbody.innerHTML = '<tr class="no-data"><td colspan="8">No vehicles found. Add your first vehicle!</td></tr>';
return;
}
const vehiclesHtml = this.data.vehicles.map(vehicle => `
<tr>
<td>${vehicle.id}</td>
<td>${vehicle.name}</td>
<td>${vehicle.model}</td>
<td>${vehicle.year}</td>
<td>${vehicle.registration}</td>
<td>${vehicle.odometer}</td>
<td>${vehicle.fuelType}</td>
<td>
<div class="action-buttons">
<button class="btn btn-warning btn-small" onclick="vmms.editVehicle('${vehicle.id}')">Edit</button>
<button class="btn btn-danger btn-small" onclick="vmms.deleteVehicle('${vehicle.id}')">Delete</button>
</div>
</td>
</tr>
`).join('');
tbody.innerHTML = vehiclesHtml;
}
openVehicleModal(vehicleId = null) {
const modal = document.getElementById('vehicle-modal');
const title = document.getElementById('vehicle-modal-title');
const form = document.getElementById('vehicle-form');
form.reset();
if (vehicleId) {
const vehicle = this.data.vehicles.find(v => v.id === vehicleId);
if (vehicle) {
title.textContent = 'Edit Vehicle';
document.getElementById('vehicle-id').value = vehicle.id;
document.getElementById('vehicle-name').value = vehicle.name;
document.getElementById('vehicle-model').value = vehicle.model;
document.getElementById('vehicle-year').value = vehicle.year;
document.getElementById('vehicle-registration').value = vehicle.registration;
document.getElementById('vehicle-odometer').value = vehicle.odometer;
document.getElementById('vehicle-fuel-type').value = vehicle.fuelType;
}
} else {
title.textContent = 'Add Vehicle';
document.getElementById('vehicle-id').value = '';
}
modal.style.display = 'block';
}
async saveVehicle(e) {
e.preventDefault();
const formData = new FormData(e.target);
const vehicleId = formData.get('vehicle-id') || document.getElementById('vehicle-id').value;
const isEdit = !!vehicleId;
const vehicle = {
id: vehicleId || this.generateId('V'),
name: formData.get('vehicle-name') || document.getElementById('vehicle-name').value,
model: formData.get('vehicle-model') || document.getElementById('vehicle-model').value,
year: formData.get('vehicle-year') || document.getElementById('vehicle-year').value,
registration: formData.get('vehicle-registration') || document.getElementById('vehicle-registration').value,
odometer: formData.get('vehicle-odometer') || document.getElementById('vehicle-odometer').value,
fuelType: formData.get('vehicle-fuel-type') || document.getElementById('vehicle-fuel-type').value
};
try {
this.showLoading();
if (isEdit) {
await this.updateVehicleInSheet(vehicle);
const index = this.data.vehicles.findIndex(v => v.id === vehicleId);
this.data.vehicles[index] = vehicle;
} else {
await this.addVehicleToSheet(vehicle);
this.data.vehicles.push(vehicle);
}
this.closeModal('vehicle-modal');
this.loadVehicles();
this.updateVehicleSelects();
this.showToast(isEdit ? 'Vehicle updated successfully!' : 'Vehicle added successfully!', 'success');
} catch (error) {
console.error('Error saving vehicle:', error);
this.showToast('Error saving vehicle. Please try again.', 'error');
} finally {
this.hideLoading();
}
}
async addVehicleToSheet(vehicle) {
// This is a placeholder - in a real implementation, you would use Google Sheets API
// For now, we'll simulate the operation
return new Promise(resolve => setTimeout(resolve, 500));
}
async updateVehicleInSheet(vehicle) {
// This is a placeholder - in a real implementation, you would use Google Sheets API
return new Promise(resolve => setTimeout(resolve, 500));
}
editVehicle(vehicleId) {
this.openVehicleModal(vehicleId);
}
async deleteVehicle(vehicleId) {
if (!confirm('Are you sure you want to delete this vehicle? This will also delete all associated parts and logs.')) {
return;
}
try {
this.showLoading();
// Remove vehicle and associated data
this.data.vehicles = this.data.vehicles.filter(v => v.id !== vehicleId);
this.data.parts = this.data.parts.filter(p => p.vehicleId !== vehicleId);
// Remove logs for deleted parts
const partIds = this.data.parts.filter(p => p.vehicleId === vehicleId).map(p => p.id);
this.data.logs = this.data.logs.filter(l => !partIds.includes(l.partId));
this.loadVehicles();
this.updateVehicleSelects();
this.showToast('Vehicle deleted successfully!', 'success');
} catch (error) {
console.error('Error deleting vehicle:', error);
this.showToast('Error deleting vehicle. Please try again.', 'error');
} finally {
this.hideLoading();
}
}
// Parts Management
loadParts() {
this.updateVehicleSelects();
const selectedVehicleId = document.getElementById('parts-vehicle-select').value;
this.loadPartsByVehicle(selectedVehicleId);
}
updateVehicleSelects() {
const selects = ['parts-vehicle-select'];
selects.forEach(selectId => {
const select = document.getElementById(selectId);
const currentValue = select.value;
select.innerHTML = '<option value="">Select Vehicle</option>';
this.data.vehicles.forEach(vehicle => {
const option = document.createElement('option');
option.value = vehicle.id;
option.textContent = `${vehicle.name} (${vehicle.registration})`;
select.appendChild(option);
});
if (currentValue && this.data.vehicles.find(v => v.id === currentValue)) {
select.value = currentValue;
}
});
}
loadPartsByVehicle(vehicleId) {
const tbody = document.querySelector('#parts-table tbody');
const addButton = document.getElementById('add-part-btn');
if (!vehicleId) {
tbody.innerHTML = '<tr class="no-data"><td colspan="7">Select a vehicle to view its parts.</td></tr>';
addButton.disabled = true;
return;
}
addButton.disabled = false;
const vehicleParts = this.data.parts.filter(p => p.vehicleId === vehicleId);
if (vehicleParts.length === 0) {
tbody.innerHTML = '<tr class="no-data"><td colspan="7">No parts found for this vehicle. Add the first part!</td></tr>';
return;
}
const partsHtml = vehicleParts.map(part => {
const vehicle = this.data.vehicles.find(v => v.id === part.vehicleId);
return `
<tr>
<td>${part.id}</td>
<td>${vehicle ? vehicle.name : 'Unknown'}</td>
<td>${part.name}</td>
<td>${this.formatReplacementType(part.type)}</td>
<td>${part.value}</td>
<td>${part.notes}</td>
<td>
<div class="action-buttons">
<button class="btn btn-warning btn-small" onclick="vmms.editPart('${part.id}')">Edit</button>
<button class="btn btn-danger btn-small" onclick="vmms.deletePart('${part.id}')">Delete</button>
</div>
</td>
</tr>
`;
}).join('');
tbody.innerHTML = partsHtml;
this.updatePartSelects();
}
formatReplacementType(type) {
switch (type) {
case 'month': return 'Time-based (months)';
case 'km': return 'Distance-based (km)';
case 'custom': return 'Custom';
default: return type;
}
}
openPartModal(partId = null) {
const selectedVehicleId = document.getElementById('parts-vehicle-select').value;
if (!selectedVehicleId && !partId) {
this.showToast('Please select a vehicle first', 'warning');
return;
}
const modal = document.getElementById('part-modal');
const title = document.getElementById('part-modal-title');
const form = document.getElementById('part-form');
form.reset();
if (partId) {
const part = this.data.parts.find(p => p.id === partId);
if (part) {
title.textContent = 'Edit Part';
document.getElementById('part-id').value = part.id;
document.getElementById('part-vehicle-id').value = part.vehicleId;
document.getElementById('part-name').value = part.name;
document.getElementById('part-replacement-type').value = part.type;
document.getElementById('part-replacement-value').value = part.value;
document.getElementById('part-notes').value = part.notes;
}
} else {
title.textContent = 'Add Part';
document.getElementById('part-id').value = '';
document.getElementById('part-vehicle-id').value = selectedVehicleId;
}
modal.style.display = 'block';
}
async savePart(e) {
e.preventDefault();
const formData = new FormData(e.target);
const partId = formData.get('part-id') || document.getElementById('part-id').value;
const isEdit = !!partId;
const part = {
id: partId || this.generateId('P'),
vehicleId: formData.get('part-vehicle-id') || document.getElementById('part-vehicle-id').value,
name: formData.get('part-name') || document.getElementById('part-name').value,
type: formData.get('part-replacement-type') || document.getElementById('part-replacement-type').value,
value: formData.get('part-replacement-value') || document.getElementById('part-replacement-value').value,
notes: formData.get('part-notes') || document.getElementById('part-notes').value
};
try {
this.showLoading();
if (isEdit) {
const index = this.data.parts.findIndex(p => p.id === partId);
this.data.parts[index] = part;
} else {
this.data.parts.push(part);
}
this.closeModal('part-modal');
this.loadPartsByVehicle(part.vehicleId);
this.updatePartSelects();
this.showToast(isEdit ? 'Part updated successfully!' : 'Part added successfully!', 'success');
} catch (error) {
console.error('Error saving part:', error);
this.showToast('Error saving part. Please try again.', 'error');
} finally {
this.hideLoading();
}
}
editPart(partId) {
this.openPartModal(partId);
}
async deletePart(partId) {
if (!confirm('Are you sure you want to delete this part? This will also delete all associated maintenance logs.')) {
return;
}
try {
this.showLoading();
const part = this.data.parts.find(p => p.id === partId);
// Remove part and associated logs
this.data.parts = this.data.parts.filter(p => p.id !== partId);
this.data.logs = this.data.logs.filter(l => l.partId !== partId);
this.loadPartsByVehicle(part.vehicleId);
this.updatePartSelects();
this.showToast('Part deleted successfully!', 'success');
} catch (error) {
console.error('Error deleting part:', error);
this.showToast('Error deleting part. Please try again.', 'error');
} finally {
this.hideLoading();
}
}
// Logs Management
loadLogs() {
this.updatePartSelects();
const selectedPartId = document.getElementById('logs-part-select').value;
this.loadLogsByPart(selectedPartId);
}
updatePartSelects() {
const select = document.getElementById('logs-part-select');
const currentValue = select.value;
select.innerHTML = '<option value="">Select Part</option>';
this.data.parts.forEach(part => {
const vehicle = this.data.vehicles.find(v => v.id === part.vehicleId);
const option = document.createElement('option');
option.value = part.id;
option.textContent = `${vehicle ? vehicle.name : 'Unknown'} - ${part.name}`;
select.appendChild(option);
});
if (currentValue && this.data.parts.find(p => p.id === currentValue)) {
select.value = currentValue;
}
}
loadLogsByPart(partId) {
const tbody = document.querySelector('#logs-table tbody');
const addButton = document.getElementById('add-log-btn');
if (!partId) {
tbody.innerHTML = '<tr class="no-data"><td colspan="7">Select a part to view its maintenance logs.</td></tr>';
addButton.disabled = true;
return;
}
addButton.disabled = false;
const partLogs = this.data.logs.filter(l => l.partId === partId);
if (partLogs.length === 0) {
tbody.innerHTML = '<tr class="no-data"><td colspan="7">No maintenance logs found for this part. Add the first log!</td></tr>';
return;
}
const logsHtml = partLogs.map(log => {
const part = this.data.parts.find(p => p.id === log.partId);
const vehicle = part ? this.data.vehicles.find(v => v.id === part.vehicleId) : null;
return `
<tr>
<td>${log.id}</td>
<td>${part ? part.name : 'Unknown'}</td>
<td>${vehicle ? vehicle.name : 'Unknown'}</td>
<td>${this.formatDate(log.date)}</td>
<td>${log.odometer}</td>
<td>${log.notes}</td>
<td>
<div class="action-buttons">
<button class="btn btn-warning btn-small" onclick="vmms.editLog('${log.id}')">Edit</button>
<button class="btn btn-danger btn-small" onclick="vmms.deleteLog('${log.id}')">Delete</button>
</div>
</td>
</tr>
`;
}).join('');
tbody.innerHTML = logsHtml;
}
openLogModal(logId = null) {
const selectedPartId = document.getElementById('logs-part-select').value;
if (!selectedPartId && !logId) {
this.showToast('Please select a part first', 'warning');
return;
}
const modal = document.getElementById('log-modal');
const title = document.getElementById('log-modal-title');
const form = document.getElementById('log-form');
form.reset();
if (logId) {
const log = this.data.logs.find(l => l.id === logId);
if (log) {
title.textContent = 'Edit Maintenance Log';
document.getElementById('log-id').value = log.id;
document.getElementById('log-part-id').value = log.partId;
document.getElementById('log-date').value = log.date;
document.getElementById('log-odometer').value = log.odometer;
document.getElementById('log-notes').value = log.notes;
}
} else {
title.textContent = 'Add Maintenance Log';
document.getElementById('log-id').value = '';
document.getElementById('log-part-id').value = selectedPartId;
// Set today's date as default
const today = new Date().toISOString().split('T')[0];
document.getElementById('log-date').value = today;
}
modal.style.display = 'block';
}
async saveLog(e) {
e.preventDefault();
const formData = new FormData(e.target);
const logId = formData.get('log-id') || document.getElementById('log-id').value;
const isEdit = !!logId;
const log = {
id: logId || this.generateId('L'),
partId: formData.get('log-part-id') || document.getElementById('log-part-id').value,
date: formData.get('log-date') || document.getElementById('log-date').value,
odometer: formData.get('log-odometer') || document.getElementById('log-odometer').value,
notes: formData.get('log-notes') || document.getElementById('log-notes').value
};
try {
this.showLoading();
if (isEdit) {
const index = this.data.logs.findIndex(l => l.id === logId);
this.data.logs[index] = log;
} else {
this.data.logs.push(log);
}
this.closeModal('log-modal');
this.loadLogsByPart(log.partId);
this.showToast(isEdit ? 'Log updated successfully!' : 'Log added successfully!', 'success');
} catch (error) {
console.error('Error saving log:', error);
this.showToast('Error saving log. Please try again.', 'error');
} finally {
this.hideLoading();
}
}
editLog(logId) {
this.openLogModal(logId);
}
async deleteLog(logId) {
if (!confirm('Are you sure you want to delete this maintenance log?')) {
return;
}
try {
this.showLoading();
const log = this.data.logs.find(l => l.id === logId);
this.data.logs = this.data.logs.filter(l => l.id !== logId);
this.loadLogsByPart(log.partId);
this.showToast('Log deleted successfully!', 'success');
} catch (error) {
console.error('Error deleting log:', error);
this.showToast('Error deleting log. Please try again.', 'error');
} finally {
this.hideLoading();
}
}
// Utility Functions
generateId(prefix) {
const items = this.data[prefix === 'V' ? 'vehicles' : prefix === 'P' ? 'parts' : 'logs'];
const numbers = items
.map(item => parseInt(item.id.substring(1)))
.filter(num => !isNaN(num))
.sort((a, b) => b - a);
const nextNumber = numbers.length > 0 ? numbers[0] + 1 : 1;
return `${prefix}${nextNumber}`;
}
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString();
}
closeModal(modalId) {
document.getElementById(modalId).style.display = 'none';
}
showLoading() {
document.getElementById('loading-overlay').style.display = 'flex';
}
hideLoading() {
document.getElementById('loading-overlay').style.display = 'none';
}
showToast(message, type = 'info') {
const toastContainer = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `
<div class="toast-title">${type.charAt(0).toUpperCase() + type.slice(1)}</div>
<div class="toast-message">${message}</div>
`;
toastContainer.appendChild(toast);
// Auto-remove toast after 5 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 5000);
// Add click to dismiss
toast.addEventListener('click', () => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
});
}
}
// Initialize the application when the DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.vmms = new VMMS();
});
// Additional helper functions for Google Sheets integration
// Note: In a real implementation, you would need to set up proper Google Sheets API integration
// This includes authentication, proper API calls, and error handling
class GoogleSheetsAPI {
constructor(sheetId, apiKey = null) {
this.sheetId = sheetId;
this.apiKey = apiKey;
this.baseUrl = 'https://sheets.googleapis.com/v4/spreadsheets';
}
async readSheet(sheetName) {
try {
const url = this.apiKey
? `${this.baseUrl}/${this.sheetId}/values/${sheetName}?key=${this.apiKey}`
: `https://docs.google.com/spreadsheets/d/${this.sheetId}/gviz/tq?tqx=out:json&sheet=${sheetName}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
if (this.apiKey) {
const data = await response.json();
return data.values || [];
} else {
const text = await response.text();
return this.parseGoogleSheetsData(text);
}
} catch (error) {
console.error(`Error reading sheet ${sheetName}:`, error);
throw error;
}
}
async appendToSheet(sheetName, values) {
if (!this.apiKey) {
throw new Error('API key required for write operations');
}
try {
const url = `${this.baseUrl}/${this.sheetId}/values/${sheetName}!A:A:append?valueInputOption=USER_ENTERED&key=${this.apiKey}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
values: [values]
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`Error appending to sheet ${sheetName}:`, error);
throw error;
}
}
async updateSheet(sheetName, range, values) {
if (!this.apiKey) {
throw new Error('API key required for write operations');
}
try {
const url = `${this.baseUrl}/${this.sheetId}/values/${sheetName}!${range}?valueInputOption=USER_ENTERED&key=${this.apiKey}`;
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
values: [values]
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`Error updating sheet ${sheetName}:`, error);
throw error;
}
}
parseGoogleSheetsData(text) {
try {
const jsonString = text.match(/google\.visualization\.Query\.setResponse\((.*)\);/)[1];
const data = JSON.parse(jsonString);
if (!data.table || !data.table.rows) {
return [];
}
return data.table.rows.map(row =>
row.c ? row.c.map(cell => cell ? cell.v : '') : []
);
} catch (error) {
console.error('Error parsing Google Sheets data:', error);
return [];
}
}
}
// Export for potential use in modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = { VMMS, GoogleSheetsAPI };
}