In Time Sheet Portal I want task.assigned_to field editable

shrutijibhekar
Tera Contributor

Hello

There is requirement in time sheet portal to add project's assigned to field, 

For that we have added field in form,

As well as in portal widget,

I have edited time card grid widget, it is editable but when i am selecting any name it is not saving the name and it is immediately setting up as none.

 

Does any one have any idea how to implement this

shrutijibhekar_3-1768397126299.png

 

shrutijibhekar_2-1768397032612.png

 

shrutijibhekar_0-1768396862793.png

 

2 REPLIES 2

harishk07
Tera Expert

Hi,

 

This is caused by the Time Sheet grid’s client script handling editable fields differently based on their type. When you add assigned_to as an editable field, it is treated as a numeric field instead of a reference field.

In the widget’s client script (saveRecord()), only a few fields (rate_type, project_time_category, etc.) are handled as reference fields and their sys_id is sent to the server. All other editable fields are assumed to be numeric day fields, and their display_value is validated as a number. Since the display value of assigned_to is a user name, it fails numeric validation and the script sends 0 by default to the server, which results in the field being cleared after refresh.

You need to handle assigned_to the same way as the other selector fields by sending its sys_id instead of the display value. Update the saveRecord() logic to include assigned_to in the same condition that uses record[field].value.

 

Client script change in saveRecord():

$scope.saveRecord = function(evt, record, promiseResolver){
  _preventDefault(evt);

  var data = {};
  var editableFields = $scope.data.editable_fields;

  for (var i in editableFields){
    var field = editableFields[i];

    // Treat these as selector/reference fields and send sys_id
    if (field === "rate_type" || field === "project_time_category" ||
        field === "resource_plan" || field === "resource_assignment" ||
        field === "assigned_to") {

      data[field] = record[field] && record[field].value ? record[field].value : "";
      continue;
    }

    // Numeric fields (monday..sunday, etc.)
    var val = $sanitize(record[field].display_value);
    data[field] = isValidNumber(val) ? val : 0;
  }

  TimeCardPortalService.updateTimeCard(record.sys_id, data, editableFields.join(',') + ',total');
};

 

Also ensure the field is included in data.fields and data.fields_array in the server script, and that TimeCardPortalService.updateTimeCard() allows updating this field.

 

var defaultHeaderFields = ['task.short_description', 'project_time_category'];
data.fields = defaultHeaderFields;

if(data.isAllowMultipleRateTypes)
  data.fields.push('rate_type');

// Add Assigned to column (reference field on time_card)
if (data.fields.indexOf('assigned_to') === -1)
  data.fields.push('assigned_to');
// Add assigned_to in the data.editable_fields array
data.editable_fields = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'project_time_category', 'assigned_to'];

After this change, the selected user will persist correctly in the grid instead of clearing out.

Best Regards,
Harish

✔️ Kindly mark as Helpful / Accept Solution if this answered your question

Hello @harishk07 
I tried your solution but it still not setting values, and field is assigned to (task.assigned_to).
Please check below script.  I have added client script , server script and html script.

This is Client controller script:

function($scope, TimeCardPortalService, spUtil, $sanitize, $rootScope, $window, $timeout, spModal, $q, TooltipFactory) {
 
var c = this;
 
    // var $timeout = $injector.get('$timeout');
_highlightField();
var skipNextRecordWatchUpdate = false;
$scope.pageSize = $scope.data.pageSize;
var minWidthFields = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "total"];
$scope.getWidth = function(field) {
if(field && $scope.data.column_meta[field] 
&& $scope.data.column_meta[field]['width_in_percent']) {
return {
'min-width': $scope.data.column_meta[field]['width_in_percent'] + '%'
};
}
 
// Days and Total will have Minimum width of 5%.
// Smaller the resolution, smaller the scroll. 
if (field && $scope.data.column_meta[field] && (minWidthFields.indexOf(field) > -1)) {
return {
'min-width': '5%'
}
}
 
// Default width of 10% which has not configured width_in_percent.
return {
'min-width': '10%'
}
}
 
function updatePagination(from, to) {
$scope.total = c.data.totalRows;
$scope.current = {
to: to > c.data.totalRows? c.data.totalRows: to,
from: from,
pageSize: $scope.pageSize
};
$scope.paginatedList = c.data.list.slice(from-1, to);
}
 
updatePagination(1, $scope.pageSize);
 
$scope.$on('sp_load_new_page', function(event, page) {
$scope.paginatedList = c.data.list.slice(page.from-1, page.to);
});
 
