From 28e80715708601e69453c93a57b6f712ba1a67ab Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 9 Jan 2026 07:27:37 -0500 Subject: [PATCH] Add Employee Recognition feature to notifications system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Recognition notification type (ID 5) with blue color - Add employeesso field to notifications table - Create carousel display for Recognition on shopfloor dashboard - Show employee names (lookup from wjf_employees) instead of SSO - Auto-set starttime to NOW and endtime to 4AM next day - Auto-enable shopfloor display for Recognition type - Add Achievements tab to employee profile (displayprofile.asp) - Hide Recognition from calendar view - Add lookupemployee.asp AJAX endpoint for name preview - Fix datetime double-formatting bug in save/update files - Fix URL parameter loading on shopfloor dashboard init 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- addnotification.asp | 83 +++++++- apishopfloor.asp | 70 ++++++- calendar.asp | 2 + displayprofile.asp | 140 +++++++++++-- editnotification.asp | 84 +++++++- lookupemployee.asp | 76 +++++++ savenotificationdirect.asp | 102 +++++++++- shopfloor-dashboard/index.html | 346 ++++++++++++++++++++++++++++++-- sql/add_recognition_feature.sql | 16 ++ updatenotificationdirect.asp | 70 ++++++- 10 files changed, 938 insertions(+), 51 deletions(-) create mode 100644 lookupemployee.asp create mode 100644 sql/add_recognition_feature.sql diff --git a/addnotification.asp b/addnotification.asp index 0750005..40f2a0e 100644 --- a/addnotification.asp +++ b/addnotification.asp @@ -112,14 +112,24 @@ Link this notification to a specific application (e.g., for software updates) -
+
Optional ServiceNow ticket number
-
+ + +
@@ -246,7 +256,76 @@ startInput.value = formatDateTime(now); // Leave endInput blank by default for indefinite notifications + + // Handle notification type change + var typeSelect = document.getElementById('notificationtypeid'); + typeSelect.addEventListener('change', handleTypeChange); + handleTypeChange(); // Initial check }); + + // Recognition type ID (from database) + var RECOGNITION_TYPE_ID = '5'; + + function handleTypeChange() { + var typeSelect = document.getElementById('notificationtypeid'); + var selectedText = typeSelect.options[typeSelect.selectedIndex].text; + var isRecognition = selectedText.toLowerCase().indexOf('recognition') !== -1; + + // Show/hide SSO field + document.getElementById('employeessoGroup').style.display = isRecognition ? 'block' : 'none'; + document.getElementById('employeesso').required = isRecognition; + + // Show/hide time fields and ticket number for Recognition + document.getElementById('timeFieldsRow').style.display = isRecognition ? 'none' : 'flex'; + document.getElementById('ticketnumberGroup').style.display = isRecognition ? 'none' : 'block'; + + // For Recognition, remove required from starttime + document.getElementById('starttime').required = !isRecognition; + + // Auto-check shopfloor dashboard for Recognition + if (isRecognition) { + document.getElementById('isshopfloor').checked = true; + } + } + + // Lookup employee names by SSO + var lookupTimeout = null; + document.addEventListener('DOMContentLoaded', function() { + var ssoInput = document.getElementById('employeesso'); + ssoInput.addEventListener('input', function() { + clearTimeout(lookupTimeout); + lookupTimeout = setTimeout(lookupEmployees, 500); + }); + }); + + function lookupEmployees() { + var ssoInput = document.getElementById('employeesso'); + var ssos = ssoInput.value.trim(); + var previewDiv = document.getElementById('employeePreview'); + var namesSpan = document.getElementById('employeeNames'); + + if (!ssos) { + previewDiv.style.display = 'none'; + return; + } + + // Call AJAX endpoint to lookup names + fetch('./lookupemployee.asp?sso=' + encodeURIComponent(ssos)) + .then(function(response) { return response.json(); }) + .then(function(data) { + if (data.success && data.names) { + namesSpan.textContent = data.names; + previewDiv.style.display = 'block'; + } else { + namesSpan.textContent = data.error || 'Employee not found'; + namesSpan.className = 'badge badge-warning'; + previewDiv.style.display = 'block'; + } + }) + .catch(function(err) { + previewDiv.style.display = 'none'; + }); + } diff --git a/apishopfloor.asp b/apishopfloor.asp index 75b50e4..7df6f5a 100644 --- a/apishopfloor.asp +++ b/apishopfloor.asp @@ -14,7 +14,7 @@ businessUnitFilter = Request.QueryString("businessunit") strSQL = "SELECT n.notificationid, n.notification, n.starttime, n.endtime, " & _ "n.ticketnumber, n.link, n.isactive, n.isshopfloor, n.businessunitid, " & _ - "nt.typename, nt.typecolor, bu.businessunit, " & _ + "n.employeesso, nt.typename, nt.typecolor, bu.businessunit, " & _ "CASE " & _ " WHEN n.starttime <= NOW() AND (n.endtime IS NULL OR n.endtime >= NOW()) THEN 1 " & _ " WHEN n.endtime IS NOT NULL AND n.endtime < NOW() AND DATE_ADD(n.endtime, INTERVAL 30 MINUTE) >= NOW() THEN 1 " & _ @@ -82,7 +82,9 @@ Do While Not rs.EOF End If jsonOutput = jsonOutput & """typename"":""" & JSEscape(rs("typename") & "") & """," jsonOutput = jsonOutput & """typecolor"":""" & JSEscape(rs("typecolor") & "") & """," - jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & "" + jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & "," + jsonOutput = jsonOutput & """employeesso"":" & StrOrNull(rs("employeesso")) & "," + jsonOutput = jsonOutput & """employeename"":" & StrOrNull(LookupEmployeeNames(rs("employeesso"))) & "" jsonOutput = jsonOutput & "}" End If @@ -116,7 +118,9 @@ Do While Not rs.EOF jsonOutput = jsonOutput & """isshopfloor"":true," jsonOutput = jsonOutput & """typename"":""" & JSEscape(rs("typename") & "") & """," jsonOutput = jsonOutput & """typecolor"":""" & JSEscape(rs("typecolor") & "") & """," - jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & "" + jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & "," + jsonOutput = jsonOutput & """employeesso"":" & StrOrNull(rs("employeesso")) & "," + jsonOutput = jsonOutput & """employeename"":" & StrOrNull(LookupEmployeeNames(rs("employeesso"))) & "" jsonOutput = jsonOutput & "}" End If @@ -163,4 +167,64 @@ Function StrOrNull(s) StrOrNull = """" & JSEscape(s & "") & """" End If End Function + +' Look up employee name(s) from SSO(s) +Function LookupEmployeeNames(ssoInput) + If IsNull(ssoInput) Or Len(ssoInput & "") = 0 Then + LookupEmployeeNames = "" + Exit Function + End If + + Dim empConn, empCmd, empRs, ssoList, names, i, sso, firstName, lastName + + On Error Resume Next + Set empConn = Server.CreateObject("ADODB.Connection") + empConn.ConnectionString = GetEmployeeConnectionString() + empConn.Open + + If Err.Number <> 0 Then + ' DEBUG: Return error info + LookupEmployeeNames = "[DB Error: " & Err.Description & "]" + Exit Function + End If + + ssoList = Split(ssoInput & "", ",") + names = "" + + For i = 0 To UBound(ssoList) + sso = Trim(ssoList(i)) + If IsNumeric(sso) And Len(sso) > 0 Then + Set empCmd = Server.CreateObject("ADODB.Command") + empCmd.ActiveConnection = empConn + empCmd.CommandText = "SELECT First_Name, Last_Name FROM employees WHERE SSO = ?" + empCmd.CommandType = 1 + empCmd.Parameters.Append empCmd.CreateParameter("@sso", 3, 1, , CLng(sso)) + + Set empRs = empCmd.Execute() + If Err.Number = 0 And Not empRs.EOF Then + firstName = empRs("First_Name") & "" + lastName = empRs("Last_Name") & "" + If Len(names) > 0 Then names = names & ", " + names = names & firstName & " " & lastName + End If + + If Not empRs Is Nothing Then + If empRs.State = 1 Then empRs.Close + Set empRs = Nothing + End If + Set empCmd = Nothing + End If + Next + + empConn.Close + Set empConn = Nothing + On Error GoTo 0 + + If Len(names) > 0 Then + LookupEmployeeNames = names + Else + ' DEBUG: No names found + LookupEmployeeNames = "[Not found: " & ssoInput & "]" + End If +End Function %> diff --git a/calendar.asp b/calendar.asp index 655d47a..8ccd322 100644 --- a/calendar.asp +++ b/calendar.asp @@ -185,11 +185,13 @@ Function FormatDateISO(dt) End Function ' Fetch all ACTIVE notifications with type information and convert to FullCalendar events +' Exclude Recognition type (notificationtypeid = 5) from calendar view Dim strSQL, rs, isFirst strSQL = "SELECT n.notificationid, n.notification, n.starttime, n.endtime, n.isactive, n.ticketnumber, n.link, " & _ "nt.typename, nt.typecolor " & _ "FROM notifications n " & _ "LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid " & _ + "WHERE (nt.typename IS NULL OR nt.typename <> 'Recognition') " & _ "ORDER BY n.starttime ASC" Set rs = objconn.Execute(strSQL) diff --git a/displayprofile.asp b/displayprofile.asp index 06a8659..5f88003 100644 --- a/displayprofile.asp +++ b/displayprofile.asp @@ -58,6 +58,9 @@ Option Explicit <% ' Use parameterized query to prevent SQL injection + Dim employeeFound + employeeFound = False + Set cmd = Server.CreateObject("ADODB.Command") cmd.ActiveConnection = objconn cmd.CommandText = "SELECT * FROM employees WHERE SSO = ?" @@ -65,27 +68,34 @@ Option Explicit cmd.Parameters.Append cmd.CreateParameter("@sso", 3, 1, , sso) Set rs = cmd.Execute() - If rs.EOF Then - ' Default to SSO 1 if not found - rs.Close - Set rs = Nothing - Set cmd = Nothing - Set cmd = Server.CreateObject("ADODB.Command") - cmd.ActiveConnection = objconn - cmd.CommandText = "SELECT * FROM employees WHERE SSO = ?" - cmd.CommandType = 1 - cmd.Parameters.Append cmd.CreateParameter("@sso", 3, 1, , 1) - Set rs = cmd.Execute() + If Not rs.EOF Then + employeeFound = True End If Set cmd = Nothing -%> + If employeeFound Then +%> " alt="Card image cap">
<%=Server.HTMLEncode(rs("First_Name") & "")%> <%=Server.HTMLEncode(rs("Last_Name") & "")%>
<% + Else +%> +
+ +
+
+
+
Employee Not Found
+

