This commit is contained in:
2026-04-14 08:37:19 -03:00
parent fb2bec0fd5
commit 54ab21ef3b
91 changed files with 5660 additions and 195 deletions
@@ -0,0 +1,19 @@
application.type=widget
application.code=wdtransfloja
application.title=wdtransfloja
application.description=wdtransfloja
application.fluig.version=null
application.category=SYSTEM
application.renderer=freemarker
developer.code=Andrey Cunha
developer.name=Andrey Cunha
developer.url=http://www.fluig.com
application.uiwidget=true
application.mobileapp=false
application.version=${build.version}-${build.revision}
view.file=view.ftl
edit.file=edit.ftl
locale.file.base.name=wdtransfloja
application.resource.js.1=/resources/js/wdtransfloja.js
application.resource.css.2=/resources/css/wdtransfloja.css
hash=4a16315e9e66fa7d797b3f6b1fb365b69f9a4ce2
@@ -0,0 +1,5 @@
<div id="MyWidget_${instanceId}" class="super-widget wcm-widget-class fluig-style-guide" data-params="WdTransfLojaWidget.instance()">
</div>
@@ -0,0 +1,11 @@
<div id="MyWidget_${instanceId}" class="super-widget wcm-widget-class fluig-style-guide wdtransf-agg-widget" data-params="WdTransfLojaWidget.instance()">
<div class="wdtransf-agg-shell">
<div class="wdtransf-agg-chart-wrap">
<h3 class="wdtransf-agg-title">Transferencias por Loja</h3>
<div class="wdtransf-agg-chart" data-role="chartBars">
<div class="wdtransf-agg-chart-empty">Carregando dados...</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<context-root>/wdtransfloja</context-root>
<disable-cross-context>false</disable-cross-context>
</jboss-web>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
@@ -0,0 +1,181 @@
.wdtransf-agg-widget .wdtransf-agg-shell {
background: #f4f7fb;
border: 1px solid #d8e3ee;
border-radius: 12px;
padding: 8px;
}
.wdtransf-agg-widget .wdtransf-agg-chart-wrap {
background: #e8ecf1;
border: 1px solid #d3dde8;
border-radius: 16px;
padding: 10px 12px;
}
.wdtransf-agg-widget .wdtransf-agg-title {
margin: 0 0 8px;
color: #00507c;
font-size: 18px;
font-weight: 700;
text-align: center;
}
.wdtransf-agg-widget .wdtransf-agg-chart {
display: flex;
flex-direction: column;
gap: 6px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-legend {
margin-bottom: 4px;
color: #486680;
font-size: 12px;
font-weight: 600;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-legend-swatch {
display: inline-block;
width: 16px;
height: 10px;
border-radius: 2px;
background: #0f678a;
}
.wdtransf-agg-widget .wdtransf-agg-vchart {
display: grid;
grid-template-columns: 36px 1fr;
gap: 8px;
align-items: end;
min-height: 195px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-axis {
height: 160px;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #57728b;
font-size: 11px;
text-align: right;
padding-right: 2px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-scroll {
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 2px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-plot {
position: relative;
height: 160px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-grid {
position: absolute;
left: 0;
right: 0;
border-top: 1px solid #ced9e4;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-grid.grid-100 { top: 0; }
.wdtransf-agg-widget .wdtransf-agg-vchart-grid.grid-50 { top: 50%; }
.wdtransf-agg-widget .wdtransf-agg-vchart-grid.grid-0 { bottom: 0; }
.wdtransf-agg-widget .wdtransf-agg-vchart-bars {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: flex-end;
gap: 10px;
padding: 0 6px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-item {
width: 64px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-value {
color: #1e4e71;
font-size: 11px;
font-weight: 700;
line-height: 1;
min-height: 12px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-bar-wrap {
width: 30px;
height: 120px;
display: flex;
align-items: flex-end;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-bar {
width: 100%;
border-radius: 4px 4px 0 0;
background: #0f678a;
min-height: 2px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-label {
color: #3c5c75;
font-size: 11px;
font-weight: 600;
text-align: center;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transform: rotate(-12deg);
transform-origin: center top;
}
.wdtransf-agg-widget .wdtransf-agg-chart-empty {
color: #4f6880;
font-size: 12px;
padding: 8px 0;
}
@media (max-width: 767px) {
.wdtransf-agg-widget .wdtransf-agg-chart-wrap {
padding: 8px;
}
.wdtransf-agg-widget .wdtransf-agg-title {
font-size: 16px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart {
grid-template-columns: 1fr;
min-height: 170px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-axis {
display: none;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-plot {
height: 145px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-bar-wrap {
height: 108px;
}
.wdtransf-agg-widget .wdtransf-agg-vchart-label {
transform: none;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

@@ -0,0 +1,447 @@
var WdTransfLojaWidget = SuperWidget.extend({
datasetName: "transferenciav2",
batchSize: 1000,
rowsCache: [],
isIncrementalLoading: false,
loadOffset: 0,
batchToken: 0,
lastChunkMarker: "",
noPagingMode: false,
groupKeyCandidates: ["estabelecimento", "filialOrigem", "lojaOrigem", "centroCusto", "filialDestino", "lojaDestino"],
emptyLabel: "Nao informado",
bindings: {
local: {},
global: {}
},
init: function() {
this.$widget = $("#MyWidget_" + this.instanceId);
this.bindEvents();
this.loadData();
},
bindEvents: function() {
var self = this;
self.$widget.on("click", "[data-action='reload']", function() {
self.loadData();
});
self.$widget.on("change", "[data-role='topN']", function() {
self.render();
});
self.$widget.on("input", "[data-role='search']", function() {
self.render();
});
self.$widget.on("keypress", "[data-role='search']", function(e) {
if (e.which === 13) {
e.preventDefault();
self.render();
}
});
},
loadData: function() {
this.batchToken++;
this.rowsCache = [];
this.loadOffset = 0;
this.lastChunkMarker = "";
this.noPagingMode = false;
this.isIncrementalLoading = true;
this.setLoading(true);
this.setMessage("Carregando solicitacoes...", "info");
this.render();
this.loadNextBatch(this.batchToken);
},
loadNextBatch: function(token) {
var self = this;
if (token !== self.batchToken) return;
self.fetchDatasetBatch(self.batchSize, self.loadOffset, function(err, rows) {
if (token !== self.batchToken) return;
if (err) {
self.isIncrementalLoading = false;
self.setLoading(false);
self.render();
self.setMessage("Erro ao carregar dataset: " + err, "danger");
return;
}
rows = rows || [];
if (!rows.length) {
self.isIncrementalLoading = false;
self.setLoading(false);
self.render();
self.setMessage("Dados carregados: " + self.formatNumber(self.rowsCache.length) + " registro(s).", "success");
return;
}
var chunkMarker = self.buildChunkMarker(rows[0]);
if (self.loadOffset > 0 && chunkMarker && chunkMarker === self.lastChunkMarker) {
self.isIncrementalLoading = false;
self.setLoading(false);
self.render();
self.setMessage("Paginacao do dataset nao evoluiu (sqlStart/offset). Verifique suporte no ambiente.", "warning");
return;
}
self.lastChunkMarker = chunkMarker;
self.rowsCache = self.rowsCache.concat(rows);
self.loadOffset += rows.length;
self.render();
self.setMessage("Carregando solicitacoes... " + self.formatNumber(self.rowsCache.length) + " registro(s).", "info");
if (self.noPagingMode) {
self.isIncrementalLoading = false;
self.setLoading(false);
self.render();
self.setMessage("Dados carregados: " + self.formatNumber(self.rowsCache.length) + " registro(s).", "success");
return;
}
if (rows.length < self.batchSize) {
self.isIncrementalLoading = false;
self.setLoading(false);
self.render();
self.setMessage("Dados carregados: " + self.formatNumber(self.rowsCache.length) + " registro(s).", "success");
return;
}
setTimeout(function() {
self.loadNextBatch(token);
}, 0);
});
},
fetchDatasetBatch: function(limit, offset, callback) {
var self = this;
if (self.noPagingMode && offset > 0) {
callback(null, []);
return;
}
try {
if (typeof DatasetFactory !== "undefined" && typeof DatasetFactory.getDataset === "function" &&
typeof ConstraintType !== "undefined" && typeof DatasetFactory.createConstraint === "function") {
var constraints = self.noPagingMode ? null : self.buildFactoryPagingConstraints(limit, offset);
var ds = DatasetFactory.getDataset(self.datasetName, null, constraints, null);
var rows = (ds && ds.values) ? ds.values : [];
if (!rows.length && offset === 0 && !self.noPagingMode) {
var dsNoPaging = DatasetFactory.getDataset(self.datasetName, null, null, null);
var rowsNoPaging = (dsNoPaging && dsNoPaging.values) ? dsNoPaging.values : [];
if (rowsNoPaging.length) {
self.noPagingMode = true;
callback(null, rowsNoPaging);
return;
}
}
callback(null, rows);
return;
}
} catch (e) {
// fallback REST
}
this.fetchDatasetBatchViaRest(limit, offset, callback);
},
buildFactoryPagingConstraints: function(limit, offset) {
var constraints = [];
constraints.push(DatasetFactory.createConstraint("sqlLimit", String(limit), String(limit), ConstraintType.MUST));
constraints.push(DatasetFactory.createConstraint("sqlStart", String(offset), String(offset), ConstraintType.MUST));
constraints.push(DatasetFactory.createConstraint("offset", String(offset), String(offset), ConstraintType.MUST));
constraints.push(DatasetFactory.createConstraint("sqlOffset", String(offset), String(offset), ConstraintType.MUST));
return constraints;
},
fetchDatasetBatchViaRest: function(limit, offset, callback) {
var self = this;
if (self.noPagingMode && offset > 0) {
callback(null, []);
return;
}
function readRows(resp) {
if (resp && resp.content && resp.content.values) return resp.content.values;
if (resp && resp.values) return resp.values;
return [];
}
function request(usePaging, done) {
var constraints = usePaging ? self.buildRestPagingConstraints(limit, offset) : [];
$.ajax({
url: "/api/public/ecm/dataset/datasets",
type: "POST",
contentType: "application/json; charset=UTF-8",
dataType: "json",
data: JSON.stringify({
name: self.datasetName,
fields: [],
constraints: constraints,
order: []
})
}).done(function(resp) {
done(null, readRows(resp) || []);
}).fail(function(xhr) {
var detail = xhr && (xhr.responseText || xhr.statusText || xhr.status);
done(detail || "falha na API REST de datasets", []);
});
}
request(!self.noPagingMode, function(err, rows) {
if (err) {
callback(err);
return;
}
if (!rows.length && offset === 0 && !self.noPagingMode) {
request(false, function(err2, rows2) {
if (err2) {
callback(err2);
return;
}
if (rows2.length) {
self.noPagingMode = true;
}
callback(null, rows2 || []);
});
return;
}
callback(null, rows || []);
});
},
buildRestPagingConstraints: function(limit, offset) {
return [
{ _field: "sqlLimit", _initialValue: String(limit), _finalValue: String(limit), _type: 1 },
{ _field: "sqlStart", _initialValue: String(offset), _finalValue: String(offset), _type: 1 },
{ _field: "offset", _initialValue: String(offset), _finalValue: String(offset), _type: 1 },
{ _field: "sqlOffset", _initialValue: String(offset), _finalValue: String(offset), _type: 1 }
];
},
buildChunkMarker: function(row) {
if (!row) return "";
var id = this.pick(row, ["metadata#id", "id", "WKNumProces", "processInstanceId"]);
var state = this.pick(row, ["activity", "WKNumState", "state"]);
return (id || "-") + "|" + (state || "-");
},
render: function() {
var grouped = this.aggregateRows(this.rowsCache);
var filteredGroups = this.filterGroups(grouped);
var shownGroups = this.applyTopN(filteredGroups);
this.renderChart(shownGroups, this.rowsCache.length);
},
aggregateRows: function(rows) {
var map = {};
var key;
for (var i = 0; i < (rows || []).length; i++) {
key = this.resolveGroupValue(rows[i]) || this.emptyLabel;
if (!map[key]) {
map[key] = {
name: key,
count: 0
};
}
map[key].count++;
}
var list = [];
for (var name in map) {
if (map.hasOwnProperty(name)) {
list.push(map[name]);
}
}
list.sort(function(a, b) {
if (b.count !== a.count) return b.count - a.count;
return String(a.name).localeCompare(String(b.name));
});
return list;
},
filterGroups: function(groups) {
var $search = this.$widget.find("[data-role='search']");
if (!$search.length) return groups.slice(0);
var search = $.trim(String($search.val() || "")).toLowerCase();
if (!search) return groups.slice(0);
var out = [];
for (var i = 0; i < groups.length; i++) {
var g = groups[i];
if (String(g.name || "").toLowerCase().indexOf(search) !== -1) {
out.push(g);
}
}
return out;
},
applyTopN: function(groups) {
var $top = this.$widget.find("[data-role='topN']");
if (!$top.length) return groups.slice(0);
var raw = $.trim(String($top.val() || "0"));
var topN = parseInt(raw, 10);
if (isNaN(topN) || topN < 0) topN = 0;
if (topN === 0) return groups.slice(0);
return groups.slice(0, topN);
},
renderKpis: function(allGroups, shownGroups) {
var totalSolicitacoes = this.rowsCache.length;
var totalGrupos = allGroups.length;
var solicitacoesExibidas = 0;
for (var i = 0; i < shownGroups.length; i++) {
solicitacoesExibidas += Number(shownGroups[i].count || 0);
}
this.$widget.find("[data-role='kpiTotalSolicitacoes']").text(this.formatNumber(totalSolicitacoes));
this.$widget.find("[data-role='kpiTotalGrupos']").text(this.formatNumber(totalGrupos));
this.$widget.find("[data-role='kpiSolicitacoesExibidas']").text(this.formatNumber(solicitacoesExibidas));
},
renderChart: function(groups, totalRows) {
var html = [];
if (!groups.length) {
var emptyMsg = this.isIncrementalLoading ?
"Carregando dados..." :
"Nenhum registro para os filtros informados.";
html.push("<div class='wdtransf-agg-chart-empty'>" + this.escapeHtml(emptyMsg) + "</div>");
this.$widget.find("[data-role='chartBars']").html(html.join(""));
return;
}
var bars = [];
for (var i = 0; i < groups.length; i++) {
var item = groups[i];
var count = Number(item.count || 0);
var percentTotal = totalRows > 0 ? (count * 100 / totalRows) : 0;
bars.push(
"<div class='wdtransf-agg-vchart-item'>" +
"<div class='wdtransf-agg-vchart-value'>" + this.escapeHtml(this.formatPercent(percentTotal)) + "</div>" +
"<div class='wdtransf-agg-vchart-bar-wrap'>" +
"<div class='wdtransf-agg-vchart-bar' style='height:" + percentTotal.toFixed(2) + "%;'></div>" +
"</div>" +
"<div class='wdtransf-agg-vchart-label' title='" + this.escapeHtml(item.name) + "'>" + this.escapeHtml(item.name) + "</div>" +
"</div>"
);
}
var minWidth = Math.max(groups.length * 86, 360);
html.push(
"<div class='wdtransf-agg-vchart-legend'>" +
"<span class='wdtransf-agg-vchart-legend-item'>" +
"<span class='wdtransf-agg-vchart-legend-swatch'></span>Participacao (%)" +
"</span>" +
"</div>" +
"<div class='wdtransf-agg-vchart'>" +
"<div class='wdtransf-agg-vchart-axis'>" +
"<span>100%</span><span>50%</span><span>0%</span>" +
"</div>" +
"<div class='wdtransf-agg-vchart-scroll'>" +
"<div class='wdtransf-agg-vchart-plot' style='min-width:" + minWidth + "px;'>" +
"<div class='wdtransf-agg-vchart-grid grid-100'></div>" +
"<div class='wdtransf-agg-vchart-grid grid-50'></div>" +
"<div class='wdtransf-agg-vchart-grid grid-0'></div>" +
"<div class='wdtransf-agg-vchart-bars'>" + bars.join("") + "</div>" +
"</div>" +
"</div>" +
"</div>"
);
this.$widget.find("[data-role='chartBars']").html(html.join(""));
},
resolveGroupValue: function(row) {
return this.pick(row || {}, this.groupKeyCandidates);
},
pick: function(row, keys) {
for (var i = 0; i < keys.length; i++) {
var value = row[keys[i]];
if (value !== null && value !== undefined && String(value).trim() !== "") {
return String(value).trim();
}
}
return "";
},
formatNumber: function(value) {
var n = Number(value || 0);
if (isNaN(n)) return "0";
return n.toLocaleString("pt-BR");
},
formatPercent: function(value) {
var n = Number(value || 0);
if (isNaN(n)) n = 0;
return n.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + "%";
},
renderUpdatedAt: function() {
var now = new Date();
var stamp = now.toLocaleDateString("pt-BR") + " " + now.toLocaleTimeString("pt-BR");
this.$widget.find("[data-role='updatedAt']").text("Atualizado em: " + stamp);
},
setLoading: function(isLoading) {
this.$widget.toggleClass("is-loading", !!isLoading);
if (isLoading) {
this.setMessage("Carregando dados...", "info");
}
},
setMessage: function(text, type) {
var $msg = this.$widget.find("[data-role='message']");
$msg.removeClass("is-info is-success is-warning is-danger");
if (!text) {
$msg.hide().text("");
return;
}
var className = "is-info";
if (type === "success") className = "is-success";
if (type === "warning") className = "is-warning";
if (type === "danger") className = "is-danger";
$msg.addClass(className).text(text).show();
},
escapeHtml: function(text) {
return String(text == null ? "" : text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#39;");
}
});