$scope.cancelEdit = function(evt, index){
if(_isEmptyObject($scope.data.editTimecardObj))
return;
_preventDefault(evt);
$scope.data.list[index] = $scope.data.editTimecardObj;
$scope.data.editTimecardObj = {};
$scope.data.timecardInEditMode = '';
$scope.data.highlightedField = $scope.data.previousHighlightedField || $scope.data.defaultField;
$scope.highlightDuplicates();
};
 
 
$scope.saveRecord = function(evt, record, promiseResolver){
_preventDefault(evt);
if($scope.data.recordSetForDelete == record.sys_id) return;
var data = {};
var editabelFields = $scope.data.editable_fields;
for (var i in editabelFields){
var field = editabelFields[i];
 
if(field === "rate_type" || field === "project_time_category" || field === "resource_plan" || field === "resource_assignment" || field === "task.assigned_to") {
// we don't need sanitization here as this is not directly edited by user
// this value is only selected from pre existing values and typed by user
data[field] = record[field].value;
continue;
}
var val = $sanitize(record[field].display_value);
if(!isValidNumber(val))
data[field] = 0;
else
data[field] = val;
 
}$scope.saveRecord = function(evt, record, promiseResolver){
  _preventDefault(evt);
 
  var data = {};
  var editableFields = $scope.data.editable_fields;
 
  for (var i in editableFields){
    var field = editableFields[i];
 
    // Treat these as selector/reference fields and send sys_id
    if (field === "rate_type" || field === "project_time_category" ||
        field === "resource_plan" || field === "resource_assignment" ||
        field === "task.assigned_to") {
 
      data[field] = record[field] && record[field].value ? record[field].value : "";
      continue;
    }
 
    // Numeric fields (monday..sunday, etc.)
    var val = $sanitize(record[field].display_value);
    data[field] = isValidNumber(val) ? val : 0;
  }
 
  TimeCardPortalService.updateTimeCard(record.sys_id, data, editableFields.join(',') + ',total');
};
 
$scope.data.timecardInEditMode = '';
$scope.data.editTimecardObj = {};
TimeCardPortalService.updateTimeCard(record.sys_id, data, editabelFields.join(',')+',total').then(function(response){
var result = response.data.result;
for(var field in result){
record[field].display_value = result[field].display_value;
record[field].value = result[field].value;
if(isValidNumber(result[field].value))
record[field].invalid = false;
$scope.data.highlightedField = $scope.data.previousHighlightedField;
}
var status = response.data.status;
if(status == 'error')
refreshWidget();
else {
skipNextRecordWatchUpdate = true;
$scope.highlightDuplicates();
}
if(promiseResolver) {
promiseResolver();
}
});
};
 
var $timeout = $injector.get('$timeout');
$scope.deleteRecord = function(evt){
var _target = $(evt.target);
var _id = _target.closest('.flex-item').data('id')  || evt.target.id;
var record = $scope.data.list.filter(function(listItem) {
return (listItem.sys_id == _id);
})[0];
var confirmModalCtrl;
 
var index = $(".container .table-body .flex.tc-row").index(_target.closest('.flex.tc-row'));
$scope.data['tcPopover' + record.sys_id] = false;
var numRows = _target.closest('.table-body').children().length;
var currRow = _target.closest('.table-body').find('.tc-row').get(index);
var nextRow;
if(numRows > 1){
if(index == numRows-1)
nextRow = _target.closest('.table-body').find('.tc-row').get(index-1);
else
nextRow = _target.closest('.table-body').find('.tc-row').get(index+1);
}
 
$scope.data.confirmDialogOpts = {
title: $scope.data.messages.confirmDlgTitle,
text: $scope.data.messages.confirmMssg,
okTxt: $scope.data.messages['delete'],
cancelTxt: $scope.data.messages.cancel,
okBtnClass: 'btn-danger',
ok: function(){
$scope.data.recordSetForDelete = record.sys_id;
TimeCardPortalService.deleteTimeCard(record.sys_id).then(function(response){
$scope.data.recordSetForDelete = '';
$scope.data.list.splice(index, 1);
skipNextRecordWatchUpdate = true;
$scope.highlightDuplicates();
c.data.totalRows -= 1;
updatePagination($scope.current.from, $scope.current.to);
$rootScope.$broadcast("record-delete");
});
confirmModalCtrl.close();
},
afterOpen: function(ctrl){
confirmModalCtrl = ctrl;
},
afterClose : function(){
$scope.showConfirm = false;
confirmModalCtrl = null;
$scope.data.confirmDialogOpts = null;
if(currRow && $('#' + _id).length)
currRow.focus();
else if(numRows==1 && $('#generate-timecard'))
$('#generate-timecard').focus();
else if(nextRow)
nextRow.focus();
}
};
$scope.showConfirm = true;
$scope.safeDigest();
 
_preventDefault(evt);
};
 
function updateRecordState(evt, newState) {
var _target = $(evt.target);
var _id = _target.closest('.flex-item').data('id') || evt.target.id;
var record = $scope.data.list.filter(function(listItem) {
return (listItem.sys_id == _id);
})[0];
TimeCardPortalService.updateTimeCardState(record.sys_id, newState).then(function(response) {
var status = response.data.status;
var message = response.data.data.message;
if (status != 'success')
spUtil.addErrorMessage(message);
else if(newState == 'Submitted' && parseInt(record.total.value) > 0)
spUtil.addInfoMessage(message);
refreshWidget();
})
}
 
$scope.updateRecordState = function(evt, newState){
$scope.hideTcPopover();
if($scope.data.timecardInEditMode) {
cancelGridEditModePromiseWrapper(event, true).then(function () {
updateRecordState(evt, newState);
})
} else {
updateRecordState(evt, newState);
}
};
 
$scope.recallRecord = function(evt){
var _target = $(evt.target);
var _id = _target.closest('.flex-item').data('id')  || evt.target.id;
var record = $scope.data.list.filter(function(listItem) {
return (listItem.sys_id == _id);
})[0];
var confirmModalCtrl;
 
var index = $(".container .table-body .flex.tc-row").index(_target.closest('.flex.tc-row'));
$scope.data['tcPopover' + record.sys_id] = false;
 
$scope.data.confirmDialogOpts = {
title: $scope.data.messages.recallDlgTitle,
text: $scope.data.messages.recallConfirmMssg,
okTxt: $scope.data.messages.recall,
cancelTxt: $scope.data.messages.cancel,
okBtnClass: 'btn-danger',
ok: function(){
TimeCardPortalService.updateTimeCardState(record.sys_id, 'Recalled').then(function(response) {
var status = response.data.status;
var message = response.data.data.message;
if (status != 'success')
spUtil.addErrorMessage(message);
else
spUtil.addInfoMessage(message);
})
confirmModalCtrl.close();
},
afterOpen: function(ctrl){
confirmModalCtrl = ctrl;
},
afterClose : function(){
$scope.showConfirm = false;
confirmModalCtrl = null;
$scope.data.confirmDialogOpts = null;
}
};
$scope.showConfirm = true;
$scope.safeDigest();
_preventDefault(evt);
};
 