SSO: <%=Server.HTMLEncode(sso)%>

+
+<% + End If +%> +<% +If employeeFound Then ' Easter Egg for SSO 570005354 Dim showEasterEgg showEasterEgg = False @@ -238,6 +248,7 @@ ELSE
<% END IF +End If ' employeeFound %>
@@ -253,10 +264,14 @@ END IF +
Profile
+<% If employeeFound Then %>
<%=Server.HTMLEncode(rs("First_Name") & "")%> <%=Server.HTMLEncode(rs("Last_Name") & "")%>
@@ -276,6 +291,15 @@ END IF
+<% Else %> +
+ +

No employee record found for SSO: <%=Server.HTMLEncode(sso)%>

+ + Search Again + +
+<% End If %>
@@ -526,6 +550,98 @@ End If
+
+
Achievements & Recognition
+<% +' Query achievements from notifications table (Recognition type) +Dim objConnAchieve, achieveAvailable +achieveAvailable = False + +On Error Resume Next +Set objConnAchieve = Server.CreateObject("ADODB.Connection") +objConnAchieve.ConnectionString = GetConnectionString() +objConnAchieve.Open +If Err.Number = 0 Then + achieveAvailable = True +Else + Err.Clear +End If +On Error Goto 0 + +If achieveAvailable And IsNumeric(sso) Then + ' Get achievements for this SSO (all-time) + Dim cmdAchieve, rsAchieve + Dim achieveSQL, achieveCount + achieveCount = 0 + + ' Recognition type ID is 5, search for SSO in employeesso field + achieveSQL = "SELECT n.notification, n.starttime " & _ + "FROM notifications n " & _ + "WHERE n.notificationtypeid = 5 " & _ + "AND n.employeesso LIKE ? " & _ + "ORDER BY n.starttime DESC" + + Set cmdAchieve = Server.CreateObject("ADODB.Command") + cmdAchieve.ActiveConnection = objConnAchieve + cmdAchieve.CommandText = achieveSQL + cmdAchieve.CommandType = 1 + cmdAchieve.Parameters.Append cmdAchieve.CreateParameter("@sso", 200, 1, 100, "%" & sso & "%") + + On Error Resume Next + Set rsAchieve = cmdAchieve.Execute + + If Err.Number = 0 Then +%> +
+<% + Do While Not rsAchieve.EOF + achieveCount = achieveCount + 1 + Dim achieveDate, achieveText + achieveDate = "" + achieveText = rsAchieve("notification") & "" + + If Not IsNull(rsAchieve("starttime")) Then + achieveDate = FormatDateTime(rsAchieve("starttime"), 2) + End If +%> +
+
+
<%=Server.HTMLEncode(achieveText)%>
+ <%=achieveDate%> +
+
+<% + rsAchieve.MoveNext + Loop + + rsAchieve.Close + Set rsAchieve = Nothing +%> +
+<% + If achieveCount = 0 Then +%> +
+ +

