A real script, and the card it draws
This is the script behind the Spend under contract card on a Vendor HUB dashboard, as it is stored. It adds up the value of the active contracts, counts them and the ones ending soon, and builds the card as HTML. The last expression is the output.
ConfigureThe script, in the Designer
Lines 1 to 33: helper functions shared by the cards on this dashboard
var C = {ink:'#151b2b', sub:'#3f4a5c', muted:'#6b7385', faint:'#9aa1af', line:'#eef0f3', track:'#f1f3f6', blue:'#3358d4', ok:'#17803d', warn:'#b5600b', bad:'#c02626'};var T = {bad:'#fcefef', warn:'#fdf4e7', blue:'#eef2fd', slate:'#f2f4f7'};function num(x) { var v = parseFloat(x); return isNaN(v) ? 0 : v; }function cnt(t, w) { var v = parseInt(FunctionManager.evaluateCount(t, w) || '0', 10); return isNaN(v) ? 0 : v; }function sum(f, t, w) { return num(FunctionManager.evaluateSumFunction(f, t, w)); }function money(v) { v = num(v); if (v >= 1000000) { return 'EGP ' + (v / 1000000).toFixed(v >= 10000000 ? 1 : 2) + 'M'; } if (v >= 1000) { return 'EGP ' + Math.round(v / 1000) + 'K'; } return 'EGP ' + Math.round(v); }function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); }function cut(s, n) { s = String(s == null ? '' : s); return s.length > n ? s.substring(0, n - 1) + '…' : s; }function fv(rec, f) { var x = rec.getFieldValueMap().get(f); var v = x ? x.getCurrentValue() : null; return (v === null || v === undefined) ? '' : String(v); }var _lk = {};function lab(t, id) { if (!id) { return ''; } id = String(id).replace(/\.0+$/, ''); var k = t + '|' + id; if (_lk[k] === undefined) { var r = null; try { r = FunctionManager.resolveLookupIdValue(t, id); } catch (e) { r = null; } _lk[k] = r ? String(r) : ''; } return _lk[k]; }var _vn = {};function vendor(id) { if (!id || id === '0') { return ''; } if (_vn[id] === undefined) { var n = ''; try { n = FunctionManager.loadValueByClause('trade_name', 'cm_vendor_base', 'WHERE cm_vendor_base.vendor_pr_id = ' + parseInt(id, 10)); } catch (e) { n = ''; } _vn[id] = n ? String(n) : ''; } return _vn[id]; }function parseDate(str) { if (!str) { return null; } var s = String(str).trim(); if (/^\d+$/.test(s)) { return new Date(parseInt(s, 10)); } var p = s.split('T')[0].split(' ')[0].split('-'); if (p.length === 3) { return new Date(parseInt(p[0], 10), parseInt(p[1], 10) - 1, parseInt(p[2], 10)); } var q = s.split(' ')[0].split('/'); if (q.length === 3) { return new Date(parseInt(q[2], 10), parseInt(q[1], 10) - 1, parseInt(q[0], 10)); } return null; }function daysFromToday(str) { var d = parseDate(str); if (!d) { return null; } var t = new Date(); t = new Date(t.getFullYear(), t.getMonth(), t.getDate()); return Math.round((d.getTime() - t.getTime()) / 86400000); }var MON = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];function dshort(str) { var d = parseDate(str); return d ? d.getDate() + ' ' + MON[d.getMonth()] : ''; }function icon(name, color) { var p = { doc: "<path d='M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z'/><path d='M14 3v5h5'/>", pen: "<path d='M12 20h9'/><path d='M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z'/>", alert: "<path d='M12 3 2 20h20z'/><path d='M12 10v4M12 17h.01'/>", clock: "<circle cx='12' cy='12' r='9'/><path d='M12 7v5l3 2'/>", box: "<path d='M21 8 12 3 3 8v8l9 5 9-5z'/><path d='M3 8l9 5 9-5M12 13v8'/>", scale: "<path d='M12 3v18M5 21h14M3 8l4-5 4 5M13 8l4-5 4 5'/><path d='M3 8a4 4 0 0 0 8 0M13 8a4 4 0 0 0 8 0'/>", users: "<path d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/><circle cx='9' cy='7' r='4'/>", check: "<path d='M20 6 9 17l-5-5'/>" }[name] || ''; return "<svg width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='" + color + "' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'>" + p + "</svg>";}function tile(name, bg, color) { return "<div style='width:30px;height:30px;border-radius:8px;background:" + bg + ";display:flex;align-items:center;justify-content:center'>" + icon(name, color) + "</div>"; }function status(text, tone) { return "<span style='display:inline-flex;align-items:center;gap:8px;font-size:12.5px;color:" + (C[tone] || C.sub) + "'><span style='width:1px;height:12px;background:#d8dde5'></span>" + esc(text) + "</span>"; }function meter(parts) { var t = 0; parts.forEach(function (p) { t += p[0]; }); if (t <= 0) { return "<div style='height:4px;border-radius:2px;background:" + C.track + "'></div>"; } var h = "<div style='display:flex;gap:2px;height:4px'>"; parts.forEach(function (p) { if (p[0] > 0) { h += "<div style='flex:" + p[0] + ";background:" + p[1] + ";border-radius:2px'></div>"; } }); return h + "</div>"; }
var act = "WHERE cm_contract_base.status IN ('2','8')";var soon = act + " AND cm_contract_base.end_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 30 DAY)";var value = sum('contract_value', 'cm_contract_base', act);var n = cnt('cm_contract_base', act);var ending = cnt('cm_contract_base', soon);var endingV = sum('contract_value', 'cm_contract_base', soon);var cap = n + ' active contracts' + (ending > 0 ? " · <span style='color:" + C.warn + "'>" + ending + ' ends in 30 days</span>' : ''); var html = "<div style='display:flex;flex-direction:column;gap:10px;font-variant-numeric:tabular-nums;padding-top:4px'>" + "<div style='font-size:31px;font-weight:600;color:" + C.ink + ";letter-spacing:-0.03em;line-height:1.05;white-space:nowrap'>" + (money(value)) + "</div>" + "<div style='font-size:12.5px;color:" + C.muted + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis'>" + (cap) + "</div>" + "<div style='margin-top:4px'>" + (meter([[value - endingV, C.blue], [endingV, "#e6a456"]])) + "</div>" + "</div>";html
SeeThe card, in the application

- Lines 35 and 36Which contracts. A filter you write: contracts in the active statuses, and those whose end date falls in the next 30 days.
- Lines 37 to 41What to read. sum and cnt, two helpers at the top, call FunctionManager.evaluateSumFunction and evaluateCount on your own module.
- Lines 43 to 48The card. The HTML is built as a string, and the last expression, html, is what the dashboard shows.