$scope.generateTimecards = function(){
TimeCardPortalService.generateTimecards({
timesheet_id: $scope.data.timesheetId,
tables: $scope.data.tables
}).then(function(response){
spUtil.addInfoMessage(response.data.message);
});
};
 
$scope.copyFromPreviousTimesheet = function(){
$rootScope.$broadcast('tcp.copy.timesheet');
};
 
$scope.filterNumericInput = function(item) {
        if (!item.display_value) {
            item.display_value = '0';
            return;
        }
        
        var decimalSeparator = g_user_decimal_separator;
        var inputValue = item.display_value.trim();
        
        switch(inputValue) {
            case '-':
            case '0-':
                item.display_value = '-';
                return;
            case '00':
                item.display_value = '0';
                return;
            case '-00':
                item.display_value = '-0';
                return;
        }
        
        if (/^0[1-9]/.test(inputValue)) {
            item.display_value = inputValue.substring(1);
            return;
        }
        
        if (/^-0[1-9]/.test(inputValue)) {
            item.display_value = '-' + inputValue.substring(2);
            return;
        }
        
        var hasNegativeSign = inputValue.charAt(0) === '-';
        var numericValue = inputValue.replace(new RegExp('[^0-9' + decimalSeparator + '-]', 'g'), '');
        
        if (numericValue) {
            var parts = numericValue.split(decimalSeparator);
            if (parts.length > 2) {
                numericValue = parts[0] + decimalSeparator + parts.slice(1).join('');
            }
            
            numericValue = numericValue.replace(/-/g, '');
            if (hasNegativeSign) {
                numericValue = '-' + numericValue;
            }
        }
        
        if (numericValue === '-') {
            item.display_value = '-';
        } else {
            item.display_value = numericValue || '0';
        }
    };
 
$scope.focusOutEndDecimalCheck = function(item) {
        if (!item.display_value) {
            item.display_value = '0';
            return;
        }
        
        var decimalSeparator = g_user_decimal_separator;
        var inputValue = item.display_value.trim();
        
        switch(inputValue) {
            case '-':
            case '-0':
                item.display_value = '-0';
                return;
            case decimalSeparator:
                item.display_value = '0';
                return;
        }
        
        var hasNegativeSign = inputValue.charAt(0) === '-';
        var numericValue = inputValue.replace(new RegExp('[^0-9' + decimalSeparator + '-]', 'g'), '');
        
        numericValue = numericValue.replace(/-/g, '');
        if (hasNegativeSign) {
            numericValue = '-' + numericValue;
        }
        
        if (numericValue.endsWith(decimalSeparator)) {
            numericValue = numericValue.slice(0, -1);
        }
        
        if (numericValue === '-') {
            numericValue = '-0';
        }
        
        item.display_value = numericValue || '0';
    };
 
function isValidNumber(number){
if (number === '-') {
return true;
}
 
var decimalSeperator = g_user_decimal_separator;
var regx =  new RegExp('^-?((\\d+(\\'+ decimalSeperator + '\\d+)?)|(\\' + decimalSeperator + '\\d+))$');
if(regx.test(number) && !isNaN(parseFloat(number))){
return true;
}
return false;
}
function refreshWidget(start, end){
/* Carry forward options for grid customizaions */
start = start ? start:1;
end = end ? end: $scope.pageSize
$scope.data.options = $scope.options.tm_grid_options;
spUtil.update($scope).then(function(){
var timecardToEdit = $scope.data.timecardToEdit;
if(timecardToEdit){
$scope.data.timecardToEdit = '';
$scope.showTimecardInEditMode(timecardToEdit);
}
updatePagination(start, end);
$scope.highlightDuplicates();
$scope.addSyncScrollEventListener();
});
}
 
var filterQuery = 'time_sheet='+ $scope.data.timesheetId;
var recordWatcherTimer;
spUtil.recordWatch($scope, 'time_card', filterQuery, function(){
$timeout.cancel(recordWatcherTimer);
recordWatcherTimer = $timeout(function(){
if (skipNextRecordWatchUpdate) {
skipNextRecordWatchUpdate = false;
$scope.highlightDuplicates();
} else
refreshWidget();
}, 250);
});
 
$scope.mergeDuplicateTimeCards = function(record) {
TimeCardPortalService.mergeDuplicateTc({
timesheet_id: $scope.data.timesheetId,
record : record
}).then(function() {
refreshWidget();
});
};
 
$scope.$on('tcp.refresh.widgets', function(){
skipNextRecordWatchUpdate = true;
refreshWidget();
});
 
$scope.$on('tcp.highlight.field',function(scope, field){
_highlightField(field);
});
 
$scope.$on('tcp.edit.timecard', function(scope, timecardId){
$scope.showTimecardInEditMode(timecardId);
});
 
var foundRecord = null;
 
$scope.$on('pagination.send.window.for.index', function() {
$scope.enableEditMode(foundRecord[0]);
foundRecord = null;
});
 
