Repository navigation
Html file Script for ITSM Tool
Ayushi-srivastav edited this page Oct 15, 2026
·
1 revision
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local ITSM Tool</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #f4f4f4;
color: #333;
}
.header {
background: #2c3e50;
color: white;
padding: 1rem 2rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 1.5rem;
font-weight: 500;
}
.nav {
background: #34495e;
padding: 0.5rem 2rem;
display: flex;
gap: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.nav button {
background: transparent;
color: white;
border: none;
padding: 0.75rem 1.5rem;
cursor: pointer;
font-size: 0.95rem;
border-radius: 4px;
transition: background 0.3s;
}
.nav button:hover {
background: rgba(255,255,255,0.1);
}
.nav button.active {
background: #3498db;
}
.container {
max-width: 1400px;
margin: 2rem auto;
padding: 0 2rem;
}
.actions {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.95rem;
transition: all 0.3s;
font-weight: 500;
}
.btn-primary {
background: #3498db;
color: white;
}
.btn-primary:hover {
background: #2980b9;
}
.btn-secondary {
background: #95a5a6;
color: white;
}
.btn-secondary:hover {
background: #7f8c8d;
}
.btn-success {
background: #27ae60;
color: white;
}
.btn-success:hover {
background: #229954;
}
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
}
.card-header {
background: #ecf0f1;
padding: 1rem 1.5rem;
border-bottom: 1px solid #ddd;
font-weight: 600;
font-size: 1.1rem;
}
.card-body {
padding: 1.5rem;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background: #ecf0f1;
padding: 0.75rem;
text-align: left;
font-weight: 600;
border-bottom: 2px solid #bdc3c7;
}
td {
padding: 0.75rem;
border-bottom: 1px solid #ecf0f1;
}
tr:hover {
background: #f8f9fa;
}
.badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 500;
display: inline-block;
}
.badge-new { background: #3498db; color: white; }
.badge-inprogress { background: #f39c12; color: white; }
.badge-resolved { background: #27ae60; color: white; }
.badge-closed { background: #95a5a6; color: white; }
.badge-pending { background: #e67e22; color: white; }
.badge-approved { background: #27ae60; color: white; }
.badge-rejected { background: #e74c3c; color: white; }
.badge-draft { background: #95a5a6; color: white; }
.badge-scheduled { background: #9b59b6; color: white; }
.badge-implemented { background: #16a085; color: white; }
.priority-critical { color: #e74c3c; font-weight: 600; }
.priority-high { color: #e67e22; font-weight: 600; }
.priority-medium { color: #f39c12; font-weight: 600; }
.priority-low { color: #27ae60; font-weight: 600; }
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
overflow-y: auto;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.modal-content {
background: white;
border-radius: 8px;
width: 100%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
padding: 1.5rem;
border-bottom: 1px solid #ecf0f1;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
font-size: 1.5rem;
font-weight: 600;
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
padding: 1.5rem;
border-top: 1px solid #ecf0f1;
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #7f8c8d;
}
.close-btn:hover {
color: #2c3e50;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #2c3e50;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
font-family: inherit;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.section-hidden {
display: none;
}
.record-link {
color: #3498db;
text-decoration: none;
cursor: pointer;
}
.record-link:hover {
text-decoration: underline;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.stat-card h3 {
font-size: 2rem;
color: #3498db;
margin-bottom: 0.5rem;
}
.stat-card p {
color: #7f8c8d;
font-size: 0.9rem;
}
.empty-state {
text-align: center;
padding: 3rem;
color: #7f8c8d;
}
.empty-state-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.search-filter {
margin-bottom: 1.5rem;
}
.search-filter input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.95rem;
}
.action-buttons {
display: flex;
gap: 0.5rem;
}
.action-btn {
padding: 0.25rem 0.75rem;
font-size: 0.85rem;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.3s;
}
.action-btn-view {
background: #3498db;
color: white;
}
.action-btn-view:hover {
background: #2980b9;
}
.action-btn-edit {
background: #f39c12;
color: white;
}
.action-btn-edit:hover {
background: #e67e22;
}
.btn-danger {
background: #e74c3c;
color: white;
}
.btn-danger:hover {
background: #c0392b;
}
.detail-view {
display: grid;
gap: 1rem;
}
.detail-row {
display: grid;
grid-template-columns: 200px 1fr;
padding: 0.75rem 0;
border-bottom: 1px solid #ecf0f1;
}
.detail-label {
font-weight: 600;
color: #2c3e50;
}
.detail-value {
color: #555;
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.detail-row {
grid-template-columns: 1fr;
gap: 0.25rem;
}
.container {
padding: 0 1rem;
}
.nav {
overflow-x: auto;
}
}
</style>
</head>
<body>
<div class="header">
<h1>🔧 Local ITSM Tool</h1>
</div>
<div class="nav">
<button class="active" onclick="switchSection('incidents')">Incidents</button>
<button onclick="switchSection('ritm')">Request Items (RITM)</button>
<button onclick="switchSection('changes')">Change Management</button>
<button onclick="switchSection('dashboard')">Dashboard</button>
</div>
<div class="container">
<!-- Dashboard Section -->
<div id="dashboard-section" class="section-hidden">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
<h2 style="margin: 0; color: #2c3e50;">Dashboard Overview</h2>
<button class="btn btn-danger" onclick="clearAllData()">🗑️ Clear All Data</button>
</div>
<div class="stats">
<div class="stat-card">
<h3 id="stat-incidents">0</h3>
<p>Total Incidents</p>
</div>
<div class="stat-card">
<h3 id="stat-ritm">0</h3>
<p>Request Items</p>
</div>
<div class="stat-card">
<h3 id="stat-changes">0</h3>
<p>Changes</p>
</div>
<div class="stat-card">
<h3 id="stat-open">0</h3>
<p>Open Items</p>
</div>
</div>
</div>
<!-- Incidents Section -->
<div id="incidents-section">
<div class="actions">
<button class="btn btn-primary" onclick="openCreateModal('incident')">+ Create New Incident</button>
<button class="btn btn-secondary" onclick="exportData('incidents')">Export to JSON</button>
<button class="btn btn-danger" onclick="clearAllRecords('incidents')">Clear All Records</button>
</div>
<div class="search-filter">
<input type="text" placeholder="Search incidents..." oninput="filterTable('incidents', this.value)">
</div>
<div class="card">
<div class="card-header">Incident List</div>
<div class="card-body">
<table id="incidents-table">
<thead>
<tr>
<th>Number</th>
<th>Short Description</th>
<th>Priority</th>
<th>Status</th>
<th>Assigned To</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="incidents-tbody">
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">📋</div>
<p>No incidents found. Create your first incident to get started.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- RITM Section -->
<div id="ritm-section" class="section-hidden">
<div class="actions">
<button class="btn btn-primary" onclick="openCreateModal('ritm')">+ Create New Request Item</button>
<button class="btn btn-secondary" onclick="exportData('ritm')">Export to JSON</button>
<button class="btn btn-danger" onclick="clearAllRecords('ritm')">Clear All Records</button>
</div>
<div class="search-filter">
<input type="text" placeholder="Search request items..." oninput="filterTable('ritm', this.value)">
</div>
<div class="card">
<div class="card-header">Request Items (RITM) List</div>
<div class="card-body">
<table id="ritm-table">
<thead>
<tr>
<th>Number</th>
<th>Short Description</th>
<th>Category</th>
<th>Status</th>
<th>Requested By</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="ritm-tbody">
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">📦</div>
<p>No request items found. Create your first RITM to get started.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Changes Section -->
<div id="changes-section" class="section-hidden">
<div class="actions">
<button class="btn btn-primary" onclick="openCreateModal('change')">+ Create New Change</button>
<button class="btn btn-secondary" onclick="exportData('changes')">Export to JSON</button>
<button class="btn btn-danger" onclick="clearAllRecords('changes')">Clear All Records</button>
</div>
<div class="search-filter">
<input type="text" placeholder="Search changes..." oninput="filterTable('changes', this.value)">
</div>
<div class="card">
<div class="card-header">Change Management List</div>
<div class="card-body">
<table id="changes-table">
<thead>
<tr>
<th>Number</th>
<th>Short Description</th>
<th>Type</th>
<th>Risk</th>
<th>Status</th>
<th>Scheduled Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="changes-tbody">
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">🔄</div>
<p>No changes found. Create your first change request to get started.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Create/Edit Modal -->
<div id="modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modal-title">Create New Incident</h2>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<div class="modal-body" id="modal-body">
<!-- Form will be dynamically inserted here -->
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal()">Cancel</button>
<button class="btn btn-success" id="save-btn" onclick="saveRecord()">Save</button>
</div>
</div>
</div>
<!-- View Modal -->
<div id="view-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="view-modal-title">View Record</h2>
<button class="close-btn" onclick="closeViewModal()">×</button>
</div>
<div class="modal-body" id="view-modal-body">
<!-- Details will be dynamically inserted here -->
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeViewModal()">Close</button>
<button class="btn btn-primary" id="edit-from-view-btn" onclick="editFromView()">Edit</button>
</div>
</div>
</div>
<script>
// Data Storage
let incidents = JSON.parse(localStorage.getItem('itsm_incidents') || '[]');
let ritms = JSON.parse(localStorage.getItem('itsm_ritms') || '[]');
let changes = JSON.parse(localStorage.getItem('itsm_changes') || '[]');
let currentSection = 'incidents';
let currentRecordType = null;
let currentRecordId = null;
let viewingRecord = null;
// Counter for IDs
let incidentCounter = parseInt(localStorage.getItem('incident_counter') || '1000');
let ritmCounter = parseInt(localStorage.getItem('ritm_counter') || '2000');
let changeCounter = parseInt(localStorage.getItem('change_counter') || '3000');
// Initialize the app
function init() {
renderIncidents();
renderRITMs();
renderChanges();
updateDashboard();
}
// Switch between sections
function switchSection(section) {
// Hide all sections
document.getElementById('dashboard-section').classList.add('section-hidden');
document.getElementById('incidents-section').classList.add('section-hidden');
document.getElementById('ritm-section').classList.add('section-hidden');
document.getElementById('changes-section').classList.add('section-hidden');
// Show selected section
if (section === 'dashboard') {
document.getElementById('dashboard-section').classList.remove('section-hidden');
updateDashboard();
} else if (section === 'incidents') {
document.getElementById('incidents-section').classList.remove('section-hidden');
} else if (section === 'ritm') {
document.getElementById('ritm-section').classList.remove('section-hidden');
} else if (section === 'changes') {
document.getElementById('changes-section').classList.remove('section-hidden');
}
// Update nav active state
document.querySelectorAll('.nav button').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
currentSection = section;
}
// Update Dashboard
function updateDashboard() {
document.getElementById('stat-incidents').textContent = incidents.length;
document.getElementById('stat-ritm').textContent = ritms.length;
document.getElementById('stat-changes').textContent = changes.length;
const openIncidents = incidents.filter(i => i.status !== 'Closed').length;
const openRitms = ritms.filter(r => r.status !== 'Closed').length;
const openChanges = changes.filter(c => c.status !== 'Implemented' && c.status !== 'Closed').length;
document.getElementById('stat-open').textContent = openIncidents + openRitms + openChanges;
}
// Open Create/Edit Modal
function openCreateModal(type, recordId = null) {
currentRecordType = type;
currentRecordId = recordId;
const modal = document.getElementById('modal');
const modalTitle = document.getElementById('modal-title');
const modalBody = document.getElementById('modal-body');
let title = '';
let formHTML = '';
if (type === 'incident') {
title = recordId ? 'Edit Incident' : 'Create New Incident';
formHTML = getIncidentForm(recordId);
} else if (type === 'ritm') {
title = recordId ? 'Edit Request Item' : 'Create New Request Item';
formHTML = getRITMForm(recordId);
} else if (type === 'change') {
title = recordId ? 'Edit Change' : 'Create New Change';
formHTML = getChangeForm(recordId);
}
modalTitle.textContent = title;
modalBody.innerHTML = formHTML;
modal.classList.add('active');
}
// Close Modal
function closeModal() {
document.getElementById('modal').classList.remove('active');
currentRecordType = null;
currentRecordId = null;
}
// Incident Form
function getIncidentForm(id) {
let record = id ? incidents.find(i => i.id === id) : {};
return `
<div class="form-row">
<div class="form-group">
<label>Short Description *</label>
<input type="text" id="short_description" value="${record.short_description || ''}" required>
</div>
<div class="form-group">
<label>Priority *</label>
<select id="priority">
<option value="Critical" ${record.priority === 'Critical' ? 'selected' : ''}>Critical</option>
<option value="High" ${record.priority === 'High' ? 'selected' : ''}>High</option>
<option value="Medium" ${record.priority === 'Medium' || !record.priority ? 'selected' : ''}>Medium</option>
<option value="Low" ${record.priority === 'Low' ? 'selected' : ''}>Low</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Status *</label>
<select id="status">
<option value="New" ${record.status === 'New' || !record.status ? 'selected' : ''}>New</option>
<option value="In Progress" ${record.status === 'In Progress' ? 'selected' : ''}>In Progress</option>
<option value="Resolved" ${record.status === 'Resolved' ? 'selected' : ''}>Resolved</option>
<option value="Closed" ${record.status === 'Closed' ? 'selected' : ''}>Closed</option>
</select>
</div>
<div class="form-group">
<label>Assigned To</label>
<input type="text" id="assigned_to" value="${record.assigned_to || ''}">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Category</label>
<select id="category">
<option value="">-- Select --</option>
<option value="Hardware" ${record.category === 'Hardware' ? 'selected' : ''}>Hardware</option>
<option value="Software" ${record.category === 'Software' ? 'selected' : ''}>Software</option>
<option value="Network" ${record.category === 'Network' ? 'selected' : ''}>Network</option>
<option value="Database" ${record.category === 'Database' ? 'selected' : ''}>Database</option>
<option value="Security" ${record.category === 'Security' ? 'selected' : ''}>Security</option>
<option value="Other" ${record.category === 'Other' ? 'selected' : ''}>Other</option>
</select>
</div>
<div class="form-group">
<label>Caller</label>
<input type="text" id="caller" value="${record.caller || ''}">
</div>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="description">${record.description || ''}</textarea>
</div>
<div class="form-group">
<label>Resolution Notes</label>
<textarea id="resolution_notes">${record.resolution_notes || ''}</textarea>
</div>
`;
}
// RITM Form
function getRITMForm(id) {
let record = id ? ritms.find(r => r.id === id) : {};
return `
<div class="form-row">
<div class="form-group">
<label>Short Description *</label>
<input type="text" id="short_description" value="${record.short_description || ''}" required>
</div>
<div class="form-group">
<label>Category *</label>
<select id="category">
<option value="Hardware Request" ${record.category === 'Hardware Request' || !record.category ? 'selected' : ''}>Hardware Request</option>
<option value="Software Request" ${record.category === 'Software Request' ? 'selected' : ''}>Software Request</option>
<option value="Access Request" ${record.category === 'Access Request' ? 'selected' : ''}>Access Request</option>
<option value="Service Request" ${record.category === 'Service Request' ? 'selected' : ''}>Service Request</option>
<option value="Other" ${record.category === 'Other' ? 'selected' : ''}>Other</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Status *</label>
<select id="status">
<option value="Pending Approval" ${record.status === 'Pending Approval' || !record.status ? 'selected' : ''}>Pending Approval</option>
<option value="Approved" ${record.status === 'Approved' ? 'selected' : ''}>Approved</option>
<option value="Rejected" ${record.status === 'Rejected' ? 'selected' : ''}>Rejected</option>
<option value="In Progress" ${record.status === 'In Progress' ? 'selected' : ''}>In Progress</option>
<option value="Completed" ${record.status === 'Completed' ? 'selected' : ''}>Completed</option>
<option value="Closed" ${record.status === 'Closed' ? 'selected' : ''}>Closed</option>
</select>
</div>
<div class="form-group">
<label>Requested By</label>
<input type="text" id="requested_by" value="${record.requested_by || ''}">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Assigned To</label>
<input type="text" id="assigned_to" value="${record.assigned_to || ''}">
</div>
<div class="form-group">
<label>Due Date</label>
<input type="date" id="due_date" value="${record.due_date || ''}">
</div>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="description">${record.description || ''}</textarea>
</div>
<div class="form-group">
<label>Fulfillment Notes</label>
<textarea id="fulfillment_notes">${record.fulfillment_notes || ''}</textarea>
</div>
`;
}
// Change Form
function getChangeForm(id) {
let record = id ? changes.find(c => c.id === id) : {};
return `
<div class="form-row">
<div class="form-group">
<label>Short Description *</label>
<input type="text" id="short_description" value="${record.short_description || ''}" required>
</div>
<div class="form-group">
<label>Type *</label>
<select id="type">
<option value="Standard" ${record.type === 'Standard' || !record.type ? 'selected' : ''}>Standard</option>
<option value="Normal" ${record.type === 'Normal' ? 'selected' : ''}>Normal</option>
<option value="Emergency" ${record.type === 'Emergency' ? 'selected' : ''}>Emergency</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Risk *</label>
<select id="risk">
<option value="Low" ${record.risk === 'Low' || !record.risk ? 'selected' : ''}>Low</option>
<option value="Medium" ${record.risk === 'Medium' ? 'selected' : ''}>Medium</option>
<option value="High" ${record.risk === 'High' ? 'selected' : ''}>High</option>
<option value="Critical" ${record.risk === 'Critical' ? 'selected' : ''}>Critical</option>
</select>
</div>
<div class="form-group">
<label>Status *</label>
<select id="status">
<option value="Draft" ${record.status === 'Draft' || !record.status ? 'selected' : ''}>Draft</option>
<option value="Pending Approval" ${record.status === 'Pending Approval' ? 'selected' : ''}>Pending Approval</option>
<option value="Approved" ${record.status === 'Approved' ? 'selected' : ''}>Approved</option>
<option value="Scheduled" ${record.status === 'Scheduled' ? 'selected' : ''}>Scheduled</option>
<option value="In Progress" ${record.status === 'In Progress' ? 'selected' : ''}>In Progress</option>
<option value="Implemented" ${record.status === 'Implemented' ? 'selected' : ''}>Implemented</option>
<option value="Closed" ${record.status === 'Closed' ? 'selected' : ''}>Closed</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Requested By</label>
<input type="text" id="requested_by" value="${record.requested_by || ''}">
</div>
<div class="form-group">
<label>Assigned To</label>
<input type="text" id="assigned_to" value="${record.assigned_to || ''}">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Scheduled Start Date</label>
<input type="datetime-local" id="scheduled_start" value="${record.scheduled_start || ''}">
</div>
<div class="form-group">
<label>Scheduled End Date</label>
<input type="datetime-local" id="scheduled_end" value="${record.scheduled_end || ''}">
</div>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="description">${record.description || ''}</textarea>
</div>
<div class="form-group">
<label>Implementation Plan</label>
<textarea id="implementation_plan">${record.implementation_plan || ''}</textarea>
</div>
<div class="form-group">
<label>Backout Plan</label>
<textarea id="backout_plan">${record.backout_plan || ''}</textarea>
</div>
<div class="form-group">
<label>Test Plan</label>
<textarea id="test_plan">${record.test_plan || ''}</textarea>
</div>
`;
}
// Save Record
function saveRecord() {
const short_description = document.getElementById('short_description').value.trim();
if (!short_description) {
alert('Please fill in the required fields');
return;
}
if (currentRecordType === 'incident') {
saveIncident();
} else if (currentRecordType === 'ritm') {
saveRITM();
} else if (currentRecordType === 'change') {
saveChange();
}
closeModal();
}
// Save Incident
function saveIncident() {
const record = {
id: currentRecordId || `INC${incidentCounter++}`,
number: currentRecordId || `INC${incidentCounter - 1}`,
short_description: document.getElementById('short_description').value,
priority: document.getElementById('priority').value,
status: document.getElementById('status').value,
assigned_to: document.getElementById('assigned_to').value,
category: document.getElementById('category').value,
caller: document.getElementById('caller').value,
description: document.getElementById('description').value,
resolution_notes: document.getElementById('resolution_notes').value,
created: currentRecordId ? incidents.find(i => i.id === currentRecordId).created : new Date().toISOString(),
updated: new Date().toISOString()
};
if (currentRecordId) {
const index = incidents.findIndex(i => i.id === currentRecordId);
incidents[index] = record;
} else {
incidents.push(record);
localStorage.setItem('incident_counter', incidentCounter);
}
localStorage.setItem('itsm_incidents', JSON.stringify(incidents));
renderIncidents();
updateDashboard();
}
// Save RITM
function saveRITM() {
const record = {
id: currentRecordId || `RITM${ritmCounter++}`,
number: currentRecordId || `RITM${ritmCounter - 1}`,
short_description: document.getElementById('short_description').value,
category: document.getElementById('category').value,
status: document.getElementById('status').value,
requested_by: document.getElementById('requested_by').value,
assigned_to: document.getElementById('assigned_to').value,
due_date: document.getElementById('due_date').value,
description: document.getElementById('description').value,
fulfillment_notes: document.getElementById('fulfillment_notes').value,
created: currentRecordId ? ritms.find(r => r.id === currentRecordId).created : new Date().toISOString(),
updated: new Date().toISOString()
};
if (currentRecordId) {
const index = ritms.findIndex(r => r.id === currentRecordId);
ritms[index] = record;
} else {
ritms.push(record);
localStorage.setItem('ritm_counter', ritmCounter);
}
localStorage.setItem('itsm_ritms', JSON.stringify(ritms));
renderRITMs();
updateDashboard();
}
// Save Change
function saveChange() {
const record = {
id: currentRecordId || `CHG${changeCounter++}`,
number: currentRecordId || `CHG${changeCounter - 1}`,
short_description: document.getElementById('short_description').value,
type: document.getElementById('type').value,
risk: document.getElementById('risk').value,
status: document.getElementById('status').value,
requested_by: document.getElementById('requested_by').value,
assigned_to: document.getElementById('assigned_to').value,
scheduled_start: document.getElementById('scheduled_start').value,
scheduled_end: document.getElementById('scheduled_end').value,
description: document.getElementById('description').value,
implementation_plan: document.getElementById('implementation_plan').value,
backout_plan: document.getElementById('backout_plan').value,
test_plan: document.getElementById('test_plan').value,
created: currentRecordId ? changes.find(c => c.id === currentRecordId).created : new Date().toISOString(),
updated: new Date().toISOString()
};
if (currentRecordId) {
const index = changes.findIndex(c => c.id === currentRecordId);
changes[index] = record;
} else {
changes.push(record);
localStorage.setItem('change_counter', changeCounter);
}
localStorage.setItem('itsm_changes', JSON.stringify(changes));
renderChanges();
updateDashboard();
}
// Render Incidents
function renderIncidents() {
const tbody = document.getElementById('incidents-tbody');
if (incidents.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">📋</div>
<p>No incidents found. Create your first incident to get started.</p>
</td>
</tr>
`;
return;
}
tbody.innerHTML = incidents.map(inc => `
<tr>
<td><a class="record-link" onclick="viewRecord('incident', '${inc.id}')">${inc.number}</a></td>
<td>${inc.short_description}</td>
<td><span class="priority-${inc.priority.toLowerCase()}">${inc.priority}</span></td>
<td><span class="badge badge-${inc.status.toLowerCase().replace(' ', '')}">${inc.status}</span></td>
<td>${inc.assigned_to || '-'}</td>
<td>${formatDate(inc.created)}</td>
<td>
<div class="action-buttons">
<button class="action-btn action-btn-view" onclick="viewRecord('incident', '${inc.id}')">View</button>
<button class="action-btn action-btn-edit" onclick="openCreateModal('incident', '${inc.id}')">Edit</button>
</div>
</td>
</tr>
`).join('');
}
// Render RITMs
function renderRITMs() {
const tbody = document.getElementById('ritm-tbody');
if (ritms.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">📦</div>
<p>No request items found. Create your first RITM to get started.</p>
</td>
</tr>
`;
return;
}
tbody.innerHTML = ritms.map(ritm => `
<tr>
<td><a class="record-link" onclick="viewRecord('ritm', '${ritm.id}')">${ritm.number}</a></td>
<td>${ritm.short_description}</td>
<td>${ritm.category}</td>
<td><span class="badge badge-${ritm.status.toLowerCase().replace(' ', '')}">${ritm.status}</span></td>
<td>${ritm.requested_by || '-'}</td>
<td>${formatDate(ritm.created)}</td>
<td>
<div class="action-buttons">
<button class="action-btn action-btn-view" onclick="viewRecord('ritm', '${ritm.id}')">View</button>
<button class="action-btn action-btn-edit" onclick="openCreateModal('ritm', '${ritm.id}')">Edit</button>
</div>
</td>
</tr>
`).join('');
}
// Render Changes
function renderChanges() {
const tbody = document.getElementById('changes-tbody');
if (changes.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="empty-state">
<div class="empty-state-icon">🔄</div>
<p>No changes found. Create your first change request to get started.</p>
</td>
</tr>
`;
return;
}
tbody.innerHTML = changes.map(chg => `
<tr>
<td><a class="record-link" onclick="viewRecord('change', '${chg.id}')">${chg.number}</a></td>
<td>${chg.short_description}</td>
<td>${chg.type}</td>
<td><span class="priority-${chg.risk.toLowerCase()}">${chg.risk}</span></td>
<td><span class="badge badge-${chg.status.toLowerCase().replace(' ', '')}">${chg.status}</span></td>
<td>${chg.scheduled_start ? formatDate(chg.scheduled_start) : '-'}</td>
<td>
<div class="action-buttons">
<button class="action-btn action-btn-view" onclick="viewRecord('change', '${chg.id}')">View</button>
<button class="action-btn action-btn-edit" onclick="openCreateModal('change', '${chg.id}')">Edit</button>
</div>
</td>
</tr>
`).join('');
}
// View Record
function viewRecord(type, id) {
viewingRecord = { type, id };
const modal = document.getElementById('view-modal');
const modalTitle = document.getElementById('view-modal-title');
const modalBody = document.getElementById('view-modal-body');
let record, detailsHTML;
if (type === 'incident') {
record = incidents.find(i => i.id === id);
modalTitle.textContent = `Incident: ${record.number}`;
detailsHTML = `
<div class="detail-view">
<div class="detail-row">
<div class="detail-label">Number</div>
<div class="detail-value">${record.number}</div>
</div>
<div class="detail-row">
<div class="detail-label">Short Description</div>
<div class="detail-value">${record.short_description}</div>
</div>
<div class="detail-row">
<div class="detail-label">Priority</div>
<div class="detail-value"><span class="priority-${record.priority.toLowerCase()}">${record.priority}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Status</div>
<div class="detail-value"><span class="badge badge-${record.status.toLowerCase().replace(' ', '')}">${record.status}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Category</div>
<div class="detail-value">${record.category || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Assigned To</div>
<div class="detail-value">${record.assigned_to || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Caller</div>
<div class="detail-value">${record.caller || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Description</div>
<div class="detail-value">${record.description || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Resolution Notes</div>
<div class="detail-value">${record.resolution_notes || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Created</div>
<div class="detail-value">${formatDate(record.created)}</div>
</div>
<div class="detail-row">
<div class="detail-label">Last Updated</div>
<div class="detail-value">${formatDate(record.updated)}</div>
</div>
</div>
`;
} else if (type === 'ritm') {
record = ritms.find(r => r.id === id);
modalTitle.textContent = `Request Item: ${record.number}`;
detailsHTML = `
<div class="detail-view">
<div class="detail-row">
<div class="detail-label">Number</div>
<div class="detail-value">${record.number}</div>
</div>
<div class="detail-row">
<div class="detail-label">Short Description</div>
<div class="detail-value">${record.short_description}</div>
</div>
<div class="detail-row">
<div class="detail-label">Category</div>
<div class="detail-value">${record.category}</div>
</div>
<div class="detail-row">
<div class="detail-label">Status</div>
<div class="detail-value"><span class="badge badge-${record.status.toLowerCase().replace(' ', '')}">${record.status}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Requested By</div>
<div class="detail-value">${record.requested_by || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Assigned To</div>
<div class="detail-value">${record.assigned_to || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Due Date</div>
<div class="detail-value">${record.due_date ? formatDate(record.due_date) : '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Description</div>
<div class="detail-value">${record.description || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Fulfillment Notes</div>
<div class="detail-value">${record.fulfillment_notes || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Created</div>
<div class="detail-value">${formatDate(record.created)}</div>
</div>
<div class="detail-row">
<div class="detail-label">Last Updated</div>
<div class="detail-value">${formatDate(record.updated)}</div>
</div>
</div>
`;
} else if (type === 'change') {
record = changes.find(c => c.id === id);
modalTitle.textContent = `Change: ${record.number}`;
detailsHTML = `
<div class="detail-view">
<div class="detail-row">
<div class="detail-label">Number</div>
<div class="detail-value">${record.number}</div>
</div>
<div class="detail-row">
<div class="detail-label">Short Description</div>
<div class="detail-value">${record.short_description}</div>
</div>
<div class="detail-row">
<div class="detail-label">Type</div>
<div class="detail-value">${record.type}</div>
</div>
<div class="detail-row">
<div class="detail-label">Risk</div>
<div class="detail-value"><span class="priority-${record.risk.toLowerCase()}">${record.risk}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Status</div>
<div class="detail-value"><span class="badge badge-${record.status.toLowerCase().replace(' ', '')}">${record.status}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Requested By</div>
<div class="detail-value">${record.requested_by || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Assigned To</div>
<div class="detail-value">${record.assigned_to || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Scheduled Start</div>
<div class="detail-value">${record.scheduled_start ? formatDate(record.scheduled_start) : '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Scheduled End</div>
<div class="detail-value">${record.scheduled_end ? formatDate(record.scheduled_end) : '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Description</div>
<div class="detail-value">${record.description || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Implementation Plan</div>
<div class="detail-value">${record.implementation_plan || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Backout Plan</div>
<div class="detail-value">${record.backout_plan || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Test Plan</div>
<div class="detail-value">${record.test_plan || '-'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Created</div>
<div class="detail-value">${formatDate(record.created)}</div>
</div>
<div class="detail-row">
<div class="detail-label">Last Updated</div>
<div class="detail-value">${formatDate(record.updated)}</div>
</div>
</div>
`;
}
modalBody.innerHTML = detailsHTML;
modal.classList.add('active');
}
// Close View Modal
function closeViewModal() {
document.getElementById('view-modal').classList.remove('active');
viewingRecord = null;
}
// Edit from View
function editFromView() {
if (viewingRecord) {
closeViewModal();
openCreateModal(viewingRecord.type, viewingRecord.id);
}
}
// Filter Table
function filterTable(type, searchTerm) {
const term = searchTerm.toLowerCase();
let data;
if (type === 'incidents') {
data = incidents;
} else if (type === 'ritm') {
data = ritms;
} else if (type === 'changes') {
data = changes;
}
const filtered = data.filter(record => {
return record.short_description.toLowerCase().includes(term) ||
record.number.toLowerCase().includes(term) ||
(record.status && record.status.toLowerCase().includes(term));
});
// Re-render with filtered data
const originalData = type === 'incidents' ? incidents : type === 'ritm' ? ritms : changes;
if (type === 'incidents') {
incidents = filtered;
renderIncidents();
incidents = originalData;
} else if (type === 'ritm') {
ritms = filtered;
renderRITMs();
ritms = originalData;
} else if (type === 'changes') {
changes = filtered;
renderChanges();
changes = originalData;
}
}
// Export Data
function exportData(type) {
let data, filename;
if (type === 'incidents') {
data = incidents;
filename = 'incidents.json';
} else if (type === 'ritm') {
data = ritms;
filename = 'ritms.json';
} else if (type === 'changes') {
data = changes;
filename = 'changes.json';
}
const dataStr = JSON.stringify(data, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
}
// Clear All Records
function clearAllRecords(type) {
let typeName, count;
if (type === 'incidents') {
typeName = 'Incidents';
count = incidents.length;
} else if (type === 'ritm') {
typeName = 'Request Items';
count = ritms.length;
} else if (type === 'changes') {
typeName = 'Changes';
count = changes.length;
}
if (count === 0) {
alert('There are no records to delete.');
return;
}
const confirmed = confirm(
`⚠️ WARNING: This will permanently delete all ${count} ${typeName}.\n\n` +
`This action cannot be undone!\n\n` +
`Click OK to proceed with deletion, or Cancel to keep your records.`
);
if (confirmed) {
// Double confirmation for safety
const doubleConfirm = confirm(
`Are you absolutely sure?\n\n` +
`This is your last chance to cancel before deleting ${count} ${typeName}.`
);
if (doubleConfirm) {
if (type === 'incidents') {
incidents = [];
localStorage.setItem('itsm_incidents', JSON.stringify(incidents));
renderIncidents();
} else if (type === 'ritm') {
ritms = [];
localStorage.setItem('itsm_ritms', JSON.stringify(ritms));
renderRITMs();
} else if (type === 'changes') {
changes = [];
localStorage.setItem('itsm_changes', JSON.stringify(changes));
renderChanges();
}
updateDashboard();
alert(`✓ Successfully deleted all ${typeName}.`);
}
}
}
// Clear All Data (Everything)
function clearAllData() {
const totalRecords = incidents.length + ritms.length + changes.length;
if (totalRecords === 0) {
alert('There are no records to delete.');
return;
}
const confirmed = confirm(
`⚠️ DANGER: This will permanently delete ALL data:\n\n` +
`• ${incidents.length} Incidents\n` +
`• ${ritms.length} Request Items\n` +
`• ${changes.length} Changes\n\n` +
`Total: ${totalRecords} records\n\n` +
`This action CANNOT be undone!\n\n` +
`Click OK to proceed with deletion, or Cancel to keep your data.`
);
if (confirmed) {
// Triple confirmation for clearing everything
const doubleConfirm = confirm(
`🚨 FINAL WARNING 🚨\n\n` +
`You are about to DELETE EVERYTHING (${totalRecords} records).\n\n` +
`This is your LAST CHANCE to cancel!\n\n` +
`Type anything in the prompt that appears next to confirm.`
);
if (doubleConfirm) {
const finalConfirm = prompt(
`To confirm deletion of ALL ${totalRecords} records, type: DELETE`
);
if (finalConfirm === 'DELETE') {
// Clear all data
incidents = [];
ritms = [];
changes = [];
// Clear localStorage
localStorage.setItem('itsm_incidents', JSON.stringify(incidents));
localStorage.setItem('itsm_ritms', JSON.stringify(ritms));
localStorage.setItem('itsm_changes', JSON.stringify(changes));
// Re-render all sections
renderIncidents();
renderRITMs();
renderChanges();
updateDashboard();
alert('✓ All data has been permanently deleted.');
} else {
alert('Deletion cancelled. You did not type DELETE correctly.');
}
}
}
}
// Format Date
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
// Initialize on load
window.onload = init;
</script>
</body>
</html>