/*
* Copyright 2024 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* CoursePart tool panel
* @private
*/
Ext.define('Ametys.plugins.odf.pilotage.tool.CoursePartsTreeGridPanel', {
extend: 'Ametys.plugins.odf.tree.AbstractODFTreeGridPanel',
mixins: {
editContentsView: "Ametys.cms.content.EditContentsView"
},
constructor: function(config)
{
config.serverRole = config.serverRole || "org.ametys.plugins.odfpilotage.helper.CoursePartsTreeHelper";
config.cls = Ext.Array.from(config.cls);
config.cls.push("contentviewtreegrid");
config.cls.push("treegrideditionfirst");
config.cls.push("coursepartstreegrid");
config.saveBarPosition = 0;
var natureColumns = this._getNaturesColumns(config.natures, config.nbHours);
config.columns = this._getGridConfig(natureColumns);
config.methodArguments = ['contentId', 'path', 'tree'];
this.callParent(arguments);
this.mixins.editContentsView.constructor.call(this, config);
this.getStore().on('load', this._onLoad, this);
},
_createEditPlugin: function(config) {
let plugin = this.mixins.editContentsView._createEditPlugin.apply(this, arguments);
plugin.editAfterSelect = false;
return plugin;
},
_onLoad: function(store, records, successful, operation, eOpts)
{
// Checking if the new record is not about an existing record with the same contentId
let modifiedContents = {}
for (let modifiedRecord of this.store.getModifiedRecords()) // Maybe we should use this.getModifiedRecords() (from EditContentsView) to also include rootNode
{
modifiedContents[this._getContentId(modifiedRecord)] = modifiedRecord;
}
for (let record of records)
{
this._loadRecord(record, modifiedContents);
}
},
_loadRecord: function(record, modifiedContents)
{
if (!modifiedContents || Object.keys(modifiedContents).length == 0)
{
return;
}
let modifiedRecord = modifiedContents[this._getContentId(record)];
let changes;
if (modifiedRecord)
{
changes = modifiedRecord.getChanges();
}
let changesKeys = changes ? Object.keys(changes) : null;
if (changesKeys)
{
for (let changeFieldName of changesKeys)
{
if (modifiedRecord.get(changeFieldName + "_repeater") !== undefined)
{
changes[changeFieldName + "_repeater"] = modifiedRecord.get(changeFieldName + "_repeater");
}
if (modifiedRecord.get(changeFieldName + "_content") !== undefined)
{
changes[changeFieldName + "_content"] = modifiedRecord.get(changeFieldName + "_content");
}
}
changesKeys = Object.keys(changes);
for (let changeFieldName of changesKeys)
{
record.set(changeFieldName, changes[changeFieldName]);
}
}
let me = this;
window.setTimeout(function() {
for (let childRecord of record.childNodes || [])
{
me._loadRecord(childRecord, modifiedContents);
}
}, 1)
},
_getContentId: function(record)
{
return record.get('contentId');
},
_findRecords(contentId) {
let records = [];
// Using byIdMap allow to search even in filtered items
for (let id in this.store.byIdMap)
{
if (this.store.byIdMap[id].get('contentId') == contentId)
{
records.push(this.store.byIdMap[id]);
}
}
return records;
},
_validateEdit: function (editor, context)
{
let me = this;
let b = this.mixins.editContentsView._validateEdit.apply(this, arguments); // Cannot call callParent on mixins
if (!b)
{
return false;
}
function _getOriginalValue(record, dataIndex)
{
if (record.modified && record.modified[dataIndex] !== undefined)
{
return record.modified[dataIndex];
}
else
{
return record.get(dataIndex);
}
}
if (editor.activeEditor.field.getValue() == null && _getOriginalValue(context.record, context.column.dataIndex) != null && !me.forceComplete)
{
Ametys.MessageBox.confirm ("{{i18n plugin.odf:PLUGINS_ODF_WIDGET_SELECT_COURSEPART_REMOVE_TITLE}}",
"{{i18n plugin.odf:PLUGINS_ODF_WIDGET_SELECT_COURSEPART_REMOVE_CONFIRM}}",
function (answer) {
if (answer == "yes")
{
if (editor.editing)
{
me.forceComplete = true;
editor.completeEdit();
}
else
{
context.record.set(context.column.dataIndex, null);
}
}
else
{
if (editor.editing)
{
editor.cancelEdit();
}
}
}
)
return false;
}
me.forceComplete = false;
return true;
},
_saveEditionWithContents: function(contents, callback)
{
Ametys.plugins.odf.pilotage.helper.ODFTreeGridPanelHelper.prototype._saveEditionWithContents.call(this, contents, callback);
},
/**
* @private
* continue the save edition process
* @param {Object} response The response object
* @param {Object} args The arguments of the sendMessage
*/
_saveEditionCB: function(response, args)
{
var content = args['content'];
var callback = args['callback'];
// Object content may be outdated, just trust its id
var contentId = content.getId();
let records = this._findRecords(contentId);
// If there are errors, show them
if (response.errors && Object.keys(response.errors).length > 0)
{
let title = "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COURSEPARTS_SAVE_ACTION_ERROR}}";
let contentTitle = records[0].data.title;
// If the error is on the course level, show the error of the course
if (response.errors.course)
{
Ametys.log.ErrorDialog.display({
title: title,
text: Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COURSEPARTS_SAVE_ACTION_ERROR_COURSE_LOCKED}}", contentTitle)
});
}
// If there are errors on the number of hours fields, show them
else
{
let nbErrors = Object.keys(response.errors).length;
var msg = Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COURSEPARTS_SAVE_ACTION_FAILED_DESC}}", contentTitle);
msg += nbErrors == 1 ? "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COURSEPARTS_SAVE_ACTION_FAILED_DESC_SINGLE}}" : nbErrors + "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COURSEPARTS_SAVE_ACTION_FAILED_DESC_MULTI}}";
var detailedMsg = "";
for (let [changeKey, error] of Object.entries(response.errors))
{
detailedMsg += error.title + " - " + error.errorMsg + "\n";
}
Ametys.log.ErrorDialog.display({
title: title,
text: msg,
details: detailedMsg
});
}
}
// If there are no successes, quit
if (!response.success)
{
callback(false);
return;
}
// graphically finish the save process
for (let r of records)
{
// Retrieve the values not to commit because they have errors
let uncommittedValues = null;
if (response.errors)
{
uncommittedValues = {};
let revertValues = {};
for (let keyModified of Object.keys(response.errors))
{
uncommittedValues[keyModified] = r.get(keyModified);
// Revert the changes on theses values to be able to commit
revertValues[keyModified] = r.modified[keyModified];
}
r.set(revertValues);
}
// Commit the changes
r.commit(null, Object.keys(r.modified));
// Re set the values that could not be commited
if (uncommittedValues)
{
r.set(uncommittedValues);
}
}
this._showHideSaveBar();
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.MODIFIED,
targets: {
id: this.messageTarget,
parameters: { ids: [contentId] }
}
});
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.LOCK_CHANGED,
targets: {
id: this.messageTarget,
parameters: { ids: [contentId] }
}
});
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.WORKFLOW_CHANGED,
targets: {
id: this.messageTarget,
parameters: { ids: [contentId] }
}
});
callback(true);
},
/**
* Function creating the JSON structure of hourly volumes grid
* @param {Object} natures The list of hourly volumes codes
* @private
*/
_getNaturesColumns: function(natures, nbHours)
{
var json = [];
var i = 0;
for (var [key, nature] of Object.entries(natures))
{
let cfg = {
text: key,
editor: {
msgTarget: 'qtip',
cls: 'ametys',
xtype: Ametys.form.WidgetManager.getWidgetXType (nbHours.widget, nbHours.type.toLowerCase(), false, false),
},
stateId: nature.id,
dataIndex: nature.id,
align: 'center',
sortable: false,
renderer: this._renderVolumeOfHours,
tooltip: nature.label,
hidden: nature.archived,
lockable: false
};
if (nbHours['widget-params'])
{
cfg.editor = Ext.apply(cfg.editor, nbHours['widget-params']);
}
json[i++] = cfg;
}
return json;
},
/**
* Render the hourly volume column.
* @param {Object} value The data value for the current cell.
* @param {Object} metadata A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @param {Number} rowIndex The index of the current row
* @param {Number} cellIndex The index of the current cell
* @param {Ext.data.Store} store The store
* @param {Ext.view.View} view The current view
* @private
*/
_renderVolumeOfHours: function(value, metadata, record, rowIndex, cellIndex, store, view)
{
let headerCt = this.getHeaderContainer();
let column = headerCt.getHeaderAtIndex(cellIndex);
// Tooltip and style
if (!(record.data['notEditableData'] === true)
&& !(record.data['notEditableDataIndex'] && Ext.Array.contains(record.data['notEditableDataIndex'], column.dataIndex))
&& !(record.data[column.dataIndex + '_external_status'] == 'external'))
{
metadata.tdCls += ' editable';
}
// If value === -1, that means that there are multiple CoursePart for the nature
if (value === -1)
{
let valuesOfColumn = record.data.volumesOfHours[column.dataIndex]['values'];
if (valuesOfColumn.length > 1)
{
let sum = 0;
let i = 0;
for (let valueInMap of valuesOfColumn)
{
i++;
if (valueInMap != undefined)
{
sum += valueInMap;
}
}
var msgForToolTip = Ext.String.format("{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COURSEPARTS_MULTIPLE_COURSEPARTS_MESSAGE}}", record.data.volumesOfHours[column.dataIndex].name, record.data.title);
metadata.tdAttr = 'data-qtip="' + msgForToolTip + '"';
// Show the sum of all the courseParts in the field
return sum.toFixed(2);
}
}
else
{
metadata.tdAttr = 'data-qtip=""'; // reset qtip if a previous value was set in another rendering
}
if (value != undefined)
{
return value.toFixed(2);
}
},
/**
* Function creating the structure of the columns grid
* @param {Object} natures the structure of the hourly volumes columns
* @private
*/
_getGridConfig: function(natureColumns)
{
let gridConfig = [{
xtype: 'treecolumn',
text: "{{i18n plugin.odf-pilotage:PLUGINS_ODF_PILOTAGE_TOOL_COST_MODELING_GRID_MODEL}}",
stateId: 'title',
sortable: false,
dataIndex: 'title',
width: 400,
locked: true,
lockable: false
}];
for (let natureColumn of natureColumns)
{
gridConfig.push(natureColumn);
}
return gridConfig;
}
})