$scope.$on('ng.repeat.completed.tc-grid', function() {
if(foundRecord) {
$scope.enableEditMode(foundRecord[0]);
foundRecord = null;
}
});
 
function cancelGridEditModePromiseWrapper(event, buttonClicked) {
return $q(function(resolve) {
$scope.cancelGridEditMode(event, buttonClicked, resolve);
});
}
 
function submitTimesheet (timesheetId, oldState) {
TimeCardPortalService.submitTimesheet(timesheetId).then(function(response){
$scope.data.canSubmitTimesheet = response.data.canSubmitTimesheet;
var newState = response.data.data.value;
$scope.data.state = response.data.data;
var mssg = response.data.message;
if(response.data.status == 'error'){
refreshWidget();
if(mssg)
spUtil.addErrorMessage(mssg);
}
else if(oldState == newState){
refreshWidget();
if(mssg)
spUtil.addInfoMessage(mssg);
}
else if(mssg && response.data.status == 'success'){
spUtil.addInfoMessage(mssg);
}
}
);
}
 
function submitTimesheetButtonClicked(event, timesheetId, oldState) {
if($scope.data.timecardInEditMode) {
cancelGridEditModePromiseWrapper(event, false).then(function () {
submitTimesheet(timesheetId, oldState);
})
} else {
submitTimesheet(timesheetId, oldState);
}
}
 
$scope.$on('submitTimesheetButtonClicked', function(event, data) {
submitTimesheetButtonClicked(data.event, data.timesheetId, data.oldState);
});
 
$scope.showTimecardInEditMode = function(timecardId){
var index = null
var record = $scope.data.list.filter(function(record, idx){
if(record.sys_id == timecardId) {
index = index ? index: idx;
return true;
}
return false;
});
 
if(record.length > 0) {
foundRecord = record;
 
// if index is on current page. directly call enableEditmode.
// else figure out on which page it is.
var pageIndex = index + 1;
if (pageIndex <= $scope.current.to && pageIndex >= $scope.current.from) {
$scope.enableEditMode(foundRecord[0]);
} else {
// figure out which page it will be.
var targetPage = parseInt(pageIndex / $scope.current.pageSize);
targetPage += pageIndex % $scope.current.pageSize ? 1 : 0;
$rootScope.$broadcast('pagination.get.window.for.index',  {
targetPage: targetPage
});
}
} else {
$scope.data.timecardToEdit = timecardId;
skipNextRecordWatchUpdate = true;
refreshWidget();
}
};
 
function _highlightField(field){
if(field)
$scope.data.highlightedField = field;
else
$scope.data.highlightedField = $scope.data.defaultField;
}
 
function _preventDefault(evt){
if(evt){
evt.stopPropagation();
evt.preventDefault();
}
}
 
function _isEmptyObject(obj){
return !obj || Object.keys(obj).length === 0;
}
 
$scope.openTimecard = function(ev){
_preventDefault(ev);
var _target = $(ev.target);
var sysId = _target.closest('.flex-item').data('id');
$scope.data['tcPopover' + sysId] = false;
 
spUtil.get('widget-modal', {embeddedWidgetId: 'widget-form', embeddedWidgetOptions: {table:'time_card', sys_id: sysId, view: 'worker_portal'}}).then(function(widget){
var modalCtrl;
var unregister = $scope.$on('sp.form.record.updated', function(){
skipNextRecordWatchUpdate = true;
refreshWidget();
$rootScope.$broadcast("record-add");
modalCtrl.close();
});
widget.options.afterClose = function(){
unregister();
$scope.data.modalInstance = null;
modalCtrl = null;
$rootScope.$broadcast("timesheet_portal_form_closed");
if($('#' + sysId)){
var moreActionBtn = $('#' + sysId).find('.more-action-btn');
if(moreActionBtn)
moreActionBtn.focus();
}
};
widget.options.afterOpen = function(ctrl){
modalCtrl = ctrl;
};
$scope.data.modalInstance = widget;
});
};
 
$scope.keydownOnMoreActionCntr = function(e) {
var menuLinks = document.getElementById("moreActionCntr").querySelectorAll("a");
// Added e.preventDefault to prevent page to scroll
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
scope.moveUpInMenu(menuLinks);
break;
case 'ArrowDown':
e.preventDefault(); 
scope.moveDownInMenu(menuLinks);
break;
case 'Home':
e.preventDefault();
menuLinks && menuLinks.length > 0 ? menuLinks[0].focus() : '';
break;
case 'End':
e.preventDefault();
menuLinks && menuLinks.length > 0 ? menuLinks[menuLinks.length - 1].focus() : '';
break;
}
//reset the attributes on all the tabs
menuLinks.forEach(function(link){
link.setAttribute("tabindex", "-1");
});
//set the attribute on selected tab
document.activeElement.removeAttribute("tabindex");
 
};
 
$scope.moveUpInMenu = function(menuLinks) {
var currentLink = document.activeElement;
if (!currentLink.parentElement.previousElementSibling) {
(menuLinks && menuLinks.length > 0) ? menuLinks[menuLinks.length - 1].focus() : '';
} else {
if(currentLink.parentElement.previousElementSibling.querySelector("a")){
currentLink.parentElement.previousElementSibling.querySelector("a").focus();
}
}
};
 
$scope.moveDownInMenu = function(menuLinks) {
var currentLink = document.activeElement;
if (!currentLink.parentElement.nextElementSibling) {
(menuLinks && menuLinks.length > 0) ? menuLinks[0].focus() : '';
} else {
if(currentLink.parentElement.nextElementSibling.querySelector("a")){
currentLink.parentElement.nextElementSibling.querySelector("a").focus();
}
}
};
 
