7 Commits
Author SHA1 Message Date
siaupheng 11e17bd978 Refactor multiple JavaScript classes to use ES6 syntax, including Download, Pdf, Printer, PrinterPos, PoleDisplay, and Simulator. Update method names for consistency and improve error handling. Modify AJAX requests to enhance functionality and user experience. 2026-11-02 16:10:36 +07:00
siaupheng 929f3af79a Refactor PoleDisplay class and update Auth user_log method
- Converted PoleDisplay from a function-based structure to a class-based structure for improved readability and maintainability.
- Simplified the Open and Close methods to handle port state more clearly.
- Enhanced the Send method to support both string and array inputs for sending data.
- Updated the cd_Text method to use modern JavaScript features for padding calculations.
- Modified the user_log method in Auth class to streamline database fetch and update logic.
2026-11-01 15:55:28 +07:00
siaupheng cf100a86e2 update last modified date and refactor HTTP host retrieval in Menu class 2026-10-30 16:47:21 +07:00
siaupheng a538cc0cc5 update dependencies in composer.json for compatibility 2026-10-29 13:08:14 +07:00
siaupheng 4c8399cf64 add gen_uuidv4 and gen_uuidv7 2026-10-29 12:21:42 +07:00
siaupheng 2be02c36ec add hide header to xlsx and printhtml 2026-10-25 12:55:43 +07:00
siaupheng 0a47ddc422 corrected same function 2026-10-21 16:13:30 +07:00
15 changed files with 1653 additions and 1712 deletions
+261 -375
View File
@@ -1,40 +1,8 @@
var __extends = (undefined && undefined.__extends) || (function () { var AjaxResolver = class {
var extendStatics = function (d, b) { constructor(options) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var BaseResolver = (function () {
function BaseResolver(options) {
this._settings = $.extend(true, {}, this.getDefaults(), options); this._settings = $.extend(true, {}, this.getDefaults(), options);
} }
BaseResolver.prototype.getDefaults = function () { getDefaults() {
return {};
};
BaseResolver.prototype.getResults = function (limit, start, end) {
return this.results;
};
BaseResolver.prototype.search = function (q, cbk) {
cbk(this.getResults());
};
return BaseResolver;
}());
var AjaxResolver = (function (_super) {
__extends(AjaxResolver, _super);
function AjaxResolver(options) {
return _super.call(this, options) || this;
}
AjaxResolver.prototype.getDefaults = function () {
return { return {
url: '', url: '',
method: 'post', method: 'post',
@@ -43,42 +11,42 @@ var AjaxResolver = (function (_super) {
timeout: 0, timeout: 0,
requestThrottling: 500 requestThrottling: 500
}; };
}; }
AjaxResolver.prototype.search = function (q, cbk) { search(q, cbk) {
var _this = this; if (this.jqXHR != null) this.jqXHR.abort();
if (this.jqXHR != null) {
this.jqXHR.abort();
}
var data = {}; var data = {};
data[this._settings.queryKey] = q; data[this._settings.queryKey] = q;
if (this._settings.param.length > 0) $.extend(data, FoniaJS.RequestParam(this._settings.param)); if (this._settings.param.length > 0) {
if (this.requestTID) { $.extend(data, FoniaJS.RequestParam(this._settings.param));
window.clearTimeout(this.requestTID);
} }
this.requestTID = window.setTimeout(function () {
_this.jqXHR = $.ajax(_this._settings.url, { if (this.requestTID) window.clearTimeout(this.requestTID);
method: _this._settings.method,
this.requestTID = window.setTimeout(() => {
this.jqXHR = $.ajax(this._settings.url, {
method: this._settings.method,
data: data, data: data,
timeout: _this._settings.timeout timeout: this._settings.timeout
}); });
_this.jqXHR.done(function (result) {
//gnips: result in object this.jqXHR.done(result => {
cbk((undefined !== result.autodata) ? result.autodata : result); cbk(result.autodata !== undefined ? result.autodata : result);
}); });
_this.jqXHR.fail(function (err) {
var _a; this.jqXHR.fail(err => {
(_a = _this._settings) === null || _a === void 0 ? void 0 : _a.fail(err); this._settings.fail?.(err);
}); });
_this.jqXHR.always(function () {
_this.jqXHR = null; this.jqXHR.always(() => {
this.jqXHR = null;
}); });
}, this._settings.requestThrottling); }, this._settings.requestThrottling);
}; }
return AjaxResolver; };
}(BaseResolver));
var DropdownV5 = (function () { var DropdownV5 = class {
function DropdownV5(e, formatItemCbk, autoSelect, noResultsText) { constructor(e, formatItemCbk, autoSelect, noResultsText) {
this.initialized = false; this.initialized = false;
this.shown = false; this.shown = false;
this.items = []; this.items = [];
@@ -88,195 +56,160 @@ var DropdownV5 = (function () {
this.autoSelect = autoSelect; this.autoSelect = autoSelect;
this.noResultsText = noResultsText; this.noResultsText = noResultsText;
} }
DropdownV5.prototype.getElPos = function () {
var pos = $.extend({}, this._$el.position(), { getElPos() {
height: this._$el[0].offsetHeight return $.extend({}, this._$el.position(), { height: this._$el[0].offsetHeight });
}); }
return pos;
}; init() {
DropdownV5.prototype.init = function () {
var _this = this;
var pos = this.getElPos(); var pos = this.getElPos();
var fontsize = _this._$el.css('font-size'); var fontsize = this._$el.css('font-size');
this._dd = $('<div />');
this._dd.addClass('bootstrap-autocomplete dropdown-menu'); this._dd = $('<div />')
this._dd.insertAfter(this._$el); .addClass('bootstrap-autocomplete dropdown-menu')
this._dd.css({ top: pos.top + this._$el.outerHeight(), left: pos.left, width: this._$el.outerWidth(), 'font-size': fontsize }); .insertAfter(this._$el)
this._dd.on('click', '.dropdown-item', function (evt) { .css({
top: pos.top + this._$el.outerHeight(),
left: pos.left,
width: this._$el.outerWidth(),
'font-size': fontsize
});
this._dd.on('click', '.dropdown-item', evt => {
var item = $(evt.currentTarget).data('item'); var item = $(evt.currentTarget).data('item');
_this.itemSelectedLaunchEvent(item); this.itemSelectedLaunchEvent(item);
evt.preventDefault(); evt.preventDefault();
}); });
this._dd.on('keyup', function (evt) {
if (_this.shown) { this._dd.on('keyup', evt => {
switch (evt.which) { if (this.shown && evt.which === 27) {
case 27: this.hide();
_this.hide(); this._$el.focus();
_this._$el.focus();
break;
}
return false; return false;
} }
}); });
this._dd.on('mouseenter', function (evt) {
_this.ddMouseover = true; this._dd.on('mouseenter', () => { this.ddMouseover = true; });
}); this._dd.on('mouseleave', () => { this.ddMouseover = false; });
this._dd.on('mouseleave', function (evt) {
_this.ddMouseover = false; this._dd.on('mouseenter', '.dropdown-item', evt => {
}); if (this.haveResults) {
this._dd.on('mouseenter', '.dropdown-item', function (evt) {
if (_this.haveResults) {
$(evt.currentTarget).closest('div').find('.dropdown-item.active').removeClass('active'); $(evt.currentTarget).closest('div').find('.dropdown-item.active').removeClass('active');
$(evt.currentTarget).addClass('active'); $(evt.currentTarget).addClass('active');
_this.mouseover = true; this.mouseover = true;
} }
}); });
this._dd.on('mouseleave', '.dropdown-item', function (evt) {
_this.mouseover = false; this._dd.on('mouseleave', '.dropdown-item', () => { this.mouseover = false; });
});
this.initialized = true; this.initialized = true;
}; }
DropdownV5.prototype.checkInitialized = function () {
if (!this.initialized) { checkInitialized() {
this.init(); if (!this.initialized) this.init();
}
get isMouseOver() { return this.mouseover; }
get isDdMouseOver() { return this.ddMouseover; }
get haveResults() { return this.items.length > 0; }
focusNextItem(reversed) {
if (!this.haveResults) return;
var currElem = this._dd.find('.dropdown-item.active');
var nextElem = reversed ? currElem.prev() : currElem.next();
if (nextElem.length === 0) {
nextElem = reversed ? this._dd.find('.dropdown-item').last() : this._dd.find('.dropdown-item').first();
} }
}; currElem.removeClass('active');
Object.defineProperty(DropdownV5.prototype, "isMouseOver", { nextElem.addClass('active');
get: function () { this.scrollTo(nextElem);
return this.mouseover; }
},
enumerable: false, focusPreviousItem() { this.focusNextItem(true); }
configurable: true selectFocusItem() { this._dd.find('.dropdown-item.active').trigger('click'); }
});
Object.defineProperty(DropdownV5.prototype, "isDdMouseOver", { get isItemFocused() {
get: function () { return !!(this._dd && this.isShown() && this._dd.find('.dropdown-item.active').length > 0);
return this.ddMouseover; }
},
enumerable: false, show() {
configurable: true
});
Object.defineProperty(DropdownV5.prototype, "haveResults", {
get: function () {
return (this.items.length > 0);
},
enumerable: false,
configurable: true
});
DropdownV5.prototype.focusNextItem = function (reversed) {
if (this.haveResults) {
var currElem = this._dd.find('.dropdown-item.active');
var nextElem = reversed ? currElem.prev() : currElem.next();
if (nextElem.length === 0) {
nextElem = reversed ? this._dd.find('.dropdown-item').last() : this._dd.find('.dropdown-item').first();
}
currElem.removeClass('active');
nextElem.addClass('active');
this.scrollTo(nextElem);
}
};
DropdownV5.prototype.focusPreviousItem = function () {
this.focusNextItem(true);
};
DropdownV5.prototype.selectFocusItem = function () {
this._dd.find('.dropdown-item.active').trigger('click');
};
Object.defineProperty(DropdownV5.prototype, "isItemFocused", {
get: function () {
if (this._dd && this.isShown() && (this._dd.find('.dropdown-item.active').length > 0)) {
return true;
}
return false;
},
enumerable: false,
configurable: true
});
DropdownV5.prototype.show = function () {
if (!this.shown) { if (!this.shown) {
this._dd.addClass('show'); this._dd.addClass('show');
this.shown = true; this.shown = true;
this._$el.trigger('autocomplete.dd.shown'); this._$el.trigger('autocomplete.dd.shown');
} }
}; }
DropdownV5.prototype.isShown = function () {
return this.shown; isShown() { return this.shown; }
};
DropdownV5.prototype.hide = function () { hide() {
if (this.shown) { if (this.shown) {
this._dd.removeClass('show'); this._dd.removeClass('show');
this.shown = false; this.shown = false;
this._$el.trigger('autocomplete.dd.hidden'); this._$el.trigger('autocomplete.dd.hidden');
} }
}; }
DropdownV5.prototype.updateItems = function (items, searchText) {
updateItems(items, searchText) {
this.items = items; this.items = items;
this.searchText = searchText; this.searchText = searchText;
this.refreshItemList(); this.refreshItemList();
}; }
DropdownV5.prototype.showMatchedText = function (text, qry) {
showMatchedText(text, qry) {
var startIndex = text.toLowerCase().indexOf(qry.toLowerCase()); var startIndex = text.toLowerCase().indexOf(qry.toLowerCase());
if (startIndex > -1) { if (startIndex > -1) {
var endIndex = startIndex + qry.length; var endIndex = startIndex + qry.length;
return text.slice(0, startIndex) + '<b>' return text.slice(0, startIndex) + '<b>' + text.slice(startIndex, endIndex) + '</b>' + text.slice(endIndex);
+ text.slice(startIndex, endIndex) + '</b>'
+ text.slice(endIndex);
} }
return text; return text;
}; }
DropdownV5.prototype.refreshItemList = function () {
var _this = this; refreshItemList() {
this.checkInitialized(); this.checkInitialized();
this._dd.empty(); this._dd.empty();
var liList = []; var liList = [];
if (this.items.length > 0) { if (this.items.length > 0) {
this.items.forEach(function (item) { this.items.forEach(item => {
var itemFormatted = _this.formatItem(item); var itemFormatted = this.formatItem(item);
if (typeof itemFormatted === 'string') { if (typeof itemFormatted === 'string') itemFormatted = { text: itemFormatted };
itemFormatted = { text: itemFormatted };
} var itemText = this.showMatchedText(itemFormatted.text, this.searchText);
var itemText; var itemHtml = itemFormatted.html !== undefined ? itemFormatted.html : itemText;
var itemHtml;
itemText = _this.showMatchedText(itemFormatted.text, _this.searchText); var li = $('<a />')
if (itemFormatted.html !== undefined) { .addClass('dropdown-item')
itemHtml = itemFormatted.html;
} else {
itemHtml = itemText;
}
var disabledItem = itemFormatted.disabled;
var li = $('<a >');
li.addClass('dropdown-item')
//css bisa dimasukkan ke style default + cursor select
//.css({ 'overflow': 'hidden', 'text-overflow': 'ellipsis' })
.html(itemHtml) .html(itemHtml)
.data('item', item); .data('item', item);
if (disabledItem) {
li.addClass('disabled'); if (itemFormatted.disabled) li.addClass('disabled');
}
liList.push(li); liList.push(li);
}); });
this._dd.append(liList); this._dd.append(liList);
this.show(); this.show();
} else if (this.noResultsText !== '') {
var li = $('<a />').addClass('dropdown-item disabled').html(this.noResultsText);
liList.push(li);
this._dd.append(liList);
this.show();
} else {
this.hide();
} }
else { }
if (this.noResultsText === '') {
this.hide(); itemSelectedLaunchEvent(item) {
}
else {
var li = $('<a >');
li.addClass('dropdown-item disabled')
.html(this.noResultsText);
liList.push(li);
this._dd.append(liList);
this.show();
}
}
};
DropdownV5.prototype.itemSelectedLaunchEvent = function (item) {
this._$el.trigger('autocomplete.select', item); this._$el.trigger('autocomplete.select', item);
}; }
DropdownV5.prototype.scrollTo = function (el) {
var _he = this._dd.height(), _to = this._dd.scrollTop(), _ito = el.position().top, _ihe = el.outerHeight(), scrollTo(el) {
var _he = this._dd.height(),
_to = this._dd.scrollTop(),
_ito = el.position().top,
_ihe = el.outerHeight(),
_nto = _to, _wra = false; _nto = _to, _wra = false;
if (_ito < 0){
if (_ito < 0) {
_wra = el.is(':first-child'); _wra = el.is(':first-child');
_nto = _wra ? 0 : _to - _he / 2; _nto = _wra ? 0 : _to - _he / 2;
} else if (_ito > _he - _ihe) { } else if (_ito > _he - _ihe) {
@@ -284,12 +217,11 @@ var DropdownV5 = (function () {
_nto = _wra ? (_ito + _ihe - _he) : _to + _he / 2; _nto = _wra ? (_ito + _ihe - _he) : _to + _he / 2;
} }
this._dd.scrollTop(_nto); this._dd.scrollTop(_nto);
}; }
return DropdownV5; };
}());
var AutoComplete = (function () { var AutoComplete = class {
function AutoComplete(element, options) { constructor(element, options) {
this._selectedItem = null; this._selectedItem = null;
this._defaultValue = null; this._defaultValue = null;
this._defaultText = null; this._defaultText = null;
@@ -302,235 +234,189 @@ var AutoComplete = (function () {
autoSelect: true, autoSelect: true,
noResultsText: 'Pencarian Tidak Ketemu', noResultsText: 'Pencarian Tidak Ketemu',
preventEnter: false, preventEnter: false,
events: { events: { typed: null, searchPre: null, search: null, searchPost: null, select: null, focus: null },
typed: null, selectFunc: () => {},
searchPre: null, keyFunc: () => {},
search: null, breakFocus: false,
searchPost: null,
select: null,
focus: null,
},
//gnips: adding callback, keyfunc and breakvalue
selectFunc: function(){},
keyFunc: function(){},
breakValue: [] breakValue: []
}; };
this._el = element; this._el = element;
this._$el = $(this._el); this._$el = $(this._el);
this.manageInlineDataAttributes(); this.manageInlineDataAttributes();
if (typeof options === 'object') { if (typeof options === 'object') {
this._settings = $.extend(true, {}, this.getSettings(), options); this._settings = $.extend(true, {}, this.getSettings(), options);
} }
this.init(); this.init();
} }
AutoComplete.prototype.manageInlineDataAttributes = function () {
manageInlineDataAttributes() {
var s = this.getSettings(); var s = this.getSettings();
if (this._$el.data('url')) { if (this._$el.data('url')) s.resolverSettings.url = this._$el.data('url');
s.resolverSettings.url = this._$el.data('url'); if (this._$el.data('default-value')) this._defaultValue = this._$el.data('default-value');
} if (this._$el.data('default-text')) this._defaultText = this._$el.data('default-text');
if (this._$el.data('default-value')) { if (this._$el.data('noresults-text') !== undefined) s.noResultsText = this._$el.data('noresults-text');
this._defaultValue = this._$el.data('default-value'); }
}
if (this._$el.data('default-text')) { getSettings() { return this._settings; }
this._defaultText = this._$el.data('default-text');
} init() {
if (this._$el.data('noresults-text') !== undefined) {
s.noResultsText = this._$el.data('noresults-text');
}
};
AutoComplete.prototype.getSettings = function () {
return this._settings;
};
AutoComplete.prototype.init = function () {
this.bindDefaultEventListeners(); this.bindDefaultEventListeners();
if (this._settings.resolver === 'ajax') { if (this._settings.resolver === 'ajax') {
this.resolver = new AjaxResolver(this._settings.resolverSettings); this.resolver = new AjaxResolver(this._settings.resolverSettings);
} }
this._dd = new DropdownV5(this._$el, this._settings.formatResult, this._settings.autoSelect, this._settings.noResultsText); this._dd = new DropdownV5(this._$el, this._settings.formatResult, this._settings.autoSelect, this._settings.noResultsText);
}; }
AutoComplete.prototype.bindDefaultEventListeners = function () {
var _this = this; bindDefaultEventListeners() {
this._$el.on('keydown', function (evt) { this._$el.on('keydown', evt => {
//gnips: check breakValue and call keydown function var val = this._$el.val();
(('' == _this._$el.val() || ((_this._settings.breakValue.length > 0) && (_this._settings.breakValue.indexOf(_this._$el.val()) != -1))) && _this._settings.keyFunc(evt.which, _this._$el.val())); if ((val === '' || (this._settings.breakValue.length > 0 && this._settings.breakValue.indexOf(val) !== -1))) {
this._settings.keyFunc(evt.which, val);
}
switch (evt.which) { switch (evt.which) {
case 9: case 9:
if (_this._dd.isItemFocused) { if (!this._settings.breakFocus) {
_this._dd.selectFocusItem(); if (this._dd.isItemFocused) this._dd.selectFocusItem();
this._dd.hide();
} }
_this._dd.hide();
break; break;
case 13: case 13:
if (_this._dd.isItemFocused) { if (this._dd.isItemFocused) this._dd.selectFocusItem();
_this._dd.selectFocusItem(); this._dd.hide();
} if (this._settings.preventEnter) evt.preventDefault();
_this._dd.hide();
if (_this._settings.preventEnter) {
evt.preventDefault();
}
break; break;
case 40: case 40:
_this._dd.focusNextItem(); this._dd.focusNextItem();
break; break;
case 38: case 38:
_this._dd.focusPreviousItem(); this._dd.focusPreviousItem();
break; break;
} }
}); });
this._$el.on('keyup', function (evt) {
this._$el.on('keyup', evt => {
switch (evt.which) { switch (evt.which) {
case 16: case 16: case 17: case 18: case 39: case 37: case 36: case 35: case 40: case 38:
case 17:
case 18:
case 39:
case 37:
case 36:
case 35:
break; break;
case 13: case 13: case 27:
_this._dd.hide(); this._dd.hide();
break;
case 27:
_this._dd.hide();
break;
case 40:
break;
case 38:
break; break;
default: default:
_this._selectedItem = null; this._selectedItem = null;
var newValue = _this._$el.val(); this.handlerTyped(this._$el.val());
_this.handlerTyped(newValue);
} }
}); });
this._$el.on('blur', function (evt) {
if (!_this._dd.isMouseOver && _this._dd.isDdMouseOver && _this._dd.isShown()) { this._$el.on('blur', () => {
setTimeout(function () { _this._$el.focus(); }); if (this._settings.breakFocus) return;
_this._$el.focus(); if (!this._dd.isMouseOver && this._dd.isDdMouseOver && this._dd.isShown()) {
} setTimeout(() => { this._$el.focus(); });
else if (!_this._dd.isMouseOver) { this._$el.focus();
_this._dd.hide(); } else if (!this._dd.isMouseOver) {
this._dd.hide();
} }
}); });
this._$el.on('autocomplete.select', function (evt, item) {
_this._selectedItem = item; this._$el.on('autocomplete.select', (evt, item) => {
_this.itemSelectedDefaultHandler(item); this._selectedItem = item;
this.itemSelectedDefaultHandler(item);
}); });
this._$el.on('paste', function (evt) {
setTimeout(function () { this._$el.on('paste', evt => {
_this._$el.trigger('keyup', evt); setTimeout(() => { this._$el.trigger('keyup', evt); }, 0);
}, 0);
}); });
}; }
AutoComplete.prototype.handlerTyped = function (newValue) {
handlerTyped(newValue) {
if (this._settings.events.typed !== null) { if (this._settings.events.typed !== null) {
newValue = this._settings.events.typed(newValue, this._$el); newValue = this._settings.events.typed(newValue, this._$el);
if (!newValue) { if (!newValue) return;
return;
}
} }
if (newValue.length >= this._settings.minLength) { if (newValue.length >= this._settings.minLength) {
this._searchText = newValue; this._searchText = newValue;
this.handlerPreSearch(); this.handlerPreSearch();
} } else {
else {
this._dd.hide(); this._dd.hide();
} }
}; }
AutoComplete.prototype.handlerPreSearch = function () {
handlerPreSearch() {
if (this._settings.events.searchPre !== null) { if (this._settings.events.searchPre !== null) {
var newValue = this._settings.events.searchPre(this._searchText, this._$el); var newValue = this._settings.events.searchPre(this._searchText, this._$el);
if (!newValue) if (!newValue) return;
return;
this._searchText = newValue; this._searchText = newValue;
} }
this.handlerDoSearch(); this.handlerDoSearch();
}; }
AutoComplete.prototype.handlerDoSearch = function () {
var _this = this; handlerDoSearch() {
if (this._settings.events.search !== null) { if (this._settings.events.search !== null) {
this._settings.events.search(this._searchText, function (results) { this._settings.events.search(this._searchText, results => {
_this.postSearchCallback(results); this.postSearchCallback(results);
}, this._$el); }, this._$el);
} else if (this.resolver) {
this.resolver.search(this._searchText, results => {
this.postSearchCallback(results);
});
} }
else { }
if (this.resolver) {
this.resolver.search(this._searchText, function (results) { postSearchCallback(results) {
_this.postSearchCallback(results);
});
}
}
};
AutoComplete.prototype.postSearchCallback = function (results) {
if (this._settings.events.searchPost) { if (this._settings.events.searchPost) {
results = this._settings.events.searchPost(results, this._$el); results = this._settings.events.searchPost(results, this._$el);
if ((typeof results === 'boolean') && !results) if (typeof results === 'boolean' && !results) return;
return;
} }
this.handlerStartShow(results); this.handlerStartShow(results);
}; }
AutoComplete.prototype.handlerStartShow = function (results) {
//gnips: adding auto select when barcode result of 1 item only handlerStartShow(results) {
if (1 == results.length && 1 == (void 0 !== results[0].bc ? results[0].bc : 0)) { var bcVal = results[0]?.bc ?? 0;
this._$el.trigger('autocomplete.select', results[0]), if (results.length === 1 && bcVal === 1) {
this._$el.trigger('autocomplete.select', results[0]);
this._dd.hide(); this._dd.hide();
} else this._dd.updateItems(results, this._searchText); } else {
}; this._dd.updateItems(results, this._searchText);
AutoComplete.prototype.itemSelectedDefaultHandler = function (item) { }
}
itemSelectedDefaultHandler(item) {
if (item != null) { if (item != null) {
var itemFormatted = this._settings.formatResult(item); var itemFormatted = this._settings.formatResult(item);
if (typeof itemFormatted === 'string') { if (typeof itemFormatted === 'string') itemFormatted = { text: itemFormatted };
itemFormatted = { text: itemFormatted };
}
this._$el.val(itemFormatted.text); this._$el.val(itemFormatted.text);
} } else {
else {
this._$el.val(''); this._$el.val('');
} }
this._selectedItem = item; this._selectedItem = item;
//gnips: callback with selected item
this._settings.selectFunc(item); this._settings.selectFunc(item);
this._dd.hide(); this._dd.hide();
}; }
AutoComplete.prototype.defaultFormatResult = function (item) {
if (typeof item === 'string') {
return { text: item };
}
else if (item.text) {
return item;
}
else {
return { text: item.toString() };
}
};
AutoComplete.prototype.manageAPI = function (APICmd, params) {
if (APICmd === 'set') {
this.itemSelectedDefaultHandler(params);
}
else if (APICmd === 'clear') {
this.itemSelectedDefaultHandler(null);
}
else if (APICmd === 'show') {
this._$el.trigger('keyup');
}
else if (APICmd === 'updateResolver') {
this.resolver = new AjaxResolver(params);
}
};
AutoComplete.NAME = 'autoComplete';
return AutoComplete;
}());
(function ($, window, document) { defaultFormatResult(item) {
$.fn[AutoComplete.NAME] = function (optionsOrAPI, optionalParams) { if (typeof item === 'string') return { text: item };
return this.each(function () { if (item.text) return item;
var pluginClass; return { text: item.toString() };
pluginClass = $(this).data(AutoComplete.NAME); }
if (!pluginClass) {
pluginClass = new AutoComplete(this, optionsOrAPI); manageAPI(APICmd, params) {
$(this).data(AutoComplete.NAME, pluginClass); if (APICmd === 'set') this.itemSelectedDefaultHandler(params);
} else if (APICmd === 'clear') this.itemSelectedDefaultHandler(null);
pluginClass.manageAPI(optionsOrAPI, optionalParams); else if (APICmd === 'show') this._$el.trigger('keyup');
}); else if (APICmd === 'updateResolver') this.resolver = new AjaxResolver(params);
}; }
})(jQuery, window, document); };
AutoComplete.NAME = 'autoComplete';
// jQuery plugin wrapper
$.fn[AutoComplete.NAME] = function (optionsOrAPI, optionalParams) {
return this.each(function () {
var pluginClass = $(this).data(AutoComplete.NAME);
if (!pluginClass) {
pluginClass = new AutoComplete(this, optionsOrAPI);
$(this).data(AutoComplete.NAME, pluginClass);
}
pluginClass.manageAPI(optionsOrAPI, optionalParams);
});
};
+77 -73
View File
@@ -4,82 +4,86 @@
Desc. : Fungsi-fungsi download data & laporan Desc. : Fungsi-fungsi download data & laporan
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : April 1st, 2011. Created Date : April 1st, 2011.
Last Modified : October 2nd, 2026. Last Modified : November 2nd, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
*******************************************************************/ *******************************************************************
*
* Class Download
*
* Download atau Share file.
*
* @license MIT, https://www.3fonia.com
* @copyright Copyright (c) 2008-2026, Siau Pheng <[email protected]>
*
* @param {object} options
* @param {string} options.url - Endpoint URL File
* @param {string} [options.filename='file.bin'] - Default file name for download is header is missing
* @param {string} [options.title] - Title for share file
* @param {string} [options.text] - Text for share file
* @param {Array} [options.params] - Request payload
*/
var Download = function(options) { class Download {
var _options = { constructor(options = {}) {
'url': '', this.url = typeof options === 'string' ? options : options.url || '';
'filename': 'file.bin', this.filename = options.filename || 'file.bin';
'title': '', this.title = options.title || '';
'text': 'share dari aplikasi' this.text = options.text || '';
}; this.params = options.params || [];
var _params = []; }
this.construct = function(options){ addParam(id, val) {
if (typeof options == 'object') { this.params.push(val !== undefined ? `${id}=${encodeURIComponent(val)}` : id);
$.extend(_options, options);
} else _options.url = options;
};
this.set_url = function (url){
_options.url = url;
};
this.add_param = function(par_id, par_val) {
var _getvalue = (par_val) ? par_id + '=' + encodeURIComponent(par_val) : par_id;
_params.push(_getvalue);
};
this._exec = async function(is_share) {
if (!_options.url) {
console.warn('cek url !');
return false;
}
try {
const blob = await $.ajax({
type: 'POST',
url: _options.url,
data: FoniaJS.RequestParam(_params),
xhrFields: {
responseType: 'blob'
},
success: async function (data, status, xhr) {
const disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
_options.filename = matches[1].replace(/['"]/g, '');
}
}
}
});
const file = new File([blob], _options.filename, { type: blob.type });
if (is_share === true && navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
files: [file],
title: _options.title ?? _options.filename,
text: _options.text
});
} else {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = _options.filename;
link.click();
}
} catch (error) {
console.error('Download / sharing gagal :', error);
};
return this; return this;
}; }
this.share = async function() {
await this._exec(true); async _exec(isShare = false) {
}; if (!this.url) {
this.download = async function() { console.warn('Url File Download dibutuhkan!');
await this._exec(false); return;
}; }
this.construct(options); await $.ajax({
}; type: 'POST',
url: this.url,
data: FoniaJS.RequestParam(this.params),
xhrFields: { responseType: 'blob' }
})
.done((blob, status, xhr) => {
let filename = this.filename;
const disposition = xhr.getResponseHeader('Content-Disposition');
const match = disposition?.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (match?.[1]) filename = match[1].replace(/['"]/g, '');
if (!isShare) {
const url = URL.createObjectURL(blob);
const link = Object.assign(document.createElement('a'), {
href: url,
download: filename
});
link.click();
URL.revokeObjectURL(url);
} else {
const file = new File([blob], filename, { type: blob.type });
if (isShare && navigator.canShare?.({ files: [file] })) {
navigator.share({
files: [file],
title: this.title || filename,
text: this.text
});
}
file = null;
}
})
.fail((xhr, status, err) => console.error('Gagal download File karena : ', err));
}
share() {
return this._exec(true);
}
download() {
return this._exec(false);
}
}
+662 -750
View File
File diff suppressed because it is too large Load Diff
+121 -61
View File
@@ -1,66 +1,126 @@
/******************************************************************* /*******************************************************************
Module : /assets/js/pdf.js Module : /assets/js/pdf.js
Desc. : JS - Cetak Laporan PDF Desc. : JS - Cetak Laporan PDF
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : April 6th, 2015. Created Date : April 6th, 2015.
Last Modified : October 18th, 2020. Last Modified : November 2nd, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
*******************************************************************/ *******************************************************************
*
* Class Pdf
*
* Preview dan download file pdf.
*
* @license MIT, https://www.3fonia.com
* @copyright Copyright (c) 2008-2026, Siau Pheng <[email protected]>
*
* @param {object} options
* @param {string} options.url - Endpoint PDF File URL
* @param {number} [options.zoom=80] - Zoom percentage in PDF viewer
* @param {string} [options.filename='report.pdf'] - Default file name for download is header is missing
* @param {Array} [options.params] - Request payload
*/
var Pdf = function(options){ class Pdf {
var vars = { constructor(options = {}) {
'url': '', this.url = typeof options === 'string' ? options : options.url || '';
'zoom': 80, this.zoom = options.zoom || 80;
'param': [] this.filename = options.filename || 'report.pdf';
}; this.params = options.params || [];
this.construct = function(options){ }
if (typeof options == 'object') {
$.extend(vars, options); addParam(id, val) {
} else vars.url = options; this.params.push(val !== undefined ? `${id}=${encodeURIComponent(val)}` : id);
}; return this;
this.set_url = function (url){ }
vars.url = url;
}; async downloadPdf() {
this.set_zoom = function (zoom){ if (!this.url) {
if (zoom) vars.zoom = zoom; console.warn('Url PDF dibutuhkan!');
}, return;
this.add_param = function (par_id, par_val){ }
var _getvalue = (par_val) ? par_id + "=" + escape(par_val) : par_id; await $.ajax({
vars.param.push(_getvalue); type: 'POST',
}, url: this.url,
this.downloadPDF = function(){ data: FoniaJS.RequestParam(this.params),
if (!vars.url) { xhrFields: { responseType: 'blob' }
console.warn(vars.param + ", cek url php!"); })
return false; .done((blob, status, xhr) => {
} let filename = this.filename;
$.ajax({ const disposition = xhr.getResponseHeader('Content-Disposition');
type: 'POST', const match = disposition?.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
url: vars.url, if (match?.[1]) filename = match[1].replace(/['"]/g, '');
data: FoniaJS.RequestParam(vars.param), const url = URL.createObjectURL(blob);
success: (typeof vars.onSuccess === "function") ? vars.onSuccess : null, const link = Object.assign(document.createElement('a'), {
error: (typeof vars.onFailure === "function") ? vars.onFailure : null, href: url,
}).always(FoniaJS.FormDecorated); download: filename
return this; });
}; link.click();
this.showPDF = function(){ URL.revokeObjectURL(url);
var _proto = window.location.protocol + '//'; })
var _src = _proto + FoniaJS.WEB_HOST + vars.url + '?' + $.param(FoniaJS.RequestParam(vars.param)) + '#zoom='+vars.zoom+',0,0&page=1'; .fail((xhr, status, err) => console.error('Gagal proses download PDF karena : ', err));
var _wtxt = }
'<div class="modal fade" id="winAPPPDF" tabindex="-1" role="dialog" data-bs-keyboard="false" data-bs-show="false" data-bs-backdrop="static" aria-labelledby="winAPPPDFLabel" aria-hidden="true">'+
'<div class="modal-dialog modal-dialog-centered modal-fs" role="document">'+ async showPdf() {
'<div class="modal-content">'+ if (!this.url) {
'<div class="modal-header"><h5 class="modal-title" id="winAPPPDFLabel">Preview PDF</h5><button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close"></button></div>'+ console.warn('Url PDF dibutuhkan!');
'<div class="modal-body mb-0 p-0"><div class="ratio ratio-21x9"><object type="application/pdf" data="'+_src+'"><p>Tidak Mendukung Preview PDF</p></object></div></div>'+ return;
'</div></div></div>'; }
if ($('#winAPPPDF')) $('#winAPPPDF').modal('dispose').remove(); await $.ajax({
$(_wtxt).on('shown.bs.modal', function(e){ type: 'POST',
FoniaJS.FormDecorated(); url: this.url,
}).on('hidden.bs.modal', function(e){ data: FoniaJS.RequestParam(this.params),
$(this).remove(); xhrFields: { responseType: 'blob' }
}).modal('show'); })
}; .done((blob, status, xhr) => {
this.construct(options); let filename = this.filename;
}; const disposition = xhr.getResponseHeader('Content-Disposition');
const match = disposition?.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (match?.[1]) filename = match[1].replace(/['"]/g, '');
const objectUrl = URL.createObjectURL(blob);
const pdfSrc = `${objectUrl}#zoom=${zoom}&page=1`;
const modalHtml = `
<div class="modal fade" id="winAPPPDF" tabindex="-1" data-bs-backdrop="static" aria-labelledby="winAPPPDFLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl" style="height: 90vh;">
<div class="modal-content h-100">
<div class="modal-header">
<h5 class="modal-title" id="winAPPPDFLabel">${filename}</h5>
<div class="ms-auto d-flex align-items-center gap-2">
<a href="${objectUrl}" download="${filename}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-download"></i> Download
</a>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body p-0 h-100">
<object data="${pdfSrc}" type="application/pdf" class="w-100 h-100">
<iframe src="${pdfSrc}" class="w-100 h-100" style="border: none;">
<p class="p-3">Ada kendala dalam Preview PDF di browser Anda.
<a href="${objectUrl}" download="${filename}">Unduh ${filename}</a> instead.
</p>
</iframe>
</object>
</div>
</div>
</div>
</div>
`;
$('#winAPPPDF').modal('dispose').remove();
$(modalHtml)
.appendTo('body')
//.on('shown.bs.modal', () => FoniaJS.FormDecorated())
.on('hidden.bs.modal', function () {
URL.revokeObjectURL(objectUrl);
$(this).remove();
})
.modal('show');
})
.fail((xhr, status, err) => console.error('Gagal proses preview PDF karena : ', err));
}
}
+63 -67
View File
@@ -4,90 +4,86 @@
Desc. : JS Class Tampil Data di Pole Display POS Desc. : JS Class Tampil Data di Pole Display POS
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : October 12th, 2025. Created Date : October 12th, 2025.
Last Modified : November 1st, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
*******************************************************************/ *******************************************************************/
var PoleDisplay = function(port, options){ class PoleDisplay {
var _cd_port = ''; constructor(port, options = {}) {
var _cd_pole = ''; this.port = port;
var _options = { this.pole = '';
rx: { start: '\x02', end: '\x0D', width: null }, this.options = Object.assign({
baudRate: 9600, rx: { start: '\x02', end: '\x0D', width: null },
dataBits: 8, baudRate: 9600,
stopBits: 1, dataBits: 8,
parity: 'NONE', stopBits: 1,
flowControl: 'NONE' parity: 'NONE',
}; flowControl: 'NONE'
}, options);
}
this.construct = function (port, options){ Open() {
$.extend(_options, options); if (this.pole) return;
_cd_port = port;
};
this.Open = function (){ qz.serial.openPort(this.port, this.options)
if (_cd_pole === '') { .then(() => {
qz.serial.openPort(_cd_port, _options).then(() => { this.pole = this.port;
_cd_pole = _cd_port; this._conerr(`${this.port} opened`);
this.cd_Error(_cd_port+' opened'); return qz.serial.sendData(this.pole, { type: 'HEX', data: 'x1Bx40' }, this.options);
}).then(() => { })
qz.serial.sendData(_cd_pole, {type:'HEX',data:'x1Bx40'}, _options).catch(this.cd_Error); .catch(this._conerr);
}).catch(this.cd_Error); }
}
};
this.Close = function (){ Close() {
qz.serial.closePort(_cd_port).then(() => { if (!this.pole) return;
_cd_pole = '';
this.cd_Error(_cd_port+' closed');
}).catch(this.cd_Error);
};
this.Send = function (txt){ qz.serial.closePort(this.port)
if (_cd_pole === '') return false; .then(() => {
var ascii_to_hexa = function(str) { this.pole = '';
var arr1 = []; this._conerr(`${this.port} closed`);
for (var n = 0, l = str.length; n < l; n++) { })
var hex = Number(str.charCodeAt(n)).toString(16); .catch(this._conerr);
if (hex.length==1) hex = '0' + hex; }
arr1.push(hex.toUpperCase());
} Send(txt) {
return 'x' + arr1.join('x'); if (!this.pole) return false;
};
var _txt = txt; // Convert string to ASCII hex format
const asciiToHex = str =>
Array.from(str, char => char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase())
.join('x');
let textToSend = txt;
if (Array.isArray(txt)) { if (Array.isArray(txt)) {
_txt = ''; textToSend = txt.map(ele => this._aligntext(ele.t, ele.a)).join('');
txt.forEach((ele) => {
_txt += this.cd_Text(ele['t'], ele['a']);
});
} }
qz.serial.sendData(_cd_pole, {type:'HEX',data:'x10C'+ascii_to_hexa(_txt)}, _options).catch(this.cd_Error);
};
this.cd_Text = function (text, align){ const hexData = `x10C${asciiToHex(textToSend)}`;
qz.serial.sendData(this.pole, { type: 'HEX', data: hexData }, this.options)
.catch(this._conerr);
}
_aligntext(text, align = 'L') {
const totWidth = 20; const totWidth = 20;
const txtLength = text.length; if (text.length >= totWidth) return text.substring(0, totWidth);
if (txtLength >= totWidth) {
return text.substring(0, totWidth); const padding = totWidth - text.length;
} let lPadding = 0, rPadding = padding;
const padding = totWidth - txtLength;
var lPadding = 0; if (align === 'C') {
var rPadding = padding;
if (align === null) align = 'L';
if (align == 'C'){
lPadding = Math.floor(padding / 2); lPadding = Math.floor(padding / 2);
rPadding = Math.ceil(padding / 2); rPadding = Math.ceil(padding / 2);
} else if (align == 'R'){ } else if (align === 'R') {
lPadding = padding; lPadding = padding;
rPadding = 0; rPadding = 0;
} }
return " ".repeat(lPadding) + text + " ".repeat(rPadding);
};
this.cd_Error = function (err){ return ' '.repeat(lPadding) + text + ' '.repeat(rPadding);
}
_conerr(err) {
console.error(err); console.error(err);
}; }
}
this.construct(port, options);
};
+256 -224
View File
@@ -4,233 +4,265 @@
Desc. : JS Class Printer & Printer POS Desc. : JS Class Printer & Printer POS
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : May 27th, 2007. Created Date : May 27th, 2007.
Last Modified : September 21st, 2026. Last Modified : November 2nd, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
*******************************************************************/ *******************************************************************
*
* Class Printer
*
* Standard Web Document Printing & Report Preview Class
*
* @license MIT, https://www.3fonia.com
* @copyright Copyright (c) 2008-2026, Siau Pheng <[email protected]>
*
* @param {object} options
* @param {string} options.url - Endpoint URL Print Page
* @param {number} [options.zoom=90] - Zoom percentage in viewer
* @param {Array} [options.params] - Request payload
*/
var Printer = function(options){ class Printer {
var vars = { constructor(options = {}) {
'url': '', this.url = typeof options === 'string' ? options : options.url || '';
'zoom': 90, this.zoom = options.zoom || 90;
'param': [] this.params = options.params || [];
}; }
this.construct = function(options){
if (typeof options == 'object') { addParam(id, val) {
$.extend(vars, options); this.params.push(val !== undefined ? `${id}=${encodeURIComponent(val)}` : id);
} else vars.url = options;
};
this.set_url = function (url){
vars.url = url;
};
this.set_zoom = function (zoom){
if (zoom) vars.zoom = zoom;
},
this.add_param = function (par_id, par_val){
var _getvalue = (par_val) ? par_id + "=" + escape(par_val) : par_id;
vars.param.push(_getvalue);
},
this.doPrint = function(){
if (!vars.url) {
console.warn(vars.param + ", cek url php!");
return false;
}
$.ajax({
type: 'POST',
url: vars.url,
data: FoniaJS.RequestParam(vars.param),
success: (txt) => {
var $printData = $('#dataPrint');
if (!$printData.length) {
$printData = $('<div/>', {id:'dataPrint','class':'d-none d-print-block print-body'}).appendTo('body');
}
$printData.html(txt);
window.print();
},
error: (typeof vars.onFailure === "function") ? vars.onFailure : null,
});
return this; return this;
}; }
this.doPreview = function(){
if (!vars.url) console.warn(vars.param + ", cek url php!");
$.ajax({
type: 'POST',
url: vars.url,
data: FoniaJS.RequestParam(vars.param),
success: (txt) => {
if ('' == txt) {
FoniaJS.showInfo('Tidak ada data untuk pembuatan laporan !');
return false;
}
var _wtxt =
'<div class="modal fade" id="winAPPPrint" tabindex="-1" role="dialog" data-bs-keyboard="false" data-bs-show="false" data-bs-backdrop="static" aria-labelledby="winAPPPrintLabel" aria-hidden="true">'+
'<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fs" role="document">'+
'<div class="modal-content">'+
'<div class="modal-header"><h5 class="modal-title" id="winAPPPrintLabel">Preview Laporan</h5><div class="ms-auto"><button class="btn me-2" id="tombCetak" type="button" aria-label="Cetak"><i class="bi-printer me-2"></i><span aria-hidden="true">Cetak</span></button><button class="btn" type="button" data-bs-dismiss="modal" aria-label="Tutup"><i class="bi-x-lg me-2"></i><span aria-hidden="true">Tutup</span></button></div></div>'+
'<div class="modal-body print-body" id="divPrintP">'+txt+'</div>'+
'</div></div></div>';
if ($('#winAPPPrint')) $('#winAPPPrint').modal('dispose').remove();
$(_wtxt).on('shown.bs.modal', (e) => {
$('#winAPPPrint').find('#tombCetak').click(() => {
var $printData = $('#dataPrint');
if (!$printData.length) {
$printData = $('<div/>', {id:'dataPrint','class':'d-none d-print-block print-body'}).appendTo('body');
}
$printData.html($('#divPrintP').html());
FoniaJS.closeWindow($('#winAPPPrint'));
window.print();
});
}).on('hidden.bs.modal', (e) => {
$(this).remove();
}).modal('show');
},
error: (typeof vars.onFailure === "function") ? vars.onFailure : null,
});
};
this.construct(options);
};
var PrinterPos = function(options) { doPrint() {
var b_default = false; if (!this.url) {
var _options = { console.warn('Url Laporan dibutuhkan!');
'url': '', return;
'param': [], }
'printer': null, $.ajax({
'cut_paper': "\x1D\x56\x41", type: 'POST',
'open_draw': "\x1B\x70\x30\x19\x19", url: this.url,
'chr_norm': "\x1B\x21\x01", data: FoniaJS.RequestParam(this.params),
'chr_bold': "\x1B\x21\x10", success: (txt) => {
'chr_sbold': "\x1B\x21\x31", let $printData = $('#dataPrint');
'chr_uline': "\x1B\x21\x80" if (!$printData.length) {
}; $printData = $('<div/>', {
this.construct = function(options) { id: 'dataPrint',
if (typeof options == 'object') { class: 'd-none d-print-block print-body'
$.extend(_options, options); }).appendTo('body');
} else _options.url = options; }
if (FoniaJS._def_printer) _options.printer = FoniaJS._def_printer; $printData.html(txt);
}; window.print();
this.detectPrinter = function() { },
if (!qz.websocket.isActive()) { error: typeof this.onFailure === 'function' ? this.onFailure : null
FoniaJS.showInfo('Status Printer belum Active !'); });
return false; return this;
} }
return true;
}; doPreview() {
this.getPrinters = function() { if (!this.url) {
return []; console.warn('Url Laporan dibutuhkan!');
}; return;
this.setPrinter = function(nprint) { }
(nprint != null) && (_options.printer = nprint); $.ajax({
}; type: 'POST',
this.useDefaultPrinter = function() { url: this.url,
b_default = true; data: FoniaJS.RequestParam(this.params),
}; success: (txt) => {
this.set_url = function(url) { if (!txt) {
_options.url = url; FoniaJS.showInfo('Tidak ada data untuk pembuatan laporan !');
}; return false;
this.add_param = function(par_id, par_val) { }
var _getvalue = (par_val) ? par_id + '=' + escape(par_val) : par_id; const modalHtml = `
_options.param.push(_getvalue); <div class="modal fade" id="winAPPPrint" tabindex="-1" role="dialog" data-bs-keyboard="false" data-bs-show="false" data-bs-backdrop="static" aria-labelledby="winAPPPrintLabel" aria-hidden="true">
}; <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fs" role="document">
this.exec = function() { <div class="modal-content">
if (!this.detectPrinter()) return; <div class="modal-header">
if (this.b_default) { <h5 class="modal-title" id="winAPPPrintLabel">Preview Laporan</h5>
var parent = this; <div class="ms-auto">
qz.printers.getDefault().then(function(data) { <button class="btn me-2" id="tombCetak" type="button" aria-label="Cetak">
var nama_p = data; <i class="bi-printer me-2"></i><span aria-hidden="true">Cetak</span>
if (data.name != undefined) { </button>
nama_p = data.name; <button class="btn" type="button" data-bs-dismiss="modal" aria-label="Tutup">
} <i class="bi-x-lg me-2"></i><span aria-hidden="true">Tutup</span>
parent._options.param.push('Printer='+nama_p); </button>
return parent.do_print(nama_p, parent._options.url, parent._options.param); </div>
}, function(data) { </div>
FoniaJS.showInfo(data); <div class="modal-body print-body" id="divPrintP">${txt}</div>
}); </div>
} else { </div>
var _nprinter = _options.printer; </div>
if (typeof _nprinter === 'object' && _nprinter.name == undefined) { `;
if (_nprinter.file != undefined) _nprinter = _nprinter.file;
if (_nprinter.host != undefined) _nprinter = _nprinter.host + ":" + _nprinter.port; $('#winAPPPrint').modal('dispose').remove();
} else { $(modalHtml)
if (_nprinter.name != undefined) _nprinter = _nprinter.name; .on('shown.bs.modal', () => {
if (_nprinter== undefined) _nprinter = 'None'; $('#winAPPPrint #tombCetak').off('click').on('click', () => {
} let $printData = $('#dataPrint');
_options.param.push('Printer='+_nprinter); if (!$printData.length) {
this.do_print(_options.printer, _options.url, _options.param); $printData = $('<div/>', {
} id: 'dataPrint',
}; class: 'd-none d-print-block print-body'
this.do_print = function(nama_p, url, params) { }).appendTo('body');
var qzcfg = qz.configs.create(nama_p); }
new Request({ $printData.html($('#divPrintP').html());
url: url, FoniaJS.closeWindow($('#winAPPPrint'));
sendParam: params, window.print();
onSuccess: (ajson) => { });
if (typeof ajson === "object") { })
var hasil_cetak = (ajson.type == "escp") ? this.gen_escp(ajson) : this.gen_epl(ajson); .on('hidden.bs.modal', function () {
qz.print(qzcfg, hasil_cetak); $(this).remove();
} else { })
FoniaJS.showInfo('Error dalam proses Cetak.'); .modal('show');
} },
} error: typeof this.onFailure === 'function' ? this.onFailure : null
}).send(); });
}; return this;
this.gen_escp = function(adata) { }
var ohead = []; }
var osheet = [], ocetak = [];
ohead.push("\x1B\x40"); /**
ohead.push("\x1B\x61\x01"); * Class PrinterPos
if (adata.config.logo != null) { *
//ohead.push({type:'raw',format:'image',data:adata.config.logo,options:{language:'ESCPOS',dotDensity:'double'}}); * Direct Thermal POS Printer Class (via QZ Tray API)
//ohead.push("\x1B\x32"); *
ohead.push({type:'raw',format:'image',flavor:'base64',data:adata.config.logo,options:{language:'ESCPOS',dotDensity:'double'}}); * @license MIT, https://www.3fonia.com
ohead.push("\x1B\x32"); * @copyright Copyright (c) 2008-2026, Siau Pheng <[email protected]>
} *
if (parseInt(adata.config.fontsize) == 2) { * @param {object} options
_options.chr_norm = "\x1B\x21\x02"; * @param {string} options.url - Endpoint URL Print Page
} * @param {number} [options.printer=FoniaJS._def_printer] - Printer Name
if (adata.header.length > 0) { * @param {Array} [options.params] - Request payload
for (i = 0; i < adata.header.length; i++) { */
if (i==0 && adata.config.logo==null) ohead.push(_options.chr_bold);
ohead.push(adata.header[i] + "\x0A"); class PrinterPos {
if (i==0 && adata.config.logo==null) ohead.push(_options.chr_norm); constructor(options = {}) {
} this.url = typeof options === 'string' ? options : options.url || '';
} this.printer = options.printer || (FoniaJS._def_printer ?? null);
for (s = 0; s < adata.content.length; s++) { this.cut_paper = options.cut_paper || '\x1D\x56\x41';
if (adata.content[s].length == 0) continue; this.open_draw = options.open_draw || '\x1B\x70\x30\x19\x19';
osheet = ohead.slice(0); this.chr_norm = options.chr_norm || '\x1B\x21\x01';
for (i = 0; i < adata.content[s].length; i++) { this.chr_bold = options.chr_bold || '\x1B\x21\x10';
var tmp_cont = adata.content[s][i]; this.chr_sbold = options.chr_sbold || '\x1B\x21\x31';
tmp_cont = tmp_cont.replace('_SBOLD_', _options.chr_sbold) this.chr_uline = options.chr_uline || '\x1B\x21\x80';
.replace('_BOLD_', _options.chr_bold) this.params = options.params || [];
.replace('_NORM_', _options.chr_norm) }
.replace('_ULINE_', _options.chr_uline);
osheet.push(tmp_cont + "\x0A"); _detectPrinter() {
} if (!qz?.websocket?.isActive?.()) {
if (adata.config.cutpaper) { FoniaJS.showInfo('Status Printer belum Active !');
osheet.push(_options.cut_paper + "\x0A"); return false;
} }
ocetak = ocetak.concat(osheet); return true;
} }
var notaout = [];
if (adata.config.opendraw) { setPrinter(nprint) {
notaout.push(_options.open_draw); if (nprint != null) this.printer = nprint;
} return this;
for (i = 0; i < adata.config.times; i++) { }
notaout = notaout.concat(ocetak);
} addParam(id, val) {
return notaout; this.params.push(val !== undefined ? `${id}=${encodeURIComponent(val)}` : id);
}; return this;
this.gen_epl = function(adata) { }
var cetak = [];
for (i = 0; i < adata.content.length; i++) { async exec() {
cetak.push(adata.content[i] + "\r\n"); if (!this._detectPrinter()) return;
}
var notaout = []; let printerName = this.printer;
for (i = 0; i < adata.config.times; i++) { if (typeof printerName === 'object' && printerName !== null) {
notaout = notaout.concat(cetak); if (printerName.name !== undefined) {
} printerName = printerName.name;
return notaout; } else if (printerName.file !== undefined) {
}; printerName = printerName.file;
this.construct(options); } else if (printerName.host !== undefined) {
}; printerName = `${printerName.host}:${printerName.port}`;
}
}
printerName = printerName ?? 'None';
this.params.push(`Printer=${printerName}`);
this.doPrint(this.printer, this.url, this.params);
}
doPrint(printerName, url, params) {
const qzcfg = qz.configs.create(printerName);
new Request({
url,
sendParam: params,
onSuccess: (ajson) => {
if (typeof ajson === 'object' && ajson !== null) {
const printPayload = ajson.type === 'escp' ? this.genEscp(ajson) : this.genEpl(ajson);
qz.print(qzcfg, printPayload);
} else {
FoniaJS.showInfo('Error dalam proses Cetak.');
}
}
}).send();
}
genEscp(adata) {
const ohead = ['\x1B\x40', '\x1B\x61\x01'];
if (adata.config?.logo) {
ohead.push({
type: 'raw',
format: 'image',
flavor: 'base64',
data: adata.config.logo,
options: { language: 'ESCPOS', dotDensity: 'double' }
});
ohead.push('\x1B\x32');
}
if (parseInt(adata.config?.fontsize, 10) === 2) {
this.chr_norm = '\x1B\x21\x02';
}
if (Array.isArray(adata.header)) {
adata.header.forEach((headerLine, index) => {
if (index === 0 && !adata.config?.logo) ohead.push(this.chr_bold);
ohead.push(`${headerLine}\x0A`);
if (index === 0 && !adata.config?.logo) ohead.push(this.chr_norm);
});
}
let ocetak = [];
if (Array.isArray(adata.content)) {
adata.content.forEach((sheetContent) => {
if (!sheetContent?.length) return;
const osheet = [...ohead];
sheetContent.forEach((line) => {
const formattedLine = line
.replace('_SBOLD_', this.chr_sbold)
.replace('_BOLD_', this.chr_bold)
.replace('_NORM_', this.chr_norm)
.replace('_ULINE_', this.chr_uline);
osheet.push(`${formattedLine}\x0A`);
});
if (adata.config?.cutpaper) {
osheet.push(`${this.cut_paper}\x0A`);
}
ocetak = ocetak.concat(osheet);
});
}
let notaout = [];
if (adata.config?.opendraw) {
notaout.push(this.open_draw);
}
const times = adata.config?.times || 1;
for (let i = 0; i < times; i++) {
notaout = notaout.concat(ocetak);
}
return notaout;
}
genEpl(adata) {
const cetak = (adata.content || []).map((line) => `${line}\r\n`);
let notaout = [];
const times = adata.config?.times || 1;
for (let i = 0; i < times; i++) {
notaout = notaout.concat(cetak);
}
return notaout;
}
}
+95 -68
View File
@@ -1,84 +1,111 @@
/******************************************************************* /*******************************************************************
Module : /assets/js/simulator.js Module : /assets/js/simulator.js
Desc. : JS Class Simulator Data dalam Code Desc. : JS Class Simulator Data dalam Code
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : March 18th, 2021. Created Date : March 18th, 2021.
Last Modified : December 26th, 2025. Last Modified : November 2nd, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
*******************************************************************
*
* Class Simulator
*
* Simulator Data untuk sinkron data dengan JSON
*
* @license MIT, https://www.3fonia.com
* @copyright Copyright (c) 2008-2026, Siau Pheng <[email protected]>
*
* @param {object} options
* @param {string} options.url - Endpoint URL Print Page
* @param {Array} [options.params] - Request payload
*/
*******************************************************************/ class Simulator {
constructor(options = {}) {
this.url = typeof options === 'string' ? options : options.url || '';
this.params = options.params || [];
}
var Simulator = function(options){ addParam(id, val) {
var vars = { this.params.push(val !== undefined ? `${id}=${encodeURIComponent(val)}` : id);
'url': '', return this;
'param': [] }
};
this.construct = function(options){ doPreview() {
if (typeof options == 'object') { if (!this.url) {
$.extend(vars, options); console.warn('Url Simulator dibutuhkan!');
} else vars.url = options; return;
}; }
this.set_url = function (url){
vars.url = url;
};
this.add_param = function (par_id, par_val){
var _getvalue = (par_val) ? par_id + "=" + escape(par_val) : par_id;
vars.param.push(_getvalue);
},
this.doPreview = function(){
if (!vars.url) console.warn(vars.param + ", cek url php!");
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: vars.url, url: this.url,
data: FoniaJS.RequestParam(vars.param), data: FoniaJS.RequestParam(this.params),
success: function(data){ dataType: 'json',
if (data.length == 0) { error: typeof onFailure === 'function' ? onFailure : null,
success: (data) => {
if (!data || data.length === 0) {
FoniaJS.showInfo('Tidak ada data !'); FoniaJS.showInfo('Tidak ada data !');
return false; return false;
} }
var _divd = '';
for (var no = 0; no < data.length; no++) { const divContent = data
_divd += '<div class="sim-code px-2 py-2 mb-1 border border-info">' + data[no] + '</div>'; .map(item => `<div class="sim-code px-2 py-2 mb-1 border border-info">${item}</div>`)
} .join('');
var _wtxt =
'<div class="modal fade" id="winAPPSim" tabindex="-1" role="dialog" data-bs-keyboard="false" data-bs-show="false" data-bs-backdrop="static" aria-labelledby="winAPPSimLabel" aria-hidden="true">'+ const modalHtml = `
'<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fs" role="document">'+ <div class="modal fade" id="winAPPSim" tabindex="-1" role="dialog" data-bs-keyboard="false" data-bs-show="false" data-bs-backdrop="static" aria-labelledby="winAPPSimLabel" aria-hidden="true">
'<div class="modal-content">'+ <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fs" role="document">
'<div class="modal-header"><h5 class="modal-title" id="winAPPSimLabel">Code Simulator</h5><div class="ms-auto"><button class="btn me-2" id="tombSalin" type="button" aria-label="Salin Semua"><i class="bi-copy me-2"></i><span aria-hidden="true">Salin Semua</span></button><button class="btn" type="button" data-bs-dismiss="modal" aria-label="Tutup"><i class="bi-x-lg me-2"></i><span aria-hidden="true">Tutup</span></button></div></div>'+ <div class="modal-content">
'<div class="modal-body">'+_divd+'</div>'+ <div class="modal-header">
'</div></div></div>'; <h5 class="modal-title" id="winAPPSimLabel">Code Simulator</h5>
if ($('#winAPPSim')) $('#winAPPSim').modal('dispose').remove(); <div class="ms-auto">
$(_wtxt).on('shown.bs.modal', function(e){ <button class="btn me-2" id="tombSalin" type="button" aria-label="Salin Semua">
$('#winAPPSim').find('#tombSalin').click(function(){ <i class="bi-copy me-2"></i><span aria-hidden="true">Salin Semua</span>
window.getSelection().removeAllRanges(); </button>
$('.sim-code').each(function(i, ele) { <button class="btn" type="button" data-bs-dismiss="modal" aria-label="Tutup">
var range = document.createRange(); <i class="bi-x-lg me-2"></i><span aria-hidden="true">Tutup</span>
range.selectNode(ele); </button>
window.getSelection().addRange(range); </div>
</div>
<div class="modal-body">${divContent}</div>
</div>
</div>
</div>`;
$('#winAPPSim').modal('dispose').remove();
$(modalHtml)
.on('shown.bs.modal', function () {
const $modal = $(this);
// Copy all code blocks
$modal.find('#tombSalin').on('click', () => {
window.getSelection().removeAllRanges();
$('.sim-code').each((_, ele) => {
const range = document.createRange();
range.selectNode(ele);
window.getSelection().addRange(range);
});
document.execCommand('copy');
window.getSelection().removeAllRanges();
FoniaJS.showInfo('Sukses salin semua Kode !');
}); });
document.execCommand('copy');
window.getSelection().removeAllRanges(); // Copy individual code block and hide it
FoniaJS.showInfo('Sukses salin semua Kode !'); $modal.find('.sim-code').on('click', function () {
}); const range = document.createRange();
$('#winAPPSim').find('.sim-code').click(function(){ range.selectNode(this);
var range = document.createRange(); window.getSelection().removeAllRanges();
range.selectNode(this); window.getSelection().addRange(range);
window.getSelection().removeAllRanges(); document.execCommand('copy');
window.getSelection().addRange(range); window.getSelection().removeAllRanges();
document.execCommand('copy'); $(this).addClass('d-none');
window.getSelection().removeAllRanges(); });
//$(this).addClass('bg-success'); })
$(this).addClass('d-none'); .on('hidden.bs.modal', function () {
}); $(this).remove();
}).on('hidden.bs.modal', function(e){ })
$(this).remove(); .modal('show');
}).modal('show'); }
},
error: (typeof vars.onFailure === "function") ? vars.onFailure : null,
dataType: 'json'
}); });
}; }
this.construct(options); }
};
+5 -5
View File
@@ -18,12 +18,12 @@
], ],
"require": { "require": {
"php": ">=8.3", "php": ">=8.3",
"flightphp/core": "^3.18", "flightphp/core": "^3.19",
"flightphp/session": "^0.3", "flightphp/session": "^0.3",
"mpdf/mpdf": ">=8.0", "mpdf/mpdf": "^8.3",
"avadim/fast-excel-writer": "^6.17", "avadim/fast-excel-writer": "^6.19",
"matthiasmullie/minify": ">=1.3", "matthiasmullie/minify": "^1.3",
"endroid/qr-code": ">=4.4" "endroid/qr-code": "^6.1"
}, },
"autoload": { "autoload": {
"classmap": ["src/"], "classmap": ["src/"],
+27 -37
View File
@@ -5,7 +5,7 @@
Module : /App/Auth.php Module : /App/Auth.php
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : March 16th, 2008. Created Date : March 16th, 2008.
Last Modified : September 10th, 2026. Last Modified : October 31st, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -43,73 +43,51 @@ final class Auth {
$this->app = $app; $this->app = $app;
$this->db = $app->db(); $this->db = $app->db();
$this->session = $app->session(); $this->session = $app->session();
$this->id_reg = (int) $this->session->get('WEB_APP', ['REG_ID'=>0])['REG_ID']; $this->id_reg = (int) $this->session->get('WEB_APP', ['PARENT_CODE'=>0])['PARENT_CODE'];
}
public function set_id_reg(int|string $id = 0): void {
if ((int) $id > 0) $this->id_reg = (int) $id;
} }
public function user_check(string $user, string $pass): bool { public function user_check(string $user, string $pass): bool {
$i_hasil = (int) $this->db->fetchField('SELECT COUNT(*) FROM app_operator WHERE Username=? AND Password=MD5(?) AND Aktif=1', [$user, $pass]); $i_hasil = (int) $this->db->fetchField('SELECT COUNT(*) FROM app_operator WHERE Username=? AND Password=MD5(?) AND Aktif=1', [$user, $pass]) ?? 0;
return ($i_hasil > 0 ? true : false); return ($i_hasil > 0 ? true : false);
} }
public function user_data(string $user): array { public function user_data(string $user, bool $gen_module=true): array {
$adata = [ 'Grup_MenuID' => 0 ]; $adata = [ 'Grup_MenuID' => 0 ];
$xdata = $this->db->fetchRow('SELECT *,DECODE(Menu,?) Menu2 FROM app_operator WHERE Username=?', [$this->id_reg, $user]); $xdata = $this->db->fetchRow('SELECT UserID,Grup_MenuID,Username,Nama,Posisi,Email,HP,Level,Last_IP,Last_Date FROM app_operator WHERE Username=?', [$user]);
if ($xdata) { if ($xdata) $adata = $xdata->getData();
$adata = $xdata->getData();
$adata['Menu'] = $adata['Menu2'];
unset($adata['Menu2'], $adata['Password']);
}
if (empty($this->session->get('APP_TOKEN'))) { if (empty($this->session->get('APP_TOKEN'))) {
$_token = $this->genToken(60); $_token = $this->genToken(60);
$this->session->set('APP_TOKEN', $_token); $this->session->set('APP_TOKEN', $_token);
$this->db->insert('app_token', [ 'Token' => $_token, 'UserID' => $adata['UserID'], 'IP' => $this->app->request()->ip ]); $this->db->insert('app_token', [ 'Token' => $_token, 'UserID' => $adata['UserID'], 'IP' => $this->app->request()->ip ]);
} }
if ((int) $adata['Grup_MenuID'] > 0) $adata['Menu_Grup'] = $this->user_allmenu((int) $adata['Grup_MenuID']);
$adata['UNIX'] = time(); $adata['UNIX'] = time();
$adata['LOCALE'] = $this->session->get('WEB_APP', ['DEF_LOCALE'=>'id'])['DEF_LOCALE']; $adata['LOCALE'] = $this->session->get('WEB_APP', ['DEF_LOCALE'=>'id'])['DEF_LOCALE'];
$adata['MENU_MODUL'] = []; $adata['MENU_MODUL'] = [];
return $adata; if ($gen_module === true && !empty($this->session->get('WEB_APP')['APP_MODULE'])) {
} $tallow = $this->db->fetchField('SELECT Modul_Akses FROM app_menu_grup WHERE Grup_MenuID=?', [$adata['Grup_MenuID']]) ?? '';
public function gen_module_menu(): void {
$amodul = []; $aallow = []; $tallow = '';
if ($this->session->get('WEB_APP', ['APP_MODULE'=>0])['APP_MODULE']) {
$tallow = $this->db->fetchField('SELECT Modul_Akses FROM app_menu_grup WHERE Grup_MenuID=?', [$this->session->get('WEB_USER', ['Grup_MenuID'=>0])['Grup_MenuID']]);
$aallow = explode(",", $tallow); $aallow = explode(",", $tallow);
if (count($aallow) > 0) { if (count($aallow) > 0) {
$stat = $this->db->runQuery('SELECT * FROM (SELECT APP_MODULE,APP_PATH FROM app_config) A LEFT JOIN (SELECT Modul,Urut,Judul,Icon FROM app_menu WHERE Level=0) B ON (A.APP_MODULE=B.Modul) ORDER BY B.Urut'); $stat = $this->db->runQuery('SELECT * FROM (SELECT APP_MODULE,APP_PATH FROM app_config) A LEFT JOIN (SELECT Modul,Urut,Judul,Icon FROM app_menu WHERE Level=0) B ON (A.APP_MODULE=B.Modul) ORDER BY B.Urut');
while ($row = $stat->fetch()) { while ($row = $stat->fetch()) {
if (!in_array($row['Modul'], $aallow) || $row['Modul']==$this->session->get('WEB_APP')['APP_MODULE']) continue; if (!in_array($row['Modul'], $aallow) || $row['Modul'] === $this->session->get('WEB_APP')['APP_MODULE']) continue;
$amodul[] = $row; $adata['MENU_MODUL'][] = $row;
} }
} }
} }
$auser = $this->session->get('WEB_USER', []); return $adata;
$auser['MENU_MODUL'] = $amodul;
$this->session->set('WEB_USER', $auser);
}
public function user_allmenu(int $id_grup = 0): string {
$allmenu = $this->db->fetchField('SELECT DECODE(Menu,?) MenuX FROM app_menu_grup WHERE Grup_MenuID=?', [$this->id_reg, $id_grup]);
return $allmenu;
} }
public function user_check_level(string $user, int $level = 9): bool { public function user_check_level(string $user, int $level = 9): bool {
$i_hasil = (int)$this->db->fetchField('SELECT COUNT(*) FROM app_operator WHERE Username=? AND Level<=? AND Aktif=1', [$user, $level]); $i_hasil = (int) $this->db->fetchField('SELECT COUNT(*) FROM app_operator WHERE Username=? AND Level<=? AND Aktif=1', [$user, $level]);
return ($i_hasil > 0 ? true : false); return ($i_hasil > 0 ? true : false);
} }
public function user_log(): void { public function user_log(): void {
$_user_id = $this->session->get('WEB_USER')['UserID'] ?? 0; $_user_id = $this->session->get('WEB_USER')['UserID'] ?? 0;
if ($_user_id === 0) return; if ($_user_id === 0) return;
$a_hasil = $this->db->fetchRow('SELECT * FROM app_logs WHERE UserID=? ORDER BY Last_Date DESC LIMIT 1', [$_user_id])->getData(); $a_hasil = $this->db->fetchRow('SELECT * FROM app_logs WHERE UserID=? ORDER BY Last_Date DESC LIMIT 1', [$_user_id]);
if (count($a_hasil) > 0) { if ($a_hasil) {
$this->db->update('app_operator', [ 'Last_IP' => $a_hasil['Last_IP'], 'Last_Date' => $a_hasil['Last_Date'] ], 'UserID=?', [$_user_id]); $this->db->update('app_operator', [ 'Last_IP' => $a_hasil['Last_IP'], 'Last_Date' => $a_hasil['Last_Date'] ], 'UserID=?', [$_user_id]);
} }
$this->db->runQuery('INSERT IGNORE INTO app_logs SELECT UserID,Token,IP,DateStamp FROM app_token WHERE Token=?', [$this->session->get('APP_TOKEN')]); $this->db->runQuery('INSERT IGNORE INTO app_logs SELECT UserID,Token,IP,DateStamp FROM app_token WHERE Token=?', [$this->session->get('APP_TOKEN')]);
@@ -143,9 +121,21 @@ final class Auth {
} }
public function getUserByToken(string $token): string { public function getUserByToken(string $token): string {
$hasil = $this->db->fetchField('SELECT Username FROM app_operator WHERE UserID IN (SELECT UserID FROM app_token WHERE Token=?) LIMIT 1', [$token ?? $this->session->get('APP_TOKEN')]); $hasil = $this->db->fetchField('SELECT Username FROM app_operator WHERE UserID IN (SELECT UserID FROM app_token WHERE Token=?) LIMIT 1', [$token ?? $this->session->get('APP_TOKEN')]) ?? '';
return $hasil; return $hasil;
} }
public function pos_login(int $id_cab, int $id_user, int $unix, string $ip): void {
if (empty($id_cab) || empty($id_user) || empty($ip)) return;
$this->db->delete('disp_login', 'CabID=? AND UserID=?', [$id_cab, $id_user]);
$this->db->runQuery('INSERT INTO disp_login (IP, CabID, UserID, Unix, Date) VALUES (?, ?, ?, ?, NOW())', [ $ip, $id_cab, $id_user, $unix ?? 0 ]);
}
public function pos_logout(int $id_cab, int $id_user, int $unix, string $ip): void {
if (empty($id_cab) || empty($id_user) || empty($ip)) return;
$this->db->delete('disp_login', 'CabID=? AND UserID=? AND IP=? AND Unix=?', [$id_cab, $id_user, $ip, $unix ?? 0 ]);
}
} }
?> ?>
+13 -9
View File
@@ -5,7 +5,7 @@
Module : /Data/Cache.php Module : /Data/Cache.php
Created By : Siau Pheng ([email protected]). Created By : Siau Pheng ([email protected]).
Created Date : March 16th, 2008. Created Date : March 16th, 2008.
Last Modified : September 3rd, 2026. Last Modified : October 12th, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -43,6 +43,7 @@ final class Cache {
private array $ini; private array $ini;
private array $web_app; private array $web_app;
private array $web_user; private array $web_user;
private array $my_menu = [];
private bool $is_login; private bool $is_login;
private bool $IS_DEV = false; private bool $IS_DEV = false;
protected Engine $app; protected Engine $app;
@@ -72,7 +73,7 @@ final class Cache {
$this->ini = [ $this->ini = [
'login' => [ 'css' => "", 'js' => "", 'app' => "", ], 'login' => [ 'css' => "", 'js' => "", 'app' => "", ],
'index' => [ 'css' => "", 'js' => "", 'app' => "", ], 'index' => [ 'css' => "", 'js' => "", 'app' => "", ],
'display' => [ 'css' => "", 'js' => "", 'app' => "", ], 'monitor' => [ 'css' => "", 'js' => "", 'app' => "", ],
]; ];
$this->save_ini(); $this->save_ini();
} else $this->ini = parse_ini_file($this->cachedir . "cache.ini", true); } else $this->ini = parse_ini_file($this->cachedir . "cache.ini", true);
@@ -134,7 +135,7 @@ final class Cache {
} }
if ($mode === 'index') { if ($mode === 'index') {
$_ajs = ['system/profile', 'system/chpasswd']; $_ajs = ['system/profile', 'system/chpasswd'];
$amenu = array_values($this->web_user['Merge_Menu'] ?? []); $amenu = array_values($this->my_menu ?? []);
$_ops = $this->db->fetchColumn("SELECT Link FROM app_menu WHERE Kode IN (?) AND Link<>'' ORDER BY Kode", [$amenu]); $_ops = $this->db->fetchColumn("SELECT Link FROM app_menu WHERE Kode IN (?) AND Link<>'' ORDER BY Kode", [$amenu]);
if ($_ops) $_ajs = array_merge($_ajs, $_ops); if ($_ops) $_ajs = array_merge($_ajs, $_ops);
$dirIterator = new \RecursiveDirectoryIterator($this->jsapp, \RecursiveDirectoryIterator::SKIP_DOTS); $dirIterator = new \RecursiveDirectoryIterator($this->jsapp, \RecursiveDirectoryIterator::SKIP_DOTS);
@@ -142,7 +143,7 @@ final class Cache {
$iterator = new \RecursiveIteratorIterator($dirFilter); $iterator = new \RecursiveIteratorIterator($dirFilter);
foreach ($iterator as $_file) { foreach ($iterator as $_file) {
if ($_file->isFile() && if ($_file->isFile() &&
!in_array($_file->getFilename(), ['login.js','index.js','display.js']) && !in_array($_file->getFilename(), ['login.js','index.js','monitor.js']) &&
array_any($_ajs, fn($js) => str_ends_with(str_replace(['_','\\'],['','/'],$_file->getRealPath()), $js.'.js'))) { array_any($_ajs, fn($js) => str_ends_with(str_replace(['_','\\'],['','/'],$_file->getRealPath()), $js.'.js'))) {
$afile[] = $_file->getRealPath(); $afile[] = $_file->getRealPath();
$itime += $_file->getMTime(); $itime += $_file->getMTime();
@@ -194,7 +195,10 @@ final class Cache {
public function Cache(string $mode = "index"): array { public function Cache(string $mode = "index"): array {
if ($mode === 'index') { if ($mode === 'index') {
$this->ini['index']['app'] = (string) $this->db->fetchField("SELECT JS FROM app_operator_profile WHERE UserID=?", [$this->web_user['UserID']]) ?? ''; $this->ini['index']['app'] = (string) $this->db->fetchField("SELECT JS FROM app_operator_profile WHERE UserID=?", [$this->web_user['UserID']]) ?? '';
$this->web_user['Merge_Menu'] = array_filter(array_unique(explode(",", $this->web_user['Menu_Grup'] .",". $this->web_user['Menu']))); $_menu = $this->db->fetchField("SELECT CONVERT(DECODE(Menu,?) USING UTF8MB4) Menu FROM app_operator WHERE UserID=?", [$this->web_app['PARENT_CODE'], $this->web_user['UserID']]) ?? '';
$_gmenu = '';
if ($this->web_user['Grup_MenuID'] > 0) $_gmenu = $this->db->fetchField('SELECT CONVERT(DECODE(Menu,?) USING UTF8MB4) Menu FROM app_menu_grup WHERE Grup_MenuID=?', [$this->web_app['PARENT_CODE'], $this->web_user['Grup_MenuID']]) ?? '';
$this->my_menu = array_filter(array_unique(explode(",", $_gmenu .",". $_menu)));
} }
$is_modif = false; $is_modif = false;
@@ -235,7 +239,7 @@ final class Cache {
return $acache; return $acache;
} }
public function Make_Cache(array $amode = ['login', 'index', 'display']): array { public function Make_Cache(array $amode = ['login', 'index', 'monitor']): array {
$is_modif = false; $is_modif = false;
foreach ($amode as $mode) { foreach ($amode as $mode) {
foreach (['css','js','app'] as $type) { foreach (['css','js','app'] as $type) {
@@ -252,11 +256,11 @@ final class Cache {
if ($is_modif === true) $this->save_ini(); if ($is_modif === true) $this->save_ini();
if (in_array('index', $amode)) { if (in_array('index', $amode)) {
$agrup = $this->db->fetchAll('SELECT Grup_MenuID,DECODE(Menu,?) Menu FROM app_menu_grup', [$this->web_app['PARENT_CODE']]); $agrup = $this->db->fetchAll('SELECT Grup_MenuID,CONVERT(DECODE(Menu,?) USING UTF8MB4) Menu FROM app_menu_grup', [$this->web_app['PARENT_CODE']]);
$auser = $this->db->fetchAll("SELECT A.*,IFNULL(B.JS,'') JS FROM (SELECT UserID,Grup_MenuID,DECODE(Menu,?) Menu FROM app_operator) A LEFT JOIN app_operator_profile B ON (A.UserID=B.UserID)", [$this->web_app['PARENT_CODE']]); $auser = $this->db->fetchAll("SELECT A.*,IFNULL(B.JS,'') JS FROM (SELECT UserID,Grup_MenuID,CONVERT(DECODE(Menu,?) USING UTF8MB4) Menu FROM app_operator) A LEFT JOIN app_operator_profile B ON (A.UserID=B.UserID)", [$this->web_app['PARENT_CODE']]);
foreach ($auser as $user) { foreach ($auser as $user) {
if (empty($user['JS']) || !file_exists($this->cachedir . $user['JS'])) { if (empty($user['JS']) || !file_exists($this->cachedir . $user['JS'])) {
$this->web_user['Merge_Menu'] = array_filter(array_unique(explode(",", $user['Menu'] .",". ($agrup[$user['Grup_MenuID']]['Menu'] ?? '')))); $this->my_menu = array_filter(array_unique(explode(",", $user['Menu'] .",". ($agrup[$user['Grup_MenuID']]['Menu'] ?? ''))));
[ $afile, $lastmod ] = $this->gen_file('index', 'app'); [ $afile, $lastmod ] = $this->gen_file('index', 'app');
$cachefile = md5('index-app-' . (string) $user['UserID'] . $lastmod) . '.js'; $cachefile = md5('index-app-' . (string) $user['UserID'] . $lastmod) . '.js';
+4 -4
View File
@@ -2,10 +2,10 @@
/******************************************************************* /*******************************************************************
PHP Class : /Data/Xls.php PHP Class : /Data/Xlsx.php
Created By : Siau Pheng (siaupheng@3fonia.com). Created By : Siau Pheng (siaupheng@3fonia.com).
Created Date : November 18th, 2012. Created Date : November 18th, 2012.
Last Modified : October 1st, 2026. Last Modified : October 24th, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -130,8 +130,8 @@ final class Xlsx {
$this->sheet->writeTo('A'.(string) $this->baris, ''); $this->sheet->writeTo('A'.(string) $this->baris, '');
} }
public function show_headers(): void { public function show_headers(bool $is_hide=false): void {
$this->sheet->writeHeader(array_column($this->atabel, 'n')) if (!$is_hide) $this->sheet->writeHeader(array_column($this->atabel, 'n'))
->applyFontStyleBold() ->applyFontStyleBold()
->applyTextCenter() ->applyTextCenter()
->applyBorder(Style::BORDER_STYLE_THIN); ->applyBorder(Style::BORDER_STYLE_THIN);
+39 -1
View File
@@ -6,7 +6,7 @@
Desc. : Fungsi Custom Lain Lain Desc. : Fungsi Custom Lain Lain
Created By : Siau Pheng (siaupheng@3fonia.com). Created By : Siau Pheng (siaupheng@3fonia.com).
Created Date : January 27th, 2008. Created Date : January 27th, 2008.
Last Modified : August 21st, 2026. Last Modified : October 27th, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -362,3 +362,41 @@ if (!function_exists('random_color')) {
return "rgba(".$col_r.",".$col_g.",".$col_b.",".$trans.")"; return "rgba(".$col_r.",".$col_g.",".$col_b.",".$trans.")";
} }
} }
if (!function_exists('gen_uuidv4')) {
function gen_uuidv4(): string {
$data = random_bytes(16);
// Set version to 0100 (UUID v4)
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
// Set bits 6-7 to 10
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
}
if (!function_exists('gen_uuidv7')) {
function gen_uuidv7(int $time=0): string {
// Get timestamp in milliseconds (48 bits)
if (empty($time)) $time = (int) floor(microtime(true) * 1000);
// Generate 16 bytes of random data
$randomBytes = random_bytes(16);
// Pack the 48-bit timestamp into the first 6 bytes
$timeHex = str_pad(dechex($time), 12, '0', STR_PAD_LEFT);
$bytes = hex2bin($timeHex);
// Append 10 bytes of randomness (we need 16 total bytes)
$bytes .= substr($randomBytes, 0, 10);
// Apply the version bits (Version 7 -> 0b0111) onto byte 6
$bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x70);
// Apply the variant bits (Variant 1 -> 0b10xx) onto byte 8
$bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);
// Unpack to hex string and format with hyphens
$hex = bin2hex($bytes);
return sprintf('%08s-%04s-%04s-%04s-%012s',
substr($hex, 0, 8),
substr($hex, 8, 4),
substr($hex, 12, 4),
substr($hex, 16, 4),
substr($hex, 20, 12)
);
}
}
+5 -5
View File
@@ -5,7 +5,7 @@
Module : /Printer/PrintHTML.php Module : /Printer/PrintHTML.php
Created By : Siau Pheng (siaupheng@3fonia.com). Created By : Siau Pheng (siaupheng@3fonia.com).
Created Date : June 15th, 2008. Created Date : June 15th, 2008.
Last Modified : October 2nd, 2026. Last Modified : October 24th, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -119,16 +119,16 @@ final class PrintHTML {
echo '<br />'; echo '<br />';
} }
public function show_headers(): void { public function show_headers(bool $is_hide=false): void {
vprintf('<table class="%s"><thead>', [$this->css]); vprintf('<table class="%s"><thead>', [$this->css]);
// 0-n, 1=l, 2-f, 3-a, 4-k // 0-n, 1=l, 2-f, 3-a, 4-k
$head_out = "<tr>"; $head_out = "<tr>";
foreach ($this->atabel as $tabel) { foreach ($this->atabel as $tabel) {
if ($tabel['k'] === 0) continue; if ($tabel['k'] === 0) continue;
$head_out .= $this->set_align($tabel['n'], $tabel['l'], "C", false, true, $tabel['k']); $head_out .= $this->set_align($tabel['n'], $tabel['l'], "C", false, true, $tabel['k'], ($is_hide?'d-none':''));
} }
$head_out .= "</tr>"; $head_out .= "</tr>";
echo "</thead>".$head_out."<tbody>"; echo $head_out."</thead><tbody>";
// start content row // start content row
} }
@@ -151,7 +151,7 @@ final class PrintHTML {
if ($line===true) { if ($line===true) {
return "<tr><td colspan=\"".count($this->atabel)."\" class=\"".$tstyle." ".$extra."\">".$text."</td></tr>"; return "<tr><td colspan=\"".count($this->atabel)."\" class=\"".$tstyle." ".$extra."\">".$text."</td></tr>";
} else { } else {
return (($head===true)?"<th":"<td").(($col>1)?" colspan=\"".(string)$col."\"":"")." class=\"".$tstyle." ".$extra."\" ".(($head===true)?"width=\"".(string)$width."\"":"").">".$text.(($head===true)?"</th>":"</td>"); return (($head===true)?"<th":"<td").(($col>1)?" colspan=\"".(string) $col."\"":"")." class=\"".$tstyle." ".$extra."\" ".(($head===true)?"width=\"".(string) $width."\"":"").">".$text.(($head===true)?"</th>":"</td>");
} }
} }
+24 -23
View File
@@ -5,7 +5,7 @@
Module : /Web/Menu.php Module : /Web/Menu.php
Created By : Siau Pheng (siaupheng@3fonia.com). Created By : Siau Pheng (siaupheng@3fonia.com).
Created Date : January 16th, 2008. Created Date : January 16th, 2008.
Last Modified : August 22nd, 2026. Last Modified : October 30th, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -31,8 +31,9 @@ use \flight\database\SimplePdo;
*/ */
final class Menu { final class Menu {
private array $webapp; private array $web_app;
private array $webuser; private array $web_user;
private array $my_menu;
protected Engine $app; protected Engine $app;
protected SimplePdo $db; protected SimplePdo $db;
protected Session $session; protected Session $session;
@@ -44,27 +45,28 @@ final class Menu {
$this->app = $app; $this->app = $app;
$this->db = $app->db(); $this->db = $app->db();
$this->session = $app->session(); $this->session = $app->session();
$this->webapp = $this->session->get('WEB_APP', ['APP_MODULE'=>'', 'PARENT_SITE'=>'', 'PARENT_SUB'=>false]); $this->web_app = $this->session->get('WEB_APP', ['APP_MODULE'=>'', 'PARENT_SITE'=>'', 'PARENT_SUB'=>false]);
$this->webuser = $this->session->get('WEB_USER', ['Menu'=>'', 'Menu_Grup'=>'', 'MENU_MODUL'=>[]]); $this->web_user = $this->session->get('WEB_USER', ['Menu'=>'', 'Menu_Grup'=>'', 'MENU_MODUL'=>[]]);
$_gmenu = '';
$_umenu = $this->db->fetchField("SELECT CONVERT(DECODE(Menu,?) USING UTF8MB4) Menu FROM app_operator WHERE UserID=?", [$this->web_app['PARENT_CODE'], $this->web_user['UserID']]) ?? '';
if ($this->web_user['Grup_MenuID'] > 0) $_gmenu = $this->db->fetchField('SELECT CONVERT(DECODE(Menu,?) USING UTF8MB4) Menu FROM app_menu_grup WHERE Grup_MenuID=?', [$this->web_app['PARENT_CODE'], $this->web_user['Grup_MenuID']]) ?? '';
$this->my_menu = array_unique(array_filter(explode(",", $_gmenu.",".$_umenu)));
} }
public function Sidebar(): string { public function Sidebar(): string {
$a_modul = []; $a_modul = [];
$is_modul = ($this->webapp['APP_MODULE'] !== '') ? true : false; $is_modul = !empty($this->web_app['APP_MODULE']);
if ($is_modul == true) { if ($is_modul === true) {
$a_modul[] = $this->webapp['APP_MODULE']; $a_modul[] = $this->web_app['APP_MODULE'];
} else { } else {
$atmp = $this->db->fetchAll('SELECT * FROM app_menu WHERE Level=0 AND Aktif=1 AND Modul<>"IND" ORDER BY Urut'); $atmp = $this->db->fetchAll('SELECT * FROM app_menu WHERE Level=0 AND Aktif=1 AND Modul<>"IND" ORDER BY Urut');
foreach ($atmp as $tmp) { foreach ($atmp as $tmp) $a_modul[] = $tmp['Modul'];
$a_modul[] = $tmp['Modul'];
}
} }
$my_menu = array_unique(array_filter(explode(",", $this->webuser['Menu_Grup'].",".$this->webuser['Menu'])));
$tsql = 'SELECT * FROM app_menu WHERE Aktif=1 AND Modul IN (?) AND Kode IN (?)'; $tsql = 'SELECT * FROM app_menu WHERE Aktif=1 AND Modul IN (?) AND Kode IN (?)';
if ($is_modul == true) $tsql .= ' AND Level>0'; if ($is_modul == true) $tsql .= ' AND Level>0';
$amodul = $this->db->fetchAll($tsql . ' ORDER BY Kode', [$a_modul, $my_menu]); $amodul = $this->db->fetchAll($tsql . ' ORDER BY Kode', [$a_modul, $this->my_menu]);
$thasil = ''; $thasil = '';
$last_level = 0; $last_level = 0;
@@ -88,10 +90,10 @@ final class Menu {
public function Header(): string { public function Header(): string {
$a_modul = []; $a_modul = [];
$is_modul = ($this->webapp['APP_MODULE'] !== '') ? true : false; $is_modul = !empty($this->web_app['APP_MODULE']);
$mulai_level = 0; $mulai_level = 0;
if ($is_modul == true) { if ($is_modul === true) {
$a_modul[] = $this->webapp['APP_MODULE']; $a_modul[] = $this->web_app['APP_MODULE'];
$mulai_level = 1; $mulai_level = 1;
} else { } else {
$atmp = $this->db->fetchAll('SELECT * FROM app_menu WHERE Level=0 AND Aktif=1 AND Modul<>"IND" ORDER BY Urut'); $atmp = $this->db->fetchAll('SELECT * FROM app_menu WHERE Level=0 AND Aktif=1 AND Modul<>"IND" ORDER BY Urut');
@@ -100,11 +102,10 @@ final class Menu {
} }
} }
$my_menu = array_unique(array_filter(explode(",", $this->webuser['Menu_Grup'].",".$this->webuser['Menu'])));
$tsql = 'SELECT * FROM app_menu WHERE Aktif=1 AND Modul IN (?) AND Kode IN (?)'; $tsql = 'SELECT * FROM app_menu WHERE Aktif=1 AND Modul IN (?) AND Kode IN (?)';
if ($is_modul == true) $tsql .= ' AND Level>0'; if ($is_modul == true) $tsql .= ' AND Level>0';
$amodul = $this->db->fetchAll($tsql . ' ORDER BY Kode', [$a_modul, $my_menu]); $amodul = $this->db->fetchAll($tsql . ' ORDER BY Kode', [$a_modul, $this->my_menu]);
$thasil = ''; $thasil = '';
$last_level = 0; $last_level = 0;
@@ -134,15 +135,15 @@ final class Menu {
public function Modul(): string { public function Modul(): string {
$t_modul = ''; $t_modul = '';
if (is_array($this->webuser['MENU_MODUL']) && count($this->webuser['MENU_MODUL'])>0) { if (is_array($this->web_user['MENU_MODUL']) && count($this->web_user['MENU_MODUL'])>0) {
$t_modul = '<div class="dropdown-divider"></div>'; $t_modul = '<div class="dropdown-divider"></div>';
if ($this->app->request()->getVar('SERVER_ADDR') === '127.0.0.1') { if ($this->app->request()->getVar('SERVER_ADDR') === '127.0.0.1') {
$href1 = '//127.0.0.1/'; $href1 = '//127.0.0.1/';
$href2 = ""; $href2 = "";
} else { } else {
$_host = $this->app->request()->getVar('HTTP_ORIGIN') ?? $this->app->request()->getVar('HTTP_HOST') ?? $this->webapp['PARENT_SITE']; $_host = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_HOST'] ?? $this->web_app['PARENT_SITE'];
$_host = str_replace(['http://','https://'], "", $_host); $_host = str_replace(['http://','https://'], "", $_host);
if ($this->webapp['PARENT_SUB'] === true) { if ($this->web_app['PARENT_SUB'] === true) {
$_subs = explode('.', $_host); $_subs = explode('.', $_host);
if (count($_subs) > 2) array_shift($_subs); if (count($_subs) > 2) array_shift($_subs);
$href1 = "//"; $href1 = "//";
@@ -152,7 +153,7 @@ final class Menu {
$href2 = ""; $href2 = "";
} }
} }
foreach ($this->webuser['MENU_MODUL'] as $modul) { foreach ($this->web_user['MENU_MODUL'] as $modul) {
$href = $href1 . $modul['APP_PATH'] . $href2 . '/' . $this->session->get('APP_TOKEN'); $href = $href1 . $modul['APP_PATH'] . $href2 . '/' . $this->session->get('APP_TOKEN');
$t_modul .= '<a class="dropdown-item slink" href="'.$href.'"><i class="'.$modul['Icon'].' me-2"></i> '.$modul['Judul'].'</a>'; $t_modul .= '<a class="dropdown-item slink" href="'.$href.'"><i class="'.$modul['Icon'].' me-2"></i> '.$modul['Judul'].'</a>';
} }
@@ -173,7 +174,7 @@ final class Menu {
$apage = $acus_page[$page]; $apage = $acus_page[$page];
} else if ($page <> "") { } else if ($page <> "") {
$apage = $db->fetchRow('SELECT * FROM app_menu WHERE Kode=?', [$page]); $apage = $db->fetchRow('SELECT * FROM app_menu WHERE Kode=?', [$page]);
$apage['Width'] = (int)$apage['Width']; $apage['Width'] = (int) $apage['Width'];
} }
$ajson['iswindow'] = ($apage['Width'] > 0) ? true : false; $ajson['iswindow'] = ($apage['Width'] > 0) ? true : false;
+1 -10
View File
@@ -5,7 +5,7 @@
Module : /Web/Param.php Module : /Web/Param.php
Created By : Siau Pheng (siaupheng@3fonia.com). Created By : Siau Pheng (siaupheng@3fonia.com).
Created Date : November 14th, 2013. Created Date : November 14th, 2013.
Last Modified : September 16th, 2026. Last Modified : October 16th, 2026.
(c) 3FONIA Software; WWW.3FONIA.COM. (c) 3FONIA Software; WWW.3FONIA.COM.
@@ -75,15 +75,6 @@ final class Param {
return self::$__ahasil; return self::$__ahasil;
} }
//delete
public static function Get_Text(string $key, mixed $def = null): mixed {
return self::Get($key, $def);
}
//delete
public static function Get_ArrayCBOX() {
return [];
}
} }
?> ?>