No achievements recorded yet.

+
+<% + End If + End If + Set cmdAchieve = Nothing + + objConnAchieve.Close + Set objConnAchieve = Nothing +Else +%> +
+ Achievements not available. +
+<% +End If +%> +
+
diff --git a/editnotification.asp b/editnotification.asp index a0ca292..f33a174 100644 --- a/editnotification.asp +++ b/editnotification.asp @@ -188,7 +188,7 @@ Link this notification to a specific application (e.g., for software updates) -
+
" @@ -196,7 +196,18 @@ Optional ServiceNow ticket number
-
+ + +
@@ -318,6 +329,75 @@ function clearEndtime() { document.getElementById('endtime').value = ''; } + + // Handle notification type change for Recognition + document.addEventListener('DOMContentLoaded', function() { + var typeSelect = document.getElementById('notificationtypeid'); + typeSelect.addEventListener('change', handleTypeChange); + handleTypeChange(); // Initial check + + // Employee lookup on SSO input + var ssoInput = document.getElementById('employeesso'); + ssoInput.addEventListener('input', function() { + clearTimeout(window.lookupTimeout); + window.lookupTimeout = setTimeout(lookupEmployees, 500); + }); + // Trigger initial lookup if SSO has value + if (ssoInput.value.trim()) { + lookupEmployees(); + } + }); + + function handleTypeChange() { + var typeSelect = document.getElementById('notificationtypeid'); + var selectedText = typeSelect.options[typeSelect.selectedIndex].text; + var isRecognition = selectedText.toLowerCase().indexOf('recognition') !== -1; + + // Show/hide SSO field + document.getElementById('employeessoGroup').style.display = isRecognition ? 'block' : 'none'; + document.getElementById('employeesso').required = isRecognition; + + // Show/hide time fields and ticket number for Recognition + document.getElementById('timeFieldsRow').style.display = isRecognition ? 'none' : 'flex'; + document.getElementById('ticketnumberGroup').style.display = isRecognition ? 'none' : 'block'; + + // For Recognition, remove required from starttime + document.getElementById('starttime').required = !isRecognition; + + // Auto-check shopfloor dashboard for Recognition + if (isRecognition) { + document.getElementById('isshopfloor').checked = true; + } + } + + function lookupEmployees() { + var ssoInput = document.getElementById('employeesso'); + var ssos = ssoInput.value.trim(); + var previewDiv = document.getElementById('employeePreview'); + var namesSpan = document.getElementById('employeeNames'); + + if (!ssos) { + previewDiv.style.display = 'none'; + return; + } + + fetch('./lookupemployee.asp?sso=' + encodeURIComponent(ssos)) + .then(function(response) { return response.json(); }) + .then(function(data) { + if (data.success && data.names) { + namesSpan.textContent = data.names; + namesSpan.className = 'badge badge-info'; + previewDiv.style.display = 'block'; + } else { + namesSpan.textContent = data.error || 'Employee not found'; + namesSpan.className = 'badge badge-warning'; + previewDiv.style.display = 'block'; + } + }) + .catch(function(err) { + previewDiv.style.display = 'none'; + }); + } diff --git a/lookupemployee.asp b/lookupemployee.asp new file mode 100644 index 0000000..3ae36ee --- /dev/null +++ b/lookupemployee.asp @@ -0,0 +1,76 @@ +<%@ Language=VBScript %> +<% +Response.ContentType = "application/json" +Response.Charset = "UTF-8" +Response.AddHeader "Access-Control-Allow-Origin", "*" +Response.AddHeader "Cache-Control", "no-cache, no-store, must-revalidate" +%><% + +' Employee lookup API for Recognition notifications +' Input: sso (single SSO or comma-separated list) +' Output: JSON with employee names + +Dim ssoInput, ssoList, names, jsonOutput +ssoInput = Trim(Request.QueryString("sso")) + +If Len(ssoInput) = 0 Then + Response.Write "{""success"":false,""error"":""SSO parameter required""}" + Response.End +End If + +' Split by comma for multiple SSOs +ssoList = Split(ssoInput, ",") +names = "" + +Dim i, sso, cmd, rs, firstName, lastName +For i = 0 To UBound(ssoList) + sso = Trim(ssoList(i)) + + ' Validate SSO is numeric + If IsNumeric(sso) And Len(sso) > 0 Then + ' Query employee database + Set cmd = Server.CreateObject("ADODB.Command") + cmd.ActiveConnection = objconn + cmd.CommandText = "SELECT First_Name, Last_Name FROM employees WHERE SSO = ?" + cmd.CommandType = 1 + cmd.Parameters.Append cmd.CreateParameter("@sso", 3, 1, , CLng(sso)) + + On Error Resume Next + Set rs = cmd.Execute() + + If Err.Number = 0 And Not rs.EOF Then + firstName = rs("First_Name") & "" + lastName = rs("Last_Name") & "" + If Len(names) > 0 Then names = names & ", " + names = names & firstName & " " & lastName + End If + + If Not rs Is Nothing Then + If rs.State = 1 Then rs.Close + Set rs = Nothing + End If + Set cmd = Nothing + On Error GoTo 0 + End If +Next + +If Len(names) > 0 Then + jsonOutput = "{""success"":true,""names"":""" & JSEscape(names) & """}" +Else + jsonOutput = "{""success"":false,""error"":""No employees found""}" +End If + +Response.Write jsonOutput +objconn.Close + +Function JSEscape(s) + Dim r + r = s + r = Replace(r, "\", "\\") + r = Replace(r, """", "\""") + r = Replace(r, Chr(13), "") + r = Replace(r, Chr(10), "\n") + r = Replace(r, Chr(9), "\t") + JSEscape = r +End Function +%> diff --git a/savenotificationdirect.asp b/savenotificationdirect.asp index 84c7514..4b731f1 100644 --- a/savenotificationdirect.asp +++ b/savenotificationdirect.asp @@ -9,8 +9,10 @@ <% +On Error Resume Next + ' Get form inputs -Dim notification, ticketnumber, starttime, endtime, isactive, isshopfloor, notificationtypeid, businessunitid, appid +Dim notification, ticketnumber, starttime, endtime, isactive, isshopfloor, notificationtypeid, businessunitid, appid, employeesso notification = Trim(Request.Form("notification")) ticketnumber = Trim(Request.Form("ticketnumber")) starttime = Trim(Request.Form("starttime")) @@ -18,6 +20,10 @@ endtime = Trim(Request.Form("endtime")) notificationtypeid = Trim(Request.Form("notificationtypeid")) businessunitid = Trim(Request.Form("businessunitid")) appid = Trim(Request.Form("appid")) +employeesso = Trim(Request.Form("employeesso")) + +' Recognition type ID +Const RECOGNITION_TYPE_ID = 5 ' Checkboxes - ensure they are always integers 0 or 1 If Request.Form("isactive") = "1" Then @@ -37,10 +43,55 @@ If notificationtypeid = "" Or Not IsNumeric(notificationtypeid) Then notificationtypeid = "1" End If -' Validate required fields (endtime is now optional) -If Len(notification) = 0 Or Len(starttime) = 0 Then +' Check for errors so far +If Err.Number <> 0 Then objConn.Close - ShowError "Required fields missing.", "addnotification.asp" + ShowError "Error during initialization: " & Err.Description, "addnotification.asp" + Response.End +End If + +' Handle Recognition type - auto-set times and require employeesso +Dim isRecognition +isRecognition = (CLng(notificationtypeid) = RECOGNITION_TYPE_ID) + +If isRecognition Then + ' Validate employeesso is provided for Recognition + If Len(employeesso) = 0 Then + objConn.Close + ShowError "Employee SSO is required for Recognition notifications.", "addnotification.asp" + Response.End + End If + + ' Auto-set starttime to NOW + starttime = Year(Now) & "-" & Right("0" & Month(Now), 2) & "-" & Right("0" & Day(Now), 2) & " " & _ + Right("0" & Hour(Now), 2) & ":" & Right("0" & Minute(Now), 2) & ":00" + + ' Auto-set endtime to 4AM next day + Dim nextDay + nextDay = DateAdd("d", 1, Date) + endtime = Year(nextDay) & "-" & Right("0" & Month(nextDay), 2) & "-" & Right("0" & Day(nextDay), 2) & " 04:00:00" + + ' Auto-enable shopfloor display for Recognition + isshopfloor = 1 +End If + +' Check for errors after Recognition handling +If Err.Number <> 0 Then + objConn.Close + ShowError "Error during Recognition setup: " & Err.Description, "addnotification.asp" + Response.End +End If + +' Validate required fields (endtime is now optional, starttime not required for Recognition) +If Len(notification) = 0 Then + objConn.Close + ShowError "Notification message is required.", "addnotification.asp" + Response.End +End If + +If Not isRecognition And Len(starttime) = 0 Then + objConn.Close + ShowError "Start time is required.", "addnotification.asp" Response.End End If @@ -50,8 +101,10 @@ If Len(notification) > 500 Or Len(ticketnumber) > 50 Then Response.End End If -' Convert datetime format for starttime -starttime = Replace(starttime, "T", " ") & ":00" +' Convert datetime format for starttime (skip if already formatted for Recognition) +If InStr(starttime, "T") > 0 Then + starttime = Replace(starttime, "T", " ") & ":00" +End If ' Handle optional endtime - leave as NULL if blank (indefinite) Dim endtimeValue, businessunitValue @@ -59,8 +112,10 @@ If Len(endtime) = 0 Then ' No end date - store as NULL for indefinite notifications endtimeValue = Null Else - ' End date specified - convert format - endtime = Replace(endtime, "T", " ") & ":00" + ' End date specified - convert format (only add :00 if from datetime-local input with T) + If InStr(endtime, "T") > 0 Then + endtime = Replace(endtime, "T", " ") & ":00" + End If endtimeValue = endtime End If @@ -79,11 +134,25 @@ Else appidValue = CLng(appid) End If +' Handle optional employeesso - only for Recognition type +Dim employeessoValue +If Len(employeesso) = 0 Then + employeessoValue = Null +Else + employeessoValue = employeesso +End If + ' INSERT using parameterized query +On Error Resume Next Dim strSQL, cmdInsert -strSQL = "INSERT INTO notifications (notificationtypeid, businessunitid, appid, notification, ticketnumber, starttime, endtime, isactive, isshopfloor) " & _ - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" +strSQL = "INSERT INTO notifications (notificationtypeid, businessunitid, appid, notification, ticketnumber, starttime, endtime, isactive, isshopfloor, employeesso) " & _ + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" Set cmdInsert = Server.CreateObject("ADODB.Command") +If Err.Number <> 0 Then + objConn.Close + ShowError "Error creating command: " & Err.Description, "addnotification.asp" + Response.End +End If cmdInsert.ActiveConnection = objConn cmdInsert.CommandText = strSQL cmdInsert.CommandType = 1 @@ -108,8 +177,19 @@ Else End If cmdInsert.Parameters.Append cmdInsert.CreateParameter("@isactive", 11, 1, , CBool(isactive)) cmdInsert.Parameters.Append cmdInsert.CreateParameter("@isshopfloor", 11, 1, , CBool(isshopfloor)) +If IsNull(employeessoValue) Then + cmdInsert.Parameters.Append cmdInsert.CreateParameter("@employeesso", 200, 1, 100, Null) +Else + cmdInsert.Parameters.Append cmdInsert.CreateParameter("@employeesso", 200, 1, 100, employeessoValue) +End If + +' Check for parameter errors +If Err.Number <> 0 Then + objConn.Close + ShowError "Error building parameters: " & Err.Description, "addnotification.asp" + Response.End +End If -On Error Resume Next cmdInsert.Execute If Err.Number = 0 Then diff --git a/shopfloor-dashboard/index.html b/shopfloor-dashboard/index.html index 0a2edba..180311b 100644 --- a/shopfloor-dashboard/index.html +++ b/shopfloor-dashboard/index.html @@ -3,6 +3,9 @@ + + + West Jefferson - Events & Notifications @@ -391,6 +394,122 @@ font-style: italic; } + /* Recognition carousel styles */ + .recognition-section { + margin-bottom: 20px; + } + + .section-title.recognition { + background: #0d6efd; + color: #fff; + box-shadow: 0 4px 20px rgba(13, 110, 253, 0.4); + } + + .recognition-carousel-container { + position: relative; + overflow: hidden; + width: 100%; + } + + .recognition-carousel-wrapper { + position: relative; + width: 100%; + min-height: 150px; + } + + .recognition-card { + background: linear-gradient(135deg, #1e3a5f 0%, #0d2137 100%); + border: 3px solid #0d6efd; + border-radius: 12px; + padding: 25px 30px; + box-shadow: 0 4px 20px rgba(13, 110, 253, 0.3); + transition: transform 0.8s ease-in-out, opacity 0.8s ease-in-out; + width: 100%; + } + + .recognition-card:not(.active) { + position: absolute; + top: 0; + left: 0; + } + + .recognition-card.active { + transform: translateY(0); + opacity: 1; + position: relative; + } + + .recognition-card.exit-up { + transform: translateY(-100%); + opacity: 0; + } + + .recognition-card.enter-down { + transform: translateY(100%); + opacity: 0; + } + + .recognition-header { + display: flex; + align-items: center; + margin-bottom: 15px; + } + + .recognition-star { + font-size: 48px; + color: #ffc107; + margin-right: 20px; + text-shadow: 0 0 10px rgba(255, 193, 7, 0.5); + animation: starPulse 2s ease-in-out infinite; + } + + @keyframes starPulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } + } + + .recognition-employee { + font-size: 32px; + font-weight: 700; + color: #fff; + } + + .recognition-achievement { + font-size: 24px; + color: #ccc; + line-height: 1.4; + margin-left: 68px; + } + + .recognition-counter { + position: absolute; + top: 15px; + right: 20px; + background: rgba(0, 0, 0, 0.5); + padding: 5px 12px; + border-radius: 15px; + font-size: 14px; + color: #fff; + } + + .recognition-dots { + text-align: center; + margin-top: 15px; + } + + .recognition-dots .dot { + display: inline-block; + width: 10px; + height: 10px; + background: #444; + border-radius: 50%; + margin: 0 5px; + } + + .recognition-dots .dot.active { + background: #0d6efd; + } + /* Upcoming events carousel */ .upcoming-carousel-container { position: relative; @@ -553,6 +672,33 @@ font-size: 14px; padding: 10px; } + + .recognition-card { + padding: 15px 20px; + } + + .recognition-star { + font-size: 28px; + margin-right: 12px; + } + + .recognition-employee { + font-size: 18px; + } + + .recognition-achievement { + font-size: 14px; + margin-left: 40px; + } + + .recognition-counter { + font-size: 10px; + padding: 3px 8px; + } + + .recognition-carousel-wrapper { + min-height: 100px; + } } /* ============================================ */ @@ -670,6 +816,39 @@ font-size: 48px; padding: 30px; } + + .recognition-card { + padding: 50px 60px; + } + + .recognition-star { + font-size: 96px; + margin-right: 40px; + } + + .recognition-employee { + font-size: 64px; + } + + .recognition-achievement { + font-size: 48px; + margin-left: 136px; + } + + .recognition-counter { + font-size: 28px; + padding: 10px 24px; + } + + .recognition-carousel-wrapper { + min-height: 300px; + } + + .recognition-dots .dot { + width: 20px; + height: 20px; + margin: 0 10px; + } } @@ -719,6 +898,12 @@ let isTransitioning = false; let pendingDataRender = null; + // Recognition carousel state + let recognitionEvents = []; + let currentRecognitionIndex = 0; + let recognitionCarouselInterval = null; + let isRecognitionTransitioning = false; + // Get business unit from URL parameter function getBusinessUnitFromURL() { const urlParams = new URLSearchParams(window.location.search); @@ -816,18 +1001,23 @@ const container = document.getElementById('eventsContainer'); let html = ''; - // Current Events - show all in grid layout - if (data.current && data.current.length > 0) { + // Separate Recognition events from other current events + const recognitions = data.current ? data.current.filter(e => e.typecolor === 'recognition') : []; + const otherCurrentEvents = data.current ? data.current.filter(e => e.typecolor !== 'recognition') : []; + + // Current Events - show all in grid layout (excluding Recognition) - FIRST priority + if (otherCurrentEvents.length > 0) { // Split into active and resolved events - const activeEvents = data.current.filter(e => !e.resolved); - const resolvedEvents = data.current.filter(e => e.resolved); + const activeEvents = otherCurrentEvents.filter(e => !e.resolved); + const resolvedEvents = otherCurrentEvents.filter(e => e.resolved); // Sort function: by type priority (Incident > Change > Awareness > TBD), then by date (earliest first) const typePriority = { 'Incident': 1, 'Change': 2, 'Awareness': 3, - 'TBD': 4 + 'Recognition': 4, + 'TBD': 5 }; const sortByTypeAndDate = (a, b) => { @@ -868,6 +1058,7 @@ ? `${escapeHtml(event.ticketnumber)}` : ''; const resolvedIndicator = event.resolved ? '
RESOLVED
' : ''; + const endTimeText = event.resolved ? `
Resolved: ${formatDateTime(event.endtime)}` : event.endtime ? `
Ends: ${formatDateTime(event.endtime)}` : '
Status: Ongoing (no end time)'; @@ -887,11 +1078,9 @@
${ticketBadge}
${escapeHtml(event.notification)}
-
- Started: ${formatDateTime(event.starttime)} - ${endTimeText} + Started: ${formatDateTime(event.starttime)}${endTimeText}
`; @@ -901,6 +1090,54 @@ html += '
'; // Close section } + // Recognition Carousel Section - AFTER current events, BEFORE upcoming + if (recognitions.length > 0) { + recognitionEvents = recognitions; + + // Preserve current index if valid + if (currentRecognitionIndex >= recognitions.length) { + currentRecognitionIndex = 0; + } + + html += '
'; + html += '
EMPLOYEE RECOGNITION
'; + html += ''; // Close carousel container + html += '
'; // Close section + } else { + recognitionEvents = []; + } + // Upcoming Events - carousel with slide-up transition if (data.upcoming && data.upcoming.length > 0) { // Sort upcoming events by type priority, then by date @@ -908,7 +1145,8 @@ 'Incident': 1, 'Change': 2, 'Awareness': 3, - 'TBD': 4 + 'Recognition': 4, + 'TBD': 5 }; const sortedUpcoming = [...data.upcoming].sort((a, b) => { @@ -975,12 +1213,19 @@ container.innerHTML = html; - // Start carousel if there are upcoming events + // Start carousels if there are events if (upcomingEvents.length > 1) { startUpcomingCarousel(); } else { stopUpcomingCarousel(); } + + // Start recognition carousel if there are multiple recognitions + if (recognitionEvents.length > 1) { + startRecognitionCarousel(); + } else { + stopRecognitionCarousel(); + } } // Start the upcoming events carousel @@ -1059,6 +1304,78 @@ currentUpcomingIndex = nextIndex; } + // Start the recognition carousel + function startRecognitionCarousel() { + console.log(`Recognition Carousel: Starting with ${recognitionEvents.length} recognitions`); + + // Clear any existing interval + stopRecognitionCarousel(); + + // Start new interval (5 seconds) + recognitionCarouselInterval = setInterval(rotateRecognitionEvent, 5000); + } + + // Stop the recognition carousel + function stopRecognitionCarousel() { + if (recognitionCarouselInterval) { + clearInterval(recognitionCarouselInterval); + recognitionCarouselInterval = null; + } + } + + // Rotate to next recognition + function rotateRecognitionEvent() { + if (recognitionEvents.length <= 1) { + return; + } + + const carousel = document.getElementById('recognitionCarousel'); + if (!carousel) { + return; + } + + const items = carousel.querySelectorAll('.recognition-card'); + if (items.length === 0) { + return; + } + + // Mark transition as active + isRecognitionTransitioning = true; + + const currentItem = items[currentRecognitionIndex]; + const nextIndex = (currentRecognitionIndex + 1) % recognitionEvents.length; + const nextItem = items[nextIndex]; + + // Slide current item up and out + currentItem.classList.remove('active'); + currentItem.classList.add('exit-up'); + + // Slide next item up from bottom + nextItem.classList.remove('enter-down'); + nextItem.classList.add('active'); + + // Update counter if present + const counter = nextItem.querySelector('.recognition-counter'); + if (counter) { + counter.textContent = `${nextIndex + 1} of ${recognitionEvents.length}`; + } + + // Update dots + const dots = document.querySelectorAll('.recognition-dots .dot'); + dots.forEach((dot, i) => { + dot.classList.toggle('active', i === nextIndex); + }); + + // After transition, reset the exited item for next rotation + setTimeout(() => { + currentItem.classList.remove('exit-up'); + currentItem.classList.add('enter-down'); + isRecognitionTransitioning = false; + }, 800); + + currentRecognitionIndex = nextIndex; + } + // Escape HTML to prevent XSS function escapeHtml(text) { const div = document.createElement('div'); @@ -1079,6 +1396,7 @@ 'success': '#0ad64f', // Green (GE Avionics Green) - Awareness, TBD 'warning': '#ffc107', // Yellow - Change 'danger': '#dc3545', // Red - Incident + 'recognition': '#0d6efd', // Blue - Recognition 'secondary': '#6c757d' // Gray - fallback }; return colorMap[typecolor] || colorMap['secondary']; @@ -1195,7 +1513,7 @@ } // Initialize dashboard - function init() { + async function init() { // Display fiscal week updateFiscalWeek(); @@ -1203,8 +1521,11 @@ updateClock(); clockInterval = setInterval(updateClock, 1000); + // Set business unit from URL BEFORE first fetch + selectedBusinessUnit = getBusinessUnitFromURL(); + // Load business units dropdown (only once) - loadBusinessUnits(); + await loadBusinessUnits(); // Initial data fetch fetchNotifications(); @@ -1219,6 +1540,7 @@ clearInterval(updateInterval); clearInterval(clockInterval); stopUpcomingCarousel(); + stopRecognitionCarousel(); } else { // Resume updates without reinitializing everything updateClock(); diff --git a/sql/add_recognition_feature.sql b/sql/add_recognition_feature.sql new file mode 100644 index 0000000..51505e6 --- /dev/null +++ b/sql/add_recognition_feature.sql @@ -0,0 +1,16 @@ +-- ============================================================================ +-- Recognition Feature Migration +-- Add employee recognition support to notifications +-- ============================================================================ + +-- 1. Add employeesso column to notifications table +-- VARCHAR(100) to support multiple comma-separated 9-digit SSOs +ALTER TABLE notifications ADD COLUMN employeesso VARCHAR(100) NULL; + +-- 2. Add Recognition notification type +INSERT INTO notificationtypes (typename, typedescription, typecolor, isactive) +VALUES ('Recognition', 'Employee Achievement Recognition', 'recognition', 1); + +-- Verify the changes +SELECT * FROM notificationtypes WHERE typename = 'Recognition'; +DESCRIBE notifications; diff --git a/updatenotificationdirect.asp b/updatenotificationdirect.asp index b8fa59d..d8793cc 100644 --- a/updatenotificationdirect.asp +++ b/updatenotificationdirect.asp @@ -10,7 +10,7 @@ <% ' Get form inputs -Dim notificationid, notification, ticketnumber, starttime, endtime, isactive, isshopfloor, notificationtypeid, businessunitid, appid +Dim notificationid, notification, ticketnumber, starttime, endtime, isactive, isshopfloor, notificationtypeid, businessunitid, appid, employeesso notificationid = Trim(Request.Form("notificationid")) notification = Trim(Request.Form("notification")) ticketnumber = Trim(Request.Form("ticketnumber")) @@ -19,6 +19,10 @@ endtime = Trim(Request.Form("endtime")) notificationtypeid = Trim(Request.Form("notificationtypeid")) businessunitid = Trim(Request.Form("businessunitid")) appid = Trim(Request.Form("appid")) +employeesso = Trim(Request.Form("employeesso")) + +' Recognition type ID +Const RECOGNITION_TYPE_ID = 5 ' Handle checkbox - if the hidden field is submitted but checkbox isn't, it means unchecked If Request.Form("isactive_submitted") = "1" Then @@ -64,10 +68,41 @@ If notificationtypeid = "" Or Not IsNumeric(notificationtypeid) Then notificationtypeid = "1" End If -' Validate required fields (endtime is now optional) -If Len(notification) = 0 Or Len(starttime) = 0 Then +' Handle Recognition type - auto-set times and require employeesso +Dim isRecognition +isRecognition = (CLng(notificationtypeid) = RECOGNITION_TYPE_ID) + +If isRecognition Then + ' Validate employeesso is provided for Recognition + If Len(employeesso) = 0 Then + objConn.Close + ShowError "Employee SSO is required for Recognition notifications.", "editnotification.asp?notificationid=" & notificationid + Response.End + End If + + ' Auto-set starttime to NOW + starttime = Year(Now) & "-" & Right("0" & Month(Now), 2) & "-" & Right("0" & Day(Now), 2) & " " & _ + Right("0" & Hour(Now), 2) & ":" & Right("0" & Minute(Now), 2) & ":00" + + ' Auto-set endtime to 4AM next day + Dim nextDay + nextDay = DateAdd("d", 1, Date) + endtime = Year(nextDay) & "-" & Right("0" & Month(nextDay), 2) & "-" & Right("0" & Day(nextDay), 2) & " 04:00:00" + + ' Auto-enable shopfloor display for Recognition + isshopfloor = 1 +End If + +' Validate required fields (endtime is now optional, starttime not required for Recognition) +If Len(notification) = 0 Then objConn.Close - ShowError "Required fields missing.", "editnotification.asp?notificationid=" & notificationid + ShowError "Notification message is required.", "editnotification.asp?notificationid=" & notificationid + Response.End +End If + +If Not isRecognition And Len(starttime) = 0 Then + objConn.Close + ShowError "Start time is required.", "editnotification.asp?notificationid=" & notificationid Response.End End If @@ -77,8 +112,10 @@ If Len(notification) > 500 Or Len(ticketnumber) > 50 Then Response.End End If -' Convert datetime format for starttime -starttime = Replace(starttime, "T", " ") & ":00" +' Convert datetime format for starttime (skip if already formatted for Recognition) +If InStr(starttime, "T") > 0 Then + starttime = Replace(starttime, "T", " ") & ":00" +End If ' Handle optional endtime - leave as NULL if blank (indefinite) Dim endtimeValue, businessunitValue @@ -86,8 +123,10 @@ If Len(endtime) = 0 Then ' No end date - store as NULL for indefinite notifications endtimeValue = Null Else - ' End date specified - convert format - endtime = Replace(endtime, "T", " ") & ":00" + ' End date specified - convert format (only add :00 if from datetime-local input with T) + If InStr(endtime, "T") > 0 Then + endtime = Replace(endtime, "T", " ") & ":00" + End If endtimeValue = endtime End If @@ -106,9 +145,17 @@ Else appidValue = CLng(appid) End If +' Handle optional employeesso - only for Recognition type +Dim employeessoValue +If Len(employeesso) = 0 Then + employeessoValue = Null +Else + employeessoValue = employeesso +End If + ' UPDATE using parameterized query Dim strSQL, cmdUpdate -strSQL = "UPDATE notifications SET notificationtypeid = ?, businessunitid = ?, appid = ?, notification = ?, ticketnumber = ?, starttime = ?, endtime = ?, isactive = ?, isshopfloor = ? WHERE notificationid = ?" +strSQL = "UPDATE notifications SET notificationtypeid = ?, businessunitid = ?, appid = ?, notification = ?, ticketnumber = ?, starttime = ?, endtime = ?, isactive = ?, isshopfloor = ?, employeesso = ? WHERE notificationid = ?" Set cmdUpdate = Server.CreateObject("ADODB.Command") cmdUpdate.ActiveConnection = objConn cmdUpdate.CommandText = strSQL @@ -134,6 +181,11 @@ Else End If cmdUpdate.Parameters.Append cmdUpdate.CreateParameter("@isactive", 11, 1, , CBool(isactive)) cmdUpdate.Parameters.Append cmdUpdate.CreateParameter("@isshopfloor", 11, 1, , CBool(isshopfloor)) +If IsNull(employeessoValue) Then + cmdUpdate.Parameters.Append cmdUpdate.CreateParameter("@employeesso", 200, 1, 100, Null) +Else + cmdUpdate.Parameters.Append cmdUpdate.CreateParameter("@employeesso", 200, 1, 100, employeessoValue) +End If cmdUpdate.Parameters.Append cmdUpdate.CreateParameter("@notificationid", 3, 1, , CLng(notificationid)) On Error Resume Next