$scope.highlightDuplicates = function() {
$scope.duplicateList = {};
if($scope.data.list && $scope.data.list.length) {
for(var i=0; i< $scope.data.list.length; i++) {
list_item = $scope.data.list[i];
var _sd = list_item['task.short_description'].value.replace(/\s/g, "");
 
var _state = list_item.state.value;
if($scope.data.restrictedStatesForDuplicateMerge.indexOf(_state.toLowerCase()) !== -1)
continue;
var _ptc = list_item.project_time_category.value || '';
var _taskSysId = list_item.taskSysId;
 
//Since task can be associated with any categories
var _category = list_item.category.value;
 
var _rateType = (list_item.rate_type && list_item.rate_type.value) || '';
var _rpln = (list_item.resource_plan && list_item.resource_plan.value) || '';
 
var uniqueItemKey = _sd + _state + _ptc + _rateType + _taskSysId + _category + _rpln;
var duplicateList = $scope.duplicateList;
if(duplicateList[uniqueItemKey] && duplicateList[uniqueItemKey].length && _taskSysId) {
duplicateList[uniqueItemKey][0].isDuplicate = true;
duplicateList[uniqueItemKey].push(list_item);
list_item.isDuplicate = true;
} else {
duplicateList[uniqueItemKey] = [];
duplicateList[uniqueItemKey].push(list_item);
}
}
}
 
for(var prop in $scope.duplicateList) {
if($scope.duplicateList[prop] && $scope.duplicateList[prop].length === 1) {
$scope.duplicateList[prop][0].isDuplicate = false;
}
}
$scope.$$phase || $scope.$root && $scope.$root.$$phase || $scope.$digest();
};
 
$scope.highlightDuplicates();
 
    $scope.showTooltip = TooltipFactory.showTooltip;
}



This is server script:

