Skip to content

Commit bd62cef

Browse files
authored
Live Ticket Counter Widget (#2054)
* Created Live Ticket Counter.html * Created Live Ticket Counter .css * Created a Server Script * Created Client Controller * Created README.md
1 parent ea32ba4 commit bd62cef

File tree

5 files changed

+478
-0
lines changed

5 files changed

+478
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
api.controller = function($scope, $interval, spModal, $window) {
2+
var c = this;
3+
4+
// Initialize
5+
c.counts = {};
6+
c.changes = {};
7+
c.lastUpdate = new Date();
8+
c.isRefreshing = false;
9+
c.autoRefresh = true;
10+
c.soundEnabled = true;
11+
c.newCritical = false;
12+
var refreshInterval;
13+
14+
// Load initial data
15+
c.$onInit = function() {
16+
c.counts = c.data.counts || {};
17+
c.previousCounts = angular.copy(c.counts);
18+
c.startAutoRefresh();
19+
};
20+
21+
// Refresh data
22+
c.refresh = function() {
23+
c.isRefreshing = true;
24+
25+
c.server.get().then(function(response) {
26+
var newCounts = response.data.counts;
27+
28+
// Calculate changes
29+
c.changes = {
30+
critical: (newCounts.critical || 0) - (c.counts.critical || 0),
31+
high: (newCounts.high || 0) - (c.counts.high || 0),
32+
medium: (newCounts.medium || 0) - (c.counts.medium || 0),
33+
low: (newCounts.low || 0) - (c.counts.low || 0)
34+
};
35+
36+
// Check for new critical tickets
37+
if (c.changes.critical > 0) {
38+
c.newCritical = true;
39+
if (c.soundEnabled) {
40+
c.playAlertSound();
41+
}
42+
43+
// Remove pulse animation after 3 seconds
44+
$interval(function() {
45+
c.newCritical = false;
46+
}, 3000, 1);
47+
}
48+
49+
// Update counts
50+
c.counts = newCounts;
51+
c.lastUpdate = new Date();
52+
c.isRefreshing = false;
53+
});
54+
};
55+
56+
// Auto-refresh toggle
57+
c.toggleAutoRefresh = function() {
58+
if (c.autoRefresh) {
59+
c.startAutoRefresh();
60+
} else {
61+
c.stopAutoRefresh();
62+
}
63+
};
64+
65+
// Start auto-refresh
66+
c.startAutoRefresh = function() {
67+
if (refreshInterval) {
68+
$interval.cancel(refreshInterval);
69+
}
70+
71+
refreshInterval = $interval(function() {
72+
c.refresh();
73+
}, 30000); // 30 seconds
74+
};
75+
76+
// Stop auto-refresh
77+
c.stopAutoRefresh = function() {
78+
if (refreshInterval) {
79+
$interval.cancel(refreshInterval);
80+
}
81+
};
82+
83+
// Play sound alert
84+
c.playAlertSound = function() {
85+
var audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSqA0fPTgjMGHm7A7+OZRQ0PVq/m77BdGAg+ltryxnMpBSl+zPLaizsIGGS57OibUBELTqXh8bllHAU2jdXwyH0vBSZ8yfDajkULEFau5u+wXRgIPpXa8sZzKQUpfszy2Ys7CBhkuezom1ARDEyl4fG5ZRwFNo3V8Mh9LwUmfMnw2o5FDBFWrebvsF0YCD6V2vLGcykFKX7M8tmLOwgYZLns6JtQEQxMpeHxuWUcBTaN1fDIfS8FJnzJ8NqORQwRVq3m77BdGAg+ldryx3MpBSl+zPLaizsIGGS57OmbUBEMTKXh8bllHAU2jdXwyH0vBSZ8yfDajkUMEVat5u+wXRgIPpXa8sZzKQUpfszy2Ys7CBhkuezom1ARDEyl4fG5ZRwFNo3V8Mh9LwUmfMnw2o5FDBFWrebvsF0YCD6V2vLGcykFKX7M8tmLOwgYZLns6JtQEQxMpeHxuWUcBTaN1fDIfS8FJnzJ8NqORQwRVq3m77BdGAg=');
86+
audio.play().catch(function(e) {
87+
console.log('Could not play sound:', e);
88+
});
89+
};
90+
91+
// Toggle sound
92+
c.toggleSound = function() {
93+
c.soundEnabled = !c.soundEnabled;
94+
if (c.soundEnabled) {
95+
spModal.alert('🔊 Sound alerts enabled');
96+
} else {
97+
spModal.alert('🔇 Sound alerts disabled');
98+
}
99+
};
100+
101+
// View tickets by priority
102+
c.viewTickets = function(priority) {
103+
var priorityNames = {
104+
'1': 'Critical',
105+
'2': 'High',
106+
'3': 'Medium',
107+
'4': 'Low'
108+
};
109+
110+
// Navigate to filtered list
111+
$window.location.href = '/incident_list.do?sysparm_query=priority=' + priority + '^active=true';
112+
};
113+
114+
// Cleanup on destroy
115+
c.$onDestroy = function() {
116+
c.stopAutoRefresh();
117+
};
118+
};
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
.ticket-counter-widget {
2+
background: white;
3+
border-radius: 12px;
4+
padding: 20px;
5+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
6+
max-width: 800px;
7+
margin: 0 auto;
8+
}
9+
10+
.widget-header {
11+
display: flex;
12+
justify-content: space-between;
13+
align-items: center;
14+
margin-bottom: 20px;
15+
border-bottom: 2px solid #f0f0f0;
16+
padding-bottom: 15px;
17+
}
18+
19+
.widget-header h3 {
20+
margin: 0;
21+
font-size: 24px;
22+
color: #333;
23+
}
24+
25+
.refresh-btn {
26+
background: #007bff;
27+
color: white;
28+
border: none;
29+
padding: 8px 12px;
30+
border-radius: 50%;
31+
font-size: 20px;
32+
cursor: pointer;
33+
transition: all 0.3s ease;
34+
}
35+
36+
.refresh-btn:hover {
37+
background: #0056b3;
38+
transform: scale(1.1);
39+
}
40+
41+
.refresh-btn.spinning {
42+
animation: spin 1s linear infinite;
43+
}
44+
45+
@keyframes spin {
46+
from { transform: rotate(0deg); }
47+
to { transform: rotate(360deg); }
48+
}
49+
50+
.counter-grid {
51+
display: grid;
52+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
53+
gap: 15px;
54+
margin-bottom: 20px;
55+
}
56+
57+
.counter-card {
58+
background: white;
59+
border-radius: 10px;
60+
padding: 20px;
61+
text-align: center;
62+
cursor: pointer;
63+
transition: all 0.3s ease;
64+
border: 2px solid #e0e0e0;
65+
}
66+
67+
.counter-card:hover {
68+
transform: translateY(-5px);
69+
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
70+
}
71+
72+
.counter-card.critical {
73+
border-left: 5px solid #dc3545;
74+
}
75+
76+
.counter-card.high {
77+
border-left: 5px solid #fd7e14;
78+
}
79+
80+
.counter-card.medium {
81+
border-left: 5px solid #ffc107;
82+
}
83+
84+
.counter-card.low {
85+
border-left: 5px solid #28a745;
86+
}
87+
88+
.counter-icon {
89+
font-size: 32px;
90+
margin-bottom: 10px;
91+
}
92+
93+
.counter-number {
94+
font-size: 48px;
95+
font-weight: bold;
96+
color: #333;
97+
margin: 10px 0;
98+
}
99+
100+
.counter-number.pulse {
101+
animation: pulse 1s ease-in-out infinite;
102+
}
103+
104+
@keyframes pulse {
105+
0% { transform: scale(1); }
106+
50% { transform: scale(1.1); color: #dc3545; }
107+
100% { transform: scale(1); }
108+
}
109+
110+
.counter-label {
111+
font-size: 14px;
112+
color: #666;
113+
font-weight: 600;
114+
text-transform: uppercase;
115+
}
116+
117+
.counter-change {
118+
margin-top: 5px;
119+
padding: 3px 8px;
120+
background: #ff6b6b;
121+
color: white;
122+
border-radius: 12px;
123+
font-size: 11px;
124+
display: inline-block;
125+
animation: slideIn 0.5s ease;
126+
}
127+
128+
@keyframes slideIn {
129+
from {
130+
opacity: 0;
131+
transform: translateY(-10px);
132+
}
133+
to {
134+
opacity: 1;
135+
transform: translateY(0);
136+
}
137+
}
138+
139+
.widget-footer {
140+
display: flex;
141+
justify-content: space-between;
142+
align-items: center;
143+
padding-top: 15px;
144+
border-top: 1px solid #f0f0f0;
145+
font-size: 12px;
146+
color: #666;
147+
}
148+
149+
.auto-refresh label {
150+
display: flex;
151+
align-items: center;
152+
gap: 5px;
153+
cursor: pointer;
154+
}
155+
156+
.sound-toggle {
157+
position: absolute;
158+
top: 20px;
159+
right: 70px;
160+
}
161+
162+
.sound-toggle button {
163+
background: #f0f0f0;
164+
border: none;
165+
padding: 8px 12px;
166+
border-radius: 50%;
167+
font-size: 20px;
168+
cursor: pointer;
169+
transition: all 0.3s ease;
170+
}
171+
172+
.sound-toggle button.active {
173+
background: #28a745;
174+
}
175+
176+
.sound-toggle button:hover {
177+
transform: scale(1.1);
178+
}
179+
180+
@media (max-width: 600px) {
181+
.counter-grid {
182+
grid-template-columns: repeat(2, 1fr);
183+
}
184+
185+
.widget-footer {
186+
flex-direction: column;
187+
gap: 10px;
188+
}
189+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<div class="ticket-counter-widget">
2+
<div class="widget-header">
3+
<h3>🎫 Live Ticket Monitor</h3>
4+
<button class="refresh-btn" ng-click="c.refresh()" ng-class="{'spinning': c.isRefreshing}">
5+
🔄
6+
</button>
7+
</div>
8+
9+
<div class="counter-grid">
10+
<!-- Critical Priority -->
11+
<div class="counter-card critical" ng-click="c.viewTickets('1')">
12+
<div class="counter-icon">🔴</div>
13+
<div class="counter-number" ng-class="{'pulse': c.newCritical}">
14+
{{c.counts.critical || 0}}
15+
</div>
16+
<div class="counter-label">Critical</div>
17+
<div class="counter-change" ng-if="c.changes.critical > 0">
18+
+{{c.changes.critical}} new
19+
</div>
20+
</div>
21+
22+
<!-- High Priority -->
23+
<div class="counter-card high" ng-click="c.viewTickets('2')">
24+
<div class="counter-icon">🟠</div>
25+
<div class="counter-number">
26+
{{c.counts.high || 0}}
27+
</div>
28+
<div class="counter-label">High</div>
29+
<div class="counter-change" ng-if="c.changes.high > 0">
30+
+{{c.changes.high}} new
31+
</div>
32+
</div>
33+
34+
<!-- Medium Priority -->
35+
<div class="counter-card medium" ng-click="c.viewTickets('3')">
36+
<div class="counter-icon">🟡</div>
37+
<div class="counter-number">
38+
{{c.counts.medium || 0}}
39+
</div>
40+
<div class="counter-label">Medium</div>
41+
<div class="counter-change" ng-if="c.changes.medium > 0">
42+
+{{c.changes.medium}} new
43+
</div>
44+
</div>
45+
46+
<!-- Low Priority -->
47+
<div class="counter-card low" ng-click="c.viewTickets('4')">
48+
<div class="counter-icon">🟢</div>
49+
<div class="counter-number">
50+
{{c.counts.low || 0}}
51+
</div>
52+
<div class="counter-label">Low</div>
53+
<div class="counter-change" ng-if="c.changes.low > 0">
54+
+{{c.changes.low}} new
55+
</div>
56+
</div>
57+
</div>
58+
59+
<div class="widget-footer">
60+
<div class="last-update">
61+
Last updated: {{c.lastUpdate | date:'HH:mm:ss'}}
62+
</div>
63+
<div class="auto-refresh">
64+
<label>
65+
<input type="checkbox" ng-model="c.autoRefresh" ng-change="c.toggleAutoRefresh()">
66+
Auto-refresh (30s)
67+
</label>
68+
</div>
69+
</div>
70+
71+
<!-- Sound Toggle -->
72+
<div class="sound-toggle">
73+
<button ng-click="c.toggleSound()" ng-class="{'active': c.soundEnabled}">
74+
{{c.soundEnabled ? '🔊' : '🔇'}}
75+
</button>
76+
</div>
77+
</div>

0 commit comments

Comments
 (0)