Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
};
};
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<div class="ticket-counter-widget">
<div class="widget-header">
<h3>🎫 Live Ticket Monitor</h3>
<button class="refresh-btn" ng-click="c.refresh()" ng-class="{'spinning': c.isRefreshing}">
🔄
</button>
</div>

<div class="counter-grid">
<!-- Critical Priority -->
<div class="counter-card critical" ng-click="c.viewTickets('1')">
<div class="counter-icon">🔴</div>
<div class="counter-number" ng-class="{'pulse': c.newCritical}">
{{c.counts.critical || 0}}
</div>
<div class="counter-label">Critical</div>
<div class="counter-change" ng-if="c.changes.critical > 0">
+{{c.changes.critical}} new
</div>
</div>

<!-- High Priority -->
<div class="counter-card high" ng-click="c.viewTickets('2')">
<div class="counter-icon">🟠</div>
<div class="counter-number">
{{c.counts.high || 0}}
</div>
<div class="counter-label">High</div>
<div class="counter-change" ng-if="c.changes.high > 0">
+{{c.changes.high}} new
</div>
</div>

<!-- Medium Priority -->
<div class="counter-card medium" ng-click="c.viewTickets('3')">
<div class="counter-icon">🟡</div>
<div class="counter-number">
{{c.counts.medium || 0}}
</div>
<div class="counter-label">Medium</div>
<div class="counter-change" ng-if="c.changes.medium > 0">
+{{c.changes.medium}} new
</div>
</div>

<!-- Low Priority -->
<div class="counter-card low" ng-click="c.viewTickets('4')">
<div class="counter-icon">🟢</div>
<div class="counter-number">
{{c.counts.low || 0}}
</div>
<div class="counter-label">Low</div>
<div class="counter-change" ng-if="c.changes.low > 0">
+{{c.changes.low}} new
</div>
</div>
</div>

<div class="widget-footer">
<div class="last-update">
Last updated: {{c.lastUpdate | date:'HH:mm:ss'}}
</div>
<div class="auto-refresh">
<label>
<input type="checkbox" ng-model="c.autoRefresh" ng-change="c.toggleAutoRefresh()">
Auto-refresh (30s)
</label>
</div>
</div>

<!-- Sound Toggle -->
<div class="sound-toggle">
<button ng-click="c.toggleSound()" ng-class="{'active': c.soundEnabled}">
{{c.soundEnabled ? '🔊' : '🔇'}}
</button>
</div>
</div>
Loading
Loading