(function($sp) {
data.userId = $sp.getParameter('sysparm_user_id') || gs.getUserID();
var timesheetId = $sp.getParameter('sysparm_timesheet_id') || input.timesheetId;
if(!timesheetId)
timesheetId = new TimeSheetFactory().getTimeSheet(data.userId);
data.timesheetId = timesheetId;
var timesheet = new GlideRecord('time_sheet');
timesheet.get(timesheetId);
var timecardGr = new GlideRecord('time_card');
timecardGr.newRecord();
data.canCreateTimecards = timecardGr.canCreate();
 
data.isTooltipEnable = true;
var tcps = new TimeCardPortalService();
data.week_range = tcps.getWeekRange(timesheet.week_starts_on.getGlideObject());
var state = timesheet.getValue('state');
var dayFields = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
 
var timesheetPolicy = TimeSheetPolicy.getFromUserId(data.userId);
data.isAllowMultipleRateTypes = timesheetPolicy.allowMultipleRatetypes();
data.defaultRateType = timesheetPolicy.defaultRateType();
 
if(data.isAllowMultipleRateTypes)
data.tcDuplicateMsg = gs.getMessage("A timecard for the task, project category and rate type already exists. Do you want to merge duplicate timecards?");
else
data.tcDuplicateMsg = gs.getMessage("A timecard for the task and project category already exists. Do you want to merge duplicate timecards?");
 
var defaultHeaderFields = ['task.short_description','task.assigned_to', 'project_time_category'];
data.fields = defaultHeaderFields;
 
if(data.isAllowMultipleRateTypes)
data.fields.push('rate_type');
 
// Add Assigned to column (reference field on time_card)
if (data.fields.indexOf('task.assigned_to') === -1)
  data.fields.push('task.assigned_to');
 
/*
Integrating Resource Plan in TimeCard Portal.
*/
var grRpln = new GlideRecordSecure('time_card');
var rplnColumnName = 'resource_plan';
data.allowChangeOfResourcePlan = grRpln.isValidField(rplnColumnName) && timesheetPolicy.updateResourcePlan();
// Adds resource plan column by default.
if(data.allowChangeOfResourcePlan)
data.fields.push(rplnColumnName);
 
/* Get grid configuration */
var customFields = options.header_fields || input.options.header_fields;
data.pageSize = options.pageSize;
data.restrictedStatesForDuplicateMerge = options.restrictedStatesForDuplicateMerge || input.options.restrictedStatesForDuplicateMerge || [];
 
//Consider the tables provided in the input or options
var defaultTables = 'rm_epic,sn_safe_epic,incident,problem,change_request,rm_story,rm_scrum_task,pm_project,pm_project_task,rm_defect,rm_enhancement,dmn_demand_task,sn_audit_task,sn_audit_engagement,sn_audit_advanced_milestone,sn_audit_advanced_engagement_project,sn_grc_issue';
data.tables = input && input.tables ? input.tables : options.tables || defaultTables;
var nameMapping = [];
 
var gr = new GlideRecord('sys_db_object');
gr.addQuery('name', 'IN', data.tables);
gr.query();
while (gr.next()) {
nameMapping.push({
id: gr.name.toString(),
text: gr.label.toString() || ''
});
}
 
data.tableOption = nameMapping;
/* Inlcude additional configured fields */
if(customFields) {
customFields.forEach(function(fieldConf) {
if(data.fields.indexOf(fieldConf.name)==-1) {
if(fieldConf.name === 'resource_assignment') {
var grRAss = new GlideRecordSecure('time_card');
var rassColumnName = 'resource_assignment';
data.allowChangeOfResourceAssign = grRAss.isValidField(rassColumnName) && timesheetPolicy.updateResourcePlan();
// Adds resource assignment column by default.
if(data.allowChangeOfResourceAssign && data.fields.indexOf(fieldConf.name)==-1)
data.fields.push(rassColumnName);
} else
data.fields.push(fieldConf.name);
}
});
}
var dayObj = timesheet.week_starts_on.getGlideObject();
var day = dayObj.getDayOfWeekUTC();
for(var i = 1; i<=7;i++){
data.fields.push(dayFields[day-1]);
day = (day + 1 <= 7) ? day + 1  : 1;
}
data.fields.push('total');
 
var field_labels = {
//'task.number' : gs.getMessage('Task Number'),
'task.short_description' : gs.getMessage('Task'),
'task.assigned_to' : gs.getMessage('Assigned To'),
'monday' : sn_i18n.Message.getMessage('app_ppm', 'Mon{?three char representing day of week Monday}'),
'tuesday': sn_i18n.Message.getMessage('app_ppm', 'Tue{?three char representing day of week Tuesday}'),
'wednesday': sn_i18n.Message.getMessage('app_ppm', 'Wed{?three char representing day of week Wednesday}'),
'thursday': sn_i18n.Message.getMessage('app_ppm', 'Thu{?three char representing day of week Thursday}'),
'friday': sn_i18n.Message.getMessage('app_ppm', 'Fri{?three char representing day of week Friday}'),
'saturday': sn_i18n.Message.getMessage('app_ppm', 'Sat{?three char representing day of week Saturday}'),
'sunday': sn_i18n.Message.getMessage('app_ppm', 'Sun{?three char representing day of week Sunday}')
};
 
/* Add custom labels */
if(customFields) {
customFields.forEach(function(fieldConf) {
field_labels[fieldConf.name] =  fieldConf.label;
});
}
 
data.breakdowns = tcps.getDayBreakdowns(data.timesheetId);
 
var dateOfDay = {};
for(var j in data.breakdowns) {
if(!dateOfDay[data.breakdowns[j].field]) {
dateOfDay[data.breakdowns[j].field] = data.breakdowns[j].date;
}
}
data.column_meta = {};
data.editable_fields = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'project_time_category', 'task.assigned_to'];
var grForLabels = new GlideRecord('time_card');
for (var i in data.fields) {
var field = data.fields[i];
var ge = grForLabels.getElement(field);
 
if(field_labels[field]) {
                    data.column_meta[field] = {};
                    data.column_meta[field]['label'] = field_labels[field];
}
if (ge == null)
continue;
data.column_meta[field] = {};
data.column_meta[field]['label'] = field_labels[field] || ge.getLabel();
if(dateOfDay[field]) {
data.column_meta[field]['label'] = data.column_meta[field]['label'] + " " + dateOfDay[field];
}
var userConfig = customFields.filter(function(conf){return conf.name == field && conf.width_in_percent;});
if(userConfig && userConfig.length>0)
data.column_meta[field]['width_in_percent'] = userConfig[0]['width_in_percent'];
}
for (i in data.editable_fields) {
if(data.column_meta[data.editable_fields[i]])
data.column_meta[data.editable_fields[i]]['editable'] = true;
}
 
if(data.isAllowMultipleRateTypes) {
data.editable_fields.push('rate_type');
data.column_meta['rate_type']['editable'] = true;
}
 
if(data.allowChangeOfResourcePlan && data.column_meta.hasOwnProperty('resource_plan')) {
data.editable_fields.push('resource_plan');
data.column_meta['resource_plan']['editable'] = true;
}
 
if(data.allowChangeOfResourceAssign && data.column_meta.hasOwnProperty('resource_assignment')) {
data.editable_fields.push('resource_assignment');
data.column_meta['resource_assignment']['editable'] = true;
}
 
// Fetch all active users for Assigned To dropdown
 var userGR = new GlideRecord('sys_user');
 userGR.addActiveQuery();
 userGR.query();
data.usersList = [];
 while (userGR.next()) {
   data.usersList.push({
       sys_id: userGR.getValue('sys_id'),
       name: userGR.getValue('name')
  });
 
 }
 
data.isProjectActive = data.column_meta.hasOwnProperty("project_time_category");
data.list = [];
var isDataSeparationSkippedFromNowOnwards = false;
try {
    isDataSeparationSkippedFromNowOnwards = typeof DataSeparatorGlobalUtil !== "undefined" ? DataSeparatorGlobalUtil.skipDataSeparation() : false;
var gr = new GlideRecordSecure('time_card');
gr.addQuery('time_sheet', data.timesheetId);
gr.orderByDesc('sys_created_on');
gr.query();
data.fields_array = data.fields.concat(['category', 'state', 'task.sys_id', 'task.sys_class_name', 'project_time_category', 'category']);
data.actionableRows = 0;
data.totalRows = gr.getRowCount();
while (gr.next()) {
var record = {};
record.messages = {};
var taskRec = gr.task.getRefRecord();
$sp.getRecordElements(record, gr, data.fields_array);
record.sys_id = gr.getValue('sys_id');
record.comments = gr.getDisplayValue('comments');
record.canEdit = gr.canWrite();
record.canDelete = gr.canDelete();
var timeCardUiActionHelper = new TimeCardUIActionHelper(gr);
record.canSubmit = timeCardUiActionHelper.canSubmit();
record.canApprove = timeCardUiActionHelper.canApprove();
record.canReject = timeCardUiActionHelper.canReject();
record.canRecall = timeCardUiActionHelper.canRecall();
record.isProjectWork = false;
record.taskSysId = false;
record.messages.stateDisplayMessage = gs.getMessage('State: {0}', record['state'].display_value);
record.messages.rowDisplayMessage = gs.getMessage('Total hours for task {0} is {1} and the state is {2}', [record['task.short_description'].value, record['total'].value, record['state'].display_value]);
if(taskRec){
record.isProjectWork = taskRec.instanceOf("pm_project") || taskRec.instanceOf("pm_project_task");
record.taskSysId = taskRec.getValue("sys_id");
}
if(!record.taskSysId && gr.getValue("category") != "project_work" && gr.getValue("category") != "task_work" )
record.taskSysId = "no_task";
 
if(record.canEdit || record.canDelete)
data.actionableRows++;
if(JSUtil.nil(record['task.short_description']['value']))
if(record['task.sys_class_name']['value'] == 'sn_audit_engagement'){
record['task.short_description']['display_value'] = String(gr.task.name);
record['task.short_description']['number'] = String(gr.task.number);
} else {
record['task.short_description'] = record['category'];
}
else
record['task.short_description']['number'] = String(gr.task.number);
// var projectTimeCategory = record['project_time_category']['display_value'];
// if(JSUtil.notNil(projectTimeCategory)){
// record['task.short_description'].display_value += ' (' + projectTimeCategory +')';
// }
 
data.list.push(record);
}
 
} finally {
if (isDataSeparationSkippedFromNowOnwards)
DataSeparatorGlobalUtil.honourDataSeparation();
}
data.defaultField = 'total';
data.canGenerateTimeCards = (new TimeSheetUIActionHelper(timesheet)).canGenerateTimeCards(data.userId);
 
data.messages = {};
data.messages.selectATask = gs.getMessage('Select a Task');
data.messages.enterSearchBy = gs.getMessage('Select a Table');
data.messages.allTablesOption = gs.getMessage('All');
data.messages.noMatchesFound = gs.getMessage('No matches found');
data.messages.loadingFailed = gs.getMessage('Loading failed');
data.messages.loadingMoreResults = gs.getMessage('Loading more results…');
data.messages.searching = gs.getMessage('Searching…');
data.messages.inputTooShort = gs.getMessage('Enter task number or short description');
data.messages.confirmDlgTitle = gs.getMessage('Delete Time Card');
data.messages.confirmMssg = gs.getMessage('Are you sure, you want to delete this time card?');
data.messages.recallDlgTitle = gs.getMessage('Recall Time Card');
data.messages.recallConfirmMssg = gs.getMessage('Recalling the Time Card will revert actual effort and associated expense lines.');
data.messages['delete'] = gs.getMessage('Delete');
data.messages.recall = gs.getMessage('Recall');
data.messages.cancel = gs.getMessage('Cancel');
data.messages.selectRateType = gs.getMessage('Select rate type');
data.messages.selectProjectTimeCategoryType = gs.getMessage('Select category');
data.messages.selectResourcePlan = gs.getMessage('Select Resource Plan');
data.messages.selectResourceAssignment = gs.getMessage('Select Resource Assignment');
data.messages.none = gs.getMessage('--None--');
 
})($sp);
 


