att
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
application.type=widget
|
||||
application.code=wddesligamentoranking
|
||||
application.title=wddesligamentoranking
|
||||
application.description=wddesligamentoranking
|
||||
application.fluig.version=null
|
||||
application.category=Desligamento
|
||||
application.renderer=freemarker
|
||||
developer.code=Andrey
|
||||
developer.name=Andrey
|
||||
developer.url=http://www.fluig.com
|
||||
application.uiwidget=true
|
||||
application.mobileapp=false
|
||||
application.version=${build.version}-${build.revision}
|
||||
view.file=view.ftl
|
||||
locale.file.base.name=wddesligamentoranking
|
||||
application.resource.js.1=/resources/js/wddesligamentoranking.js
|
||||
application.resource.css.2=/resources/css/wddesligamentoranking.css
|
||||
@@ -0,0 +1,52 @@
|
||||
<script src="/webdesk/vcXMLRPC.js"></script>
|
||||
|
||||
<div id="WdRanking_${instanceId}" class="super-widget wcm-widget-class fluig-style-guide wdr-widget" data-params="WdRankingWidget.instance({ instanceId: '${instanceId}' })">
|
||||
<div class="wdr-shell">
|
||||
|
||||
<div class="wdr-header">
|
||||
<div>
|
||||
<p class="wdr-eyebrow">Gestão do Desligamento</p>
|
||||
<h2 class="wdr-title">Ranking de Solicitações</h2>
|
||||
</div>
|
||||
<div class="wdr-actions">
|
||||
<div class="wdr-period-toggle" role="group">
|
||||
<button type="button" class="wdr-period-btn wdr-period-btn--active" data-period="all">Todos</button>
|
||||
<button type="button" class="wdr-period-btn" data-period="year">Este ano</button>
|
||||
<button type="button" class="wdr-period-btn" data-period="month">Este mês</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-default wdr-refresh-btn" data-refresh>
|
||||
<span class="fluigicon fluigicon-refresh icon-sm" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wdr-kpis" data-kpis>
|
||||
<div class="wdr-kpi">
|
||||
<span class="wdr-kpi__label">Solicitantes únicos</span>
|
||||
<strong class="wdr-kpi__value" data-kpi-requesters>—</strong>
|
||||
</div>
|
||||
<div class="wdr-kpi wdr-kpi--highlight">
|
||||
<span class="wdr-kpi__label">Maior volume</span>
|
||||
<strong class="wdr-kpi__value" data-kpi-top-name>—</strong>
|
||||
<span class="wdr-kpi__sub" data-kpi-top-count></span>
|
||||
</div>
|
||||
<div class="wdr-kpi">
|
||||
<span class="wdr-kpi__label">Total de processos</span>
|
||||
<strong class="wdr-kpi__value" data-kpi-total>—</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wdr-state wdr-state--loading" data-loading>
|
||||
<span class="wdr-spinner" aria-hidden="true"></span>
|
||||
Carregando dados…
|
||||
</div>
|
||||
<div class="wdr-state wdr-state--empty" data-empty style="display:none">
|
||||
Nenhum dado encontrado.
|
||||
</div>
|
||||
<div class="wdr-state wdr-state--error" data-error style="display:none"></div>
|
||||
|
||||
<div class="wdr-ranking" data-ranking></div>
|
||||
<div class="wdr-pagination" data-rank-pagination></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jboss-web>
|
||||
<context-root>/wddesligamentoranking</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>
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/* ================================================================
|
||||
wddesligamentoranking.css — Ranking de Solicitantes
|
||||
================================================================ */
|
||||
|
||||
.wdr-widget {
|
||||
color: #0f172a;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.wdr-widget * { box-sizing: border-box; }
|
||||
|
||||
.wdr-shell {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* ── Header ───────────────────────────────────────────────────── */
|
||||
.wdr-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.wdr-eyebrow {
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
margin: 0 0 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wdr-title {
|
||||
color: #0f172a;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wdr-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.wdr-refresh-btn {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
/* ── Period toggle ────────────────────────────────────────────── */
|
||||
.wdr-period-toggle {
|
||||
background: #e2e8f0;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.wdr-period-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 4px 10px;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.wdr-period-btn--active {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
/* ── KPIs ─────────────────────────────────────────────────────── */
|
||||
.wdr-kpis {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.wdr-kpi {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.wdr-kpi--highlight {
|
||||
background: #eff6ff;
|
||||
border-color: #bfdbfe;
|
||||
}
|
||||
|
||||
.wdr-kpi__label {
|
||||
color: #64748b;
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wdr-kpi__value {
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wdr-kpi--highlight .wdr-kpi__value { color: #1d4ed8; font-size: 16px; }
|
||||
|
||||
.wdr-kpi__sub {
|
||||
color: #64748b;
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ── State ────────────────────────────────────────────────────── */
|
||||
.wdr-state {
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.wdr-state--error {
|
||||
background: #fff1f2;
|
||||
border-color: #fecdd3;
|
||||
color: #9f1239;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@keyframes wdr-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.wdr-spinner {
|
||||
animation: wdr-spin 0.75s linear infinite;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 50%;
|
||||
border-top-color: #2563eb;
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
/* ── Ranking list ─────────────────────────────────────────────── */
|
||||
.wdr-ranking {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.wdr-row {
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
transition: box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.wdr-row:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* podium styling for top 3 */
|
||||
.wdr-row:nth-child(1) { border-left: 4px solid #f59e0b; }
|
||||
.wdr-row:nth-child(2) { border-left: 4px solid #94a3b8; }
|
||||
.wdr-row:nth-child(3) { border-left: 4px solid #b45309; }
|
||||
|
||||
.wdr-rank {
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.wdr-row:nth-child(1) .wdr-rank { color: #d97706; font-size: 16px; }
|
||||
.wdr-row:nth-child(2) .wdr-rank { color: #64748b; font-size: 15px; }
|
||||
.wdr-row:nth-child(3) .wdr-rank { color: #92400e; font-size: 14px; }
|
||||
|
||||
.wdr-avatar {
|
||||
align-items: center;
|
||||
background: #e2e8f0;
|
||||
border-radius: 50%;
|
||||
color: #475569;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
height: 34px;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wdr-row:nth-child(1) .wdr-avatar { background: #fef3c7; color: #b45309; }
|
||||
.wdr-row:nth-child(2) .wdr-avatar { background: #f1f5f9; color: #334155; }
|
||||
.wdr-row:nth-child(3) .wdr-avatar { background: #fef3c7; color: #78350f; }
|
||||
|
||||
.wdr-info {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wdr-name {
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wdr-dept {
|
||||
color: #64748b;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wdr-bar-wrap {
|
||||
flex: 0 0 160px;
|
||||
}
|
||||
|
||||
.wdr-bar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 999px;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wdr-bar-fill {
|
||||
background: linear-gradient(90deg, #3b82f6, #06b6d4);
|
||||
border-radius: 999px;
|
||||
height: 100%;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.wdr-row:nth-child(1) .wdr-bar-fill { background: linear-gradient(90deg, #f59e0b, #ef4444); }
|
||||
|
||||
.wdr-count {
|
||||
color: #0f172a;
|
||||
flex: 0 0 auto;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
min-width: 32px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.wdr-count small {
|
||||
color: #64748b;
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Breakdown badges */
|
||||
.wdr-breakdown {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.wdr-badge {
|
||||
border-radius: 5px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wdr-badge--progress { background: #dbeafe; color: #1d4ed8; }
|
||||
.wdr-badge--attention { background: #fef3c7; color: #92400e; }
|
||||
.wdr-badge--done { background: #dcfce7; color: #15803d; }
|
||||
.wdr-badge--canceled { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
/* ── Responsive ───────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.wdr-kpis { grid-template-columns: repeat(2, 1fr); }
|
||||
.wdr-bar-wrap { display: none; }
|
||||
.wdr-breakdown { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wdr-kpis { grid-template-columns: 1fr; }
|
||||
.wdr-header { flex-wrap: wrap; }
|
||||
}
|
||||
|
||||
/* ── Pagination ───────────────────────────────────────────────── */
|
||||
.wdr-pagination { margin-top: 4px; }
|
||||
|
||||
.wdr-pag {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
padding: 8px 0 2px;
|
||||
}
|
||||
|
||||
.wdr-pag__info {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.wdr-pag__pages {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.wdr-pag__btn {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
height: 28px;
|
||||
min-width: 28px;
|
||||
padding: 0 6px;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
}
|
||||
|
||||
.wdr-pag__btn:hover { background: #f8fafc; border-color: #94a3b8; }
|
||||
|
||||
.wdr-pag__btn--active {
|
||||
background: #2563eb;
|
||||
border-color: #2563eb;
|
||||
color: #ffffff;
|
||||
}
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
var WdRankingWidget = SuperWidget.extend({
|
||||
instanceId: null,
|
||||
|
||||
processId: "desligamentoColaborador",
|
||||
requestPageSize: 1000,
|
||||
formDatasetCandidates: ["desligamentoColaborador", "Desligamento", "desligamento"],
|
||||
|
||||
currentPeriod: "all",
|
||||
pageSize: 5, currentPage: 1,
|
||||
allRequests: [],
|
||||
|
||||
init: function() {
|
||||
this.$root = $("#WdRanking_" + this.instanceId);
|
||||
this.$ranking = this.$root.find("[data-ranking]");
|
||||
this.$loading = this.$root.find("[data-loading]");
|
||||
this.$empty = this.$root.find("[data-empty]");
|
||||
this.$error = this.$root.find("[data-error]");
|
||||
this.$pagination = this.$root.find("[data-rank-pagination]");
|
||||
|
||||
this.bindUi();
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
bindings: { local: {}, global: {} },
|
||||
|
||||
bindUi: function() {
|
||||
var self = this;
|
||||
|
||||
this.$root.find("[data-refresh]").on("click", function() {
|
||||
self.loadData();
|
||||
});
|
||||
|
||||
this.$root.on("click", "[data-period]", function() {
|
||||
self.currentPeriod = $(this).data("period");
|
||||
self.$root.find("[data-period]").removeClass("wdr-period-btn--active");
|
||||
$(this).addClass("wdr-period-btn--active");
|
||||
self.render();
|
||||
});
|
||||
|
||||
this.$root.on("click", "[data-rank-page]", function() {
|
||||
self.currentPage = parseInt($(this).data("rank-page"), 10) || 1;
|
||||
self.render();
|
||||
});
|
||||
},
|
||||
|
||||
/* ── Data loading ─────────────────────────────────────────── */
|
||||
|
||||
loadData: function() {
|
||||
this.showLoading();
|
||||
this.loadFromProcessApi();
|
||||
},
|
||||
|
||||
loadFromProcessApi: function() {
|
||||
var self = this;
|
||||
|
||||
/* /requests/tasks returns one row per movement per process.
|
||||
Each row has requester (who opened) + assignee (who owns this step).
|
||||
We deduplicate by processInstanceId keeping the best representative task. */
|
||||
$.ajax({
|
||||
url: "/process-management/api/v2/processes/" + encodeURIComponent(this.processId) +
|
||||
"/requests/tasks?page=1&pageSize=2000",
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
headers: { Accept: "application/json" }
|
||||
})
|
||||
.done(function(response) {
|
||||
var tasks = self.extractRows(response);
|
||||
if (!tasks.length) { self.loadFromDatasets(); return; }
|
||||
|
||||
/* deduplicate: prefer NOT_COMPLETED task; within same status, prefer highest movementSequence */
|
||||
var byProcess = {};
|
||||
for (var i = 0; i < tasks.length; i++) {
|
||||
var t = tasks[i];
|
||||
var pid = String(t.processInstanceId || "");
|
||||
if (!pid) { continue; }
|
||||
|
||||
var existing = byProcess[pid];
|
||||
var tSeq = t.movementSequence || 0;
|
||||
var tPending = t.status === "NOT_COMPLETED";
|
||||
|
||||
if (!existing) {
|
||||
byProcess[pid] = t;
|
||||
} else {
|
||||
var eSeq = existing.movementSequence || 0;
|
||||
var ePending = existing.status === "NOT_COMPLETED";
|
||||
if (tPending && !ePending) { byProcess[pid] = t; }
|
||||
else if (!tPending && ePending) { /* keep existing */ }
|
||||
else if (tSeq > eSeq) { byProcess[pid] = t; }
|
||||
}
|
||||
}
|
||||
|
||||
var rows = [];
|
||||
for (var k in byProcess) {
|
||||
if (byProcess.hasOwnProperty(k)) { rows.push(byProcess[k]); }
|
||||
}
|
||||
|
||||
self.allRequests = rows.map(function(r) { return self.buildRequest(r); });
|
||||
self.render();
|
||||
})
|
||||
.fail(function() { self.loadFromDatasets(); });
|
||||
},
|
||||
|
||||
loadFromDatasets: function() {
|
||||
var self = this;
|
||||
this.ensureDatasetFactory(function() {
|
||||
try {
|
||||
var candidates = self.formDatasetCandidates;
|
||||
var rows = [];
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
try {
|
||||
var ds = DatasetFactory.getDataset(candidates[i], null, null, null);
|
||||
if (ds && ds.values && ds.values.length) {
|
||||
rows = ds.values;
|
||||
break;
|
||||
}
|
||||
} catch (e) { /* try next */ }
|
||||
}
|
||||
|
||||
self.allRequests = rows.map(function(r) { return self.buildRequest(r); });
|
||||
self.render();
|
||||
} catch (e) {
|
||||
self.showError("Não foi possível carregar os dados. " + e.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
ensureDatasetFactory: function(callback) {
|
||||
if (window.DatasetFactory) { callback(); return; }
|
||||
$.getScript("/webdesk/vcXMLRPC.js")
|
||||
.done(callback)
|
||||
.fail(function() { /* DatasetFactory unavailable */ });
|
||||
},
|
||||
|
||||
extractRows: function(response) {
|
||||
if (!response) { return []; }
|
||||
if ($.isArray(response)) { return response; }
|
||||
var keys = ["content", "items", "requests", "data", "values", "records"];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var v = response[keys[i]];
|
||||
if ($.isArray(v)) { return v; }
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
buildRequest: function(row) {
|
||||
/* /requests/tasks structure:
|
||||
row.requester = who OPENED the process (constant on every movement)
|
||||
row.assignee = who owns THIS task step (changes each movement)
|
||||
We always use requester for ranking attribution. */
|
||||
var name = "";
|
||||
|
||||
if (row.requester) {
|
||||
var rq = row.requester;
|
||||
if (typeof rq === "string") {
|
||||
name = rq;
|
||||
} else if (rq && typeof rq === "object") {
|
||||
name = rq.name || rq.colleagueName || rq.login || rq.code || "";
|
||||
}
|
||||
}
|
||||
|
||||
/* plain-field fallback (DatasetFactory / other API shapes) */
|
||||
if (!name) {
|
||||
name = this.readFirst(row, [
|
||||
"requesterName", "requesterId", "colleagueName", "solicitante", "WKCreatedBy"
|
||||
]);
|
||||
}
|
||||
|
||||
/* last resort: assignee, but skip pool/group entries */
|
||||
if (!name && row.assignee) {
|
||||
var ag = row.assignee;
|
||||
var agCode = String((ag && ag.code) || "");
|
||||
if (agCode.indexOf("Pool:") < 0 && agCode.indexOf("Group:") < 0) {
|
||||
name = (ag.name || ag.login || agCode) || "";
|
||||
}
|
||||
}
|
||||
|
||||
/* department: try form field, then requester.local */
|
||||
var dept = this.readFirst(row, ["departamento", "department", "area"]);
|
||||
if (!dept && row.requester && row.requester.local) { dept = row.requester.local; }
|
||||
|
||||
var status = this.resolveStatus(row);
|
||||
var startDateRaw = this.readFirst(row, ["startDate", "dataAbertura", "createDate", "createdAt"]);
|
||||
|
||||
return {
|
||||
requesterName: name || "Desconhecido",
|
||||
department: dept || "-",
|
||||
status: status,
|
||||
startDate: this.parseDate(startDateRaw)
|
||||
};
|
||||
},
|
||||
|
||||
resolveStatus: function(row) {
|
||||
/* /requests/tasks primary path */
|
||||
var taskStatus = String(row.status || "").toUpperCase();
|
||||
if (taskStatus === "NOT_COMPLETED") {
|
||||
var sla = String(row.slaStatus || "").toUpperCase();
|
||||
if (sla === "OVERDUE") { return "attention"; }
|
||||
return "progress";
|
||||
}
|
||||
if (taskStatus === "COMPLETED") { return "done"; }
|
||||
|
||||
/* form-field fallback (DatasetFactory / ECM dataset shape) */
|
||||
var approval = String(this.readFirst(row, ["DesligamentoAprov", "approval"]) || "").toLowerCase().trim();
|
||||
if (approval === "nao") { return "canceled"; }
|
||||
|
||||
var activeRaw = this.readFirst(row, ["active", "processActive"]);
|
||||
if (activeRaw === false || String(activeRaw === null ? "" : activeRaw).toLowerCase() === "false") {
|
||||
return "done";
|
||||
}
|
||||
|
||||
var hasImpediment = String(this.readFirst(row, ["impeditivosColab", "impediment"]) || "").toLowerCase();
|
||||
if (approval === "maisinformacoes" || hasImpediment === "sim") { return "attention"; }
|
||||
|
||||
return "progress";
|
||||
},
|
||||
|
||||
parseDate: function(value) {
|
||||
if (!value) { return null; }
|
||||
var s = String(value);
|
||||
var m1 = s.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
|
||||
if (m1) { return new Date(+m1[3], +m1[2] - 1, +m1[1]); }
|
||||
var m2 = s.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (m2) { return new Date(+m2[1], +m2[2] - 1, +m2[3]); }
|
||||
var d = new Date(s);
|
||||
return isNaN(d.getTime()) ? null : d;
|
||||
},
|
||||
|
||||
readFirst: function(row, keys) {
|
||||
if (!row) { return ""; }
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var v = row[keys[i]];
|
||||
if (v !== undefined && v !== null && v !== "") { return v; }
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
/* ── Filtering by period ──────────────────────────────────── */
|
||||
|
||||
filterByPeriod: function(requests) {
|
||||
if (this.currentPeriod === "all") { return requests; }
|
||||
|
||||
var now = new Date();
|
||||
var year = now.getFullYear();
|
||||
var month = now.getMonth();
|
||||
|
||||
return requests.filter(function(r) {
|
||||
if (!r.startDate) { return true; }
|
||||
if (this.currentPeriod === "year") { return r.startDate.getFullYear() === year; }
|
||||
if (this.currentPeriod === "month") {
|
||||
return r.startDate.getFullYear() === year && r.startDate.getMonth() === month;
|
||||
}
|
||||
return true;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/* ── Render ───────────────────────────────────────────────── */
|
||||
|
||||
render: function() {
|
||||
this.$loading.hide();
|
||||
this.$error.hide();
|
||||
|
||||
var requests = this.filterByPeriod(this.allRequests);
|
||||
|
||||
if (!requests.length) {
|
||||
this.$ranking.empty();
|
||||
this.$empty.show();
|
||||
this.$pagination.html("");
|
||||
this.updateKpis([], []);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$empty.hide();
|
||||
|
||||
/* group by requester */
|
||||
var map = {};
|
||||
for (var i = 0; i < requests.length; i++) {
|
||||
var req = requests[i];
|
||||
var name = req.requesterName;
|
||||
if (!map[name]) {
|
||||
map[name] = {
|
||||
name: name, department: req.department,
|
||||
total: 0, progress: 0, attention: 0, done: 0, canceled: 0
|
||||
};
|
||||
}
|
||||
map[name].total++;
|
||||
map[name][req.status] = (map[name][req.status] || 0) + 1;
|
||||
}
|
||||
|
||||
/* sort desc by total */
|
||||
var ranked = [];
|
||||
for (var k in map) {
|
||||
if (map.hasOwnProperty(k)) { ranked.push(map[k]); }
|
||||
}
|
||||
ranked.sort(function(a, b) { return b.total - a.total; });
|
||||
|
||||
this.updateKpis(ranked, requests);
|
||||
|
||||
/* paginate: show pageSize entries per page */
|
||||
var totalPages = Math.ceil(ranked.length / this.pageSize);
|
||||
if (this.currentPage > totalPages) { this.currentPage = 1; }
|
||||
var start = (this.currentPage - 1) * this.pageSize;
|
||||
var paginated = ranked.slice(start, start + this.pageSize);
|
||||
var maxCount = ranked[0] ? ranked[0].total : 1;
|
||||
|
||||
var html = "";
|
||||
for (var j = 0; j < paginated.length; j++) {
|
||||
html += this.renderRow(paginated[j], start + j + 1, maxCount);
|
||||
}
|
||||
this.$ranking.html(html);
|
||||
this.renderPagination(ranked.length, totalPages);
|
||||
},
|
||||
|
||||
renderPagination: function(total, totalPages) {
|
||||
if (totalPages <= 1) { this.$pagination.html(""); return; }
|
||||
|
||||
var cur = this.currentPage;
|
||||
var html = '<div class="wdr-pag">';
|
||||
html += '<span class="wdr-pag__info">' + total + ' solicitantes</span>';
|
||||
html += '<div class="wdr-pag__pages">';
|
||||
|
||||
for (var p = 1; p <= totalPages; p++) {
|
||||
var cls = p === cur ? " wdr-pag__btn--active" : "";
|
||||
html += '<button type="button" class="wdr-pag__btn' + cls + '" data-rank-page="' + p + '">' + p + '</button>';
|
||||
}
|
||||
html += '</div></div>';
|
||||
this.$pagination.html(html);
|
||||
},
|
||||
|
||||
updateKpis: function(ranked, requests) {
|
||||
this.$root.find("[data-kpi-requesters]").text(ranked.length || "—");
|
||||
this.$root.find("[data-kpi-total]").text(requests.length || "—");
|
||||
|
||||
if (ranked.length) {
|
||||
this.$root.find("[data-kpi-top-name]").text(ranked[0].name);
|
||||
this.$root.find("[data-kpi-top-count]").text(ranked[0].total + " solicitação(ões)");
|
||||
} else {
|
||||
this.$root.find("[data-kpi-top-name]").text("—");
|
||||
this.$root.find("[data-kpi-top-count]").text("");
|
||||
}
|
||||
},
|
||||
|
||||
renderRow: function(entry, position, maxCount) {
|
||||
var barPct = Math.round((entry.total / maxCount) * 100);
|
||||
var initials = entry.name.split(" ").slice(0, 2).map(function(w) { return w[0]; }).join("");
|
||||
var medal = position === 1 ? "🥇" : position === 2 ? "🥈" : position === 3 ? "🥉" : position;
|
||||
|
||||
var breakdown = "";
|
||||
if (entry.progress) { breakdown += '<span class="wdr-badge wdr-badge--progress">' + entry.progress + ' andamento</span>'; }
|
||||
if (entry.attention) { breakdown += '<span class="wdr-badge wdr-badge--attention">' + entry.attention + ' atenção</span>'; }
|
||||
if (entry.done) { breakdown += '<span class="wdr-badge wdr-badge--done">' + entry.done + ' concluído</span>'; }
|
||||
if (entry.canceled) { breakdown += '<span class="wdr-badge wdr-badge--canceled">' + entry.canceled + ' cancelado</span>'; }
|
||||
|
||||
return [
|
||||
'<div class="wdr-row">',
|
||||
'<span class="wdr-rank">' + medal + '</span>',
|
||||
'<div class="wdr-avatar">' + this.escapeHtml(initials) + '</div>',
|
||||
'<div class="wdr-info">',
|
||||
'<strong class="wdr-name">' + this.escapeHtml(entry.name) + '</strong>',
|
||||
'<span class="wdr-dept">' + this.escapeHtml(entry.department) + '</span>',
|
||||
'</div>',
|
||||
'<div class="wdr-breakdown">' + breakdown + '</div>',
|
||||
'<div class="wdr-bar-wrap">',
|
||||
'<div class="wdr-bar-track"><div class="wdr-bar-fill" style="width:' + barPct + '%"></div></div>',
|
||||
'</div>',
|
||||
'<div class="wdr-count">' + entry.total + '<small>solicit.</small></div>',
|
||||
'</div>'
|
||||
].join("");
|
||||
},
|
||||
|
||||
/* ── State helpers ────────────────────────────────────────── */
|
||||
|
||||
showLoading: function() {
|
||||
this.$error.hide();
|
||||
this.$empty.hide();
|
||||
this.$ranking.empty();
|
||||
this.$loading.show();
|
||||
},
|
||||
|
||||
showError: function(msg) {
|
||||
this.$loading.hide();
|
||||
this.$ranking.empty();
|
||||
this.$error.text(msg || "Erro ao carregar dados.").show();
|
||||
},
|
||||
|
||||
escapeHtml: function(value) {
|
||||
return String(value === undefined || value === null ? "" : value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user