diff --git a/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Client Controller b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Client Controller new file mode 100644 index 0000000000..47d31fdea1 --- /dev/null +++ b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Client Controller @@ -0,0 +1,118 @@ +api.controller = function($scope, $interval, spModal, $window) { + var c = this; + + // Initialize + c.counts = {}; + c.changes = {}; + c.lastUpdate = new Date(); + c.isRefreshing = false; + c.autoRefresh = true; + c.soundEnabled = true; + c.newCritical = false; + var refreshInterval; + + // Load initial data + c.$onInit = function() { + c.counts = c.data.counts || {}; + c.previousCounts = angular.copy(c.counts); + c.startAutoRefresh(); + }; + + // Refresh data + c.refresh = function() { + c.isRefreshing = true; + + c.server.get().then(function(response) { + var newCounts = response.data.counts; + + // Calculate changes + c.changes = { + critical: (newCounts.critical || 0) - (c.counts.critical || 0), + high: (newCounts.high || 0) - (c.counts.high || 0), + medium: (newCounts.medium || 0) - (c.counts.medium || 0), + low: (newCounts.low || 0) - (c.counts.low || 0) + }; + + // Check for new critical tickets + if (c.changes.critical > 0) { + c.newCritical = true; + if (c.soundEnabled) { + c.playAlertSound(); + } + + // Remove pulse animation after 3 seconds + $interval(function() { + c.newCritical = false; + }, 3000, 1); + } + + // Update counts + c.counts = newCounts; + c.lastUpdate = new Date(); + c.isRefreshing = false; + }); + }; + + // Auto-refresh toggle + c.toggleAutoRefresh = function() { + if (c.autoRefresh) { + c.startAutoRefresh(); + } else { + c.stopAutoRefresh(); + } + }; + + // Start auto-refresh + c.startAutoRefresh = function() { + if (refreshInterval) { + $interval.cancel(refreshInterval); + } + + refreshInterval = $interval(function() { + c.refresh(); + }, 30000); // 30 seconds + }; + + // Stop auto-refresh + c.stopAutoRefresh = function() { + if (refreshInterval) { + $interval.cancel(refreshInterval); + } + }; + + // Play sound alert + c.playAlertSound = function() { + var audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSqA0fPTgjMGHm7A7+OZRQ0PVq/m77BdGAg+ltryxnMpBSl+zPLaizsIGGS57OibUBELTqXh8bllHAU2jdXwyH0vBSZ8yfDajkULEFau5u+wXRgIPpXa8sZzKQUpfszy2Ys7CBhkuezom1ARDEyl4fG5ZRwFNo3V8Mh9LwUmfMnw2o5FDBFWrebvsF0YCD6V2vLGcykFKX7M8tmLOwgYZLns6JtQEQxMpeHxuWUcBTaN1fDIfS8FJnzJ8NqORQwRVq3m77BdGAg+ldryx3MpBSl+zPLaizsIGGS57OmbUBEMTKXh8bllHAU2jdXwyH0vBSZ8yfDajkUMEVat5u+wXRgIPpXa8sZzKQUpfszy2Ys7CBhkuezom1ARDEyl4fG5ZRwFNo3V8Mh9LwUmfMnw2o5FDBFWrebvsF0YCD6V2vLGcykFKX7M8tmLOwgYZLns6JtQEQxMpeHxuWUcBTaN1fDIfS8FJnzJ8NqORQwRVq3m77BdGAg='); + audio.play().catch(function(e) { + console.log('Could not play sound:', e); + }); + }; + + // Toggle sound + c.toggleSound = function() { + c.soundEnabled = !c.soundEnabled; + if (c.soundEnabled) { + spModal.alert('🔊 Sound alerts enabled'); + } else { + spModal.alert('🔇 Sound alerts disabled'); + } + }; + + // View tickets by priority + c.viewTickets = function(priority) { + var priorityNames = { + '1': 'Critical', + '2': 'High', + '3': 'Medium', + '4': 'Low' + }; + + // Navigate to filtered list + $window.location.href = '/incident_list.do?sysparm_query=priority=' + priority + '^active=true'; + }; + + // Cleanup on destroy + c.$onDestroy = function() { + c.stopAutoRefresh(); + }; +}; diff --git a/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter .css b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter .css new file mode 100644 index 0000000000..21517211d1 --- /dev/null +++ b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter .css @@ -0,0 +1,189 @@ +.ticket-counter-widget { + background: white; + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + max-width: 800px; + margin: 0 auto; +} + +.widget-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + border-bottom: 2px solid #f0f0f0; + padding-bottom: 15px; +} + +.widget-header h3 { + margin: 0; + font-size: 24px; + color: #333; +} + +.refresh-btn { + background: #007bff; + color: white; + border: none; + padding: 8px 12px; + border-radius: 50%; + font-size: 20px; + cursor: pointer; + transition: all 0.3s ease; +} + +.refresh-btn:hover { + background: #0056b3; + transform: scale(1.1); +} + +.refresh-btn.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.counter-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; + margin-bottom: 20px; +} + +.counter-card { + background: white; + border-radius: 10px; + padding: 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid #e0e0e0; +} + +.counter-card:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0,0,0,0.2); +} + +.counter-card.critical { + border-left: 5px solid #dc3545; +} + +.counter-card.high { + border-left: 5px solid #fd7e14; +} + +.counter-card.medium { + border-left: 5px solid #ffc107; +} + +.counter-card.low { + border-left: 5px solid #28a745; +} + +.counter-icon { + font-size: 32px; + margin-bottom: 10px; +} + +.counter-number { + font-size: 48px; + font-weight: bold; + color: #333; + margin: 10px 0; +} + +.counter-number.pulse { + animation: pulse 1s ease-in-out infinite; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); color: #dc3545; } + 100% { transform: scale(1); } +} + +.counter-label { + font-size: 14px; + color: #666; + font-weight: 600; + text-transform: uppercase; +} + +.counter-change { + margin-top: 5px; + padding: 3px 8px; + background: #ff6b6b; + color: white; + border-radius: 12px; + font-size: 11px; + display: inline-block; + animation: slideIn 0.5s ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.widget-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 15px; + border-top: 1px solid #f0f0f0; + font-size: 12px; + color: #666; +} + +.auto-refresh label { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; +} + +.sound-toggle { + position: absolute; + top: 20px; + right: 70px; +} + +.sound-toggle button { + background: #f0f0f0; + border: none; + padding: 8px 12px; + border-radius: 50%; + font-size: 20px; + cursor: pointer; + transition: all 0.3s ease; +} + +.sound-toggle button.active { + background: #28a745; +} + +.sound-toggle button:hover { + transform: scale(1.1); +} + +@media (max-width: 600px) { + .counter-grid { + grid-template-columns: repeat(2, 1fr); + } + + .widget-footer { + flex-direction: column; + gap: 10px; + } +} diff --git a/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter.html b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter.html new file mode 100644 index 0000000000..1fd2d19b9f --- /dev/null +++ b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter.html @@ -0,0 +1,77 @@ +
+
+

🎫 Live Ticket Monitor

+ +
+ +
+ +
+
🔴
+
+ {{c.counts.critical || 0}} +
+
Critical
+
+ +{{c.changes.critical}} new +
+
+ + +
+
🟠
+
+ {{c.counts.high || 0}} +
+
High
+
+ +{{c.changes.high}} new +
+
+ + +
+
🟡
+
+ {{c.counts.medium || 0}} +
+
Medium
+
+ +{{c.changes.medium}} new +
+
+ + +
+
🟢
+
+ {{c.counts.low || 0}} +
+
Low
+
+ +{{c.changes.low}} new +
+
+
+ + + + +
+ +
+
diff --git a/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/README.md b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/README.md new file mode 100644 index 0000000000..642f6c3250 --- /dev/null +++ b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/README.md @@ -0,0 +1,39 @@ +# Live Ticket Counter - Service Portal Widget. + +## Use Case +The Live Ticket Counter widget displays a real-time count of incident tickets categorized by priority (Critical, High, Medium, Low). It visually updates with an animation to indicate refresh activity and allows users to click on any priority count to view a filtered list of tickets of that priority. This helps support teams monitor ticket load dynamically and quickly access relevant tickets for faster incident management. + +## Why it's unique +- Real-time ticket count updates without page refresh +- Visual pulse animation highlights new critical tickets +- Clickable cards open filtered ticket lists by priority +- Beginner-friendly and easy to implement + +## How it is Useful in ServiceNow +- Real-time updates keep support agents and managers instantly informed of critical ticket volume changes, helping prioritize work promptly. +- Visible counts by priority enable supervision to redistribute workloads or escalate issues proactively. +- Agents save time by accessing filtered ticket lists with a click instead of manually searching for tickets by priority. +- The widget's last updated timestamp and auto-refresh toggle give users control and confidence in data freshness. +- As a light, interactive component, it complements existing dashboards and pages without heavy customizations. + +## How to Use + +1. Create Widget + - Go to Service Portal > Widgets + - Click "New" + - Copy-paste HTML, Client Controller, Server Script, and CSS into appropriate sections + +2. Add to Page + - Place the widget on any Service Portal page where ticket monitoring is required + - Widget ID: `live_ticket_counter_sp` + +3. Test and Customize + - View tickets by clicking on priority cards + - Toggle auto-refresh and sound alerts + - Modify styling or priority levels as needed + +## Compatibility +This Html , css , client , server code is compatible with all standard ServiceNow instances without requiring ES2021 features. + +## Files +- `Live Ticket Counter.html`,`Live Ticket Counter .css`,`Server Script`,`Client Script`, — these are files. diff --git a/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Server Script b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Server Script new file mode 100644 index 0000000000..f74ef31b8b --- /dev/null +++ b/Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Server Script @@ -0,0 +1,55 @@ +(function() { + // Get ticket counts by priority + data.counts = {}; + + try { + // Count Critical (Priority 1) + var criticalGR = new GlideAggregate('incident'); + criticalGR.addQuery('priority', '1'); + criticalGR.addQuery('active', true); + criticalGR.addAggregate('COUNT'); + criticalGR.query(); + if (criticalGR.next()) { + data.counts.critical = parseInt(criticalGR.getAggregate('COUNT')); + } + + // Count High (Priority 2) + var highGR = new GlideAggregate('incident'); + highGR.addQuery('priority', '2'); + highGR.addQuery('active', true); + highGR.addAggregate('COUNT'); + highGR.query(); + if (highGR.next()) { + data.counts.high = parseInt(highGR.getAggregate('COUNT')); + } + + // Count Medium (Priority 3) + var mediumGR = new GlideAggregate('incident'); + mediumGR.addQuery('priority', '3'); + mediumGR.addQuery('active', true); + mediumGR.addAggregate('COUNT'); + mediumGR.query(); + if (mediumGR.next()) { + data.counts.medium = parseInt(mediumGR.getAggregate('COUNT')); + } + + // Count Low (Priority 4) + var lowGR = new GlideAggregate('incident'); + lowGR.addQuery('priority', '4'); + lowGR.addQuery('active', true); + lowGR.addAggregate('COUNT'); + lowGR.query(); + if (lowGR.next()) { + data.counts.low = parseInt(lowGR.getAggregate('COUNT')); + } + + } catch (e) { + gs.error('Error in Live Ticket Counter: ' + e.message); + data.counts = { + critical: 0, + high: 0, + medium: 0, + low: 0 + }; + } +})();