This is HTML script-

<div id="tc-grid">
<div class="grid-header clearfix">
<span class="tc-grid-header">${Logged Time Cards}</span>
<span class="pull-right clearfix" ng-if="::data.canCreateTimecards">
<span class="fa fa-question-circle pull-right" uib-tooltip="${Search any task that you have worked during the week and add to the Time Sheet.}"
tooltip-placement="left" aria-label="${Help}" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)"></span>
<button type="button" class="btn btn-link pull-right" ng-click="addTask()" id="add-task" aria-haspopup="true">${Add unassigned tasks to Time Sheet}</button>
</span>
</div>

<div class="tc-grid-cntr" ng-if="data.list.length">
<div class="table-responsive">
<div class="tc-table-layout">
<div class="container" id="tc-grid-table" role="table" aria-label="Timecards">
<div role="rowgroup">
<div class="flex table-headers" role="row">
<div scope="col" ng-repeat="field in ::data.fields track by $index" class="flex-item {{field}}" ng-class="{'selected':data.highlightedField == field}"
role="columnheader" ng-style="{{::getWidth(field)}}">
<div class="th-title {{field}}">
<strong class="wrap-text">{{::data.column_meta[field].label}}</strong>
</div>
</div>
<div class="flex-item placeholder" role="columnheader">
<a href="Javascript&colon;void(0);" class="icon-ellipsis-vertical more-action-btn"></a>
</div>
</div>
</div>

<div class="table-body" role="rowgroup" ng-style="::tableBodyStyles">
<div ng-repeat="item in paginatedList track by item.sys_id" collection="paginatedList" repeat-complete="tc-grid" class="flex tc-row" id="{{::item.sys_id}}"
ng-class="{'active': data.timecardInEditMode == item.sys_id, 'disabled': !item.canEdit, 'rejected-desc' : item.state.value === 'Rejected'}"
tabindex="{{item.canEdit ? 0 : -1}}" ng-dblclick="editRow($event, item);" ng-keydown="keyHandler($event, item, $index)"
aria-disabled="{{!item.canEdit}}" aria-selected="{{data.timecardInEditMode === item.sys_id}}" aria-label="{{item.messages.rowDisplayMessage}}"
role="row">

<div ng-repeat="field in ::data.fields track by field" class="{{::field}} flex-item" data-field="{{::field}}" data-th="{{::data.column_meta[field].label}}"
ng-class="{'selected':data.highlightedField == field}" role="cell" ng-style="{{::getWidth(field)}}">

<div class="{{field}} text-elipsis" ng-if="data.timecardInEditMode != item.sys_id || !data.column_meta[field].editable">
<div class="tc-value" ng-class="{'disp-flex align-center' : $first}">
<span ng-if="$first" tabindex="0" class="state-icon" uib-tooltip="{{item.messages.stateDisplayMessage}}" tooltip-placement="right" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)"
tooltip-append-to-body="true" ng-class="{'icon-workflow-pending' : (item['state'].value === 'Pending'), 'icon-workflow-progress' : (item['state'].value === 'Submitted'), 'icon-workflow-check' : (item['state'].value === 'Processed' || item['state'].value === 'Approved'), 'icon-workflow-rejected' : (item['state'].value === 'Rejected'), 'icon-workflow-skip icon-flipped': (item['state'].value === 'Recalled')}" aria-label="{{item.messages.stateDisplayMessage}}" role="img"></span>

<p class="text-elipsis" uib-tooltip="{{item[field].display_value}}" tooltip-placement="auto top-left" tooltip-append-to-body="true" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)">
{{item[field].display_value || (item[field].value ? item[field].value : '${None}')}}
</p>

<div ng-if="field === 'task.short_description' && item[field].number">
<a target="_blank" class="anchor-tag" ng-href="/{{::item['task.sys_class_name'].value}}.do?sys_id={{::item['task.sys_id'].value}}">{{item[field].number}}</a>
</div>
</div>
</div>

<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && field === 'task.assigned_to'" class="select2_field">
<label class="sr-only" for="{{field}}">{{field}}</label>
<select class="form-control tc-row-input"
id="{{field}}"
ng-model="item[field].value"
ng-change="item[field].display_value = (data.usersList | filter:{sys_id: item[field].value})[0].name"
ng-options="user.sys_id as user.name for user in data.usersList">
<option value="">-- None --</option>
</select>
</div>

<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && field === 'project_time_category' && data.isProjectActive" ng-class="{select2_field:(item['isProjectWork']) }">
<label class="sr-only" for="{{field}}">{{field}}</label>
<span ng-if="!(item['isProjectWork'])">${None}</span>
<input ng-if="(item['isProjectWork'])" type="text" id="{{field}}" name="{{field}}" ng-model="item[field].display_value" class="tc-row-input"
ng-class="{'error': item[field].invalid}" aria-invalid="{{item[field].invalid}}" />
</div>

<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && field !== 'rate_type' && field !== 'project_time_category' && field !== 'resource_plan' && field !== 'resource_assignment' && field !== 'task.assigned_to'" class="edit_mode">
<label class="sr-only" for="{{field}}">{{field}}</label>
<input type="text" id="{{field}}" name="{{field}}" ng-model="item[field].display_value" class="tc-row-input day-input"
ng-change="filterNumericInput(item[field])" ng-blur="focusOutEndDecimalCheck(item[field])" required/>
</div>

<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && (field === 'rate_type' || field === 'resource_plan' || field === 'resource_assignment') && (item.state.value === 'Pending' || item.state.value === 'Rejected' || item.state.value === 'Recalled')" class="select2_field">
<label class="sr-only" for="{{field}}">{{field}}</label>
<input type="text" id="{{field}}" name="{{field}}" ng-model="item[field].display_value" class="tc-row-input" ng-class="{'error': item[field].invalid}" aria-invalid="{{item[field].invalid}}" />
</div>
</div>

<div class="flex-item">
<div data-id="{{::item.sys_id}}">
<a href="Javascript&colon;void(0);" class="icon-ellipsis-vertical more-action-btn" popover-animation="true" uib-popover-template="'tcMoreActions'"
popover-placement="auto left" popover-trigger="outsideClick" popover-is-open="data.tcPopover{{item.sys_id}}"
popover-class="more-action-popover" ng-click="adjustPopover($event)" role="button" aria-label="${More actions for {{::item['task.short_description'].display_value}}}"
uib-tooltip="${More actions for {{::item['task.short_description'].display_value}}}" tooltip-placement="auto left" tooltip-append-to-body="true"
tooltip-is-open="data.moreActionTp{{item.sys_id}}" ng-mouseover="tooltipEnable($event,false)" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)"></a>
</div>
</div>
</div>
<sp-paginator ng-if="pageSize <= total" total="total" page-size="pageSize" current="current"></sp-paginator>
</div>
</div>
</div>
</div>
</div>

<div ng-if="!data.list.length">
<div id="empty-state">
<div style="font-size:12px;text-align:center;margin-top: 50px;">${No Time Cards logged yet.}</div>
<div style="margin-top:30px;text-align:center;" ng-if="::data.canCreateTimecards">
<button type="button" class="btn btn-default" id="generate-timecard" ng-if="data.canGenerateTimeCards" ng-click="generateTimecards()">${Generate Time Cards}</button>
<button type="button" class="btn btn-default" id="copy-timesheet" ng-click="copyFromPreviousTimesheet()" aria-haspopup="true">${Copy from previous Time Sheet}</button>
</div>
</div>
</div>

<sp-widget widget="data.modalInstance" ng-if="data.modalInstance"></sp-widget>
<pw-confirm-dialog data="data.confirmDialogOpts" ng-if="showConfirm"></pw-confirm-dialog>
</div>