License updated to GPLv3

🧲 New features
Custom user role permissions
Employee edit form updated
Employee daily task list
Attendance and employee distribution charts on dashboard
Improvements to company structure and company assets module
Improved tables for displaying data in several modules
Faster data loading (specially for employee module)
Initials based profile pictures
Re-designed login page
Re-designed user profile page
Improvements to filtering
New REST endpoints for employee qualifications

🐛 Bug fixes
Fixed, issue with managers being able to create performance reviews for employees who are not their direct reports
Fixed, issues related to using full profile image instead of using smaller version of profile image
Changing third gender to other
Improvements and fixes for internal frontend data caching
This commit is contained in:
Thilina Pituwala
2020-10-31 19:02:37 +01:00
parent 86b8345505
commit b1df0037db
29343 changed files with 867614 additions and 2191082 deletions
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -35,7 +35,7 @@ class AttendanceAdapter extends AdapterBase {
getFormFields() {
return [
['employee', {
label: 'Employee', type: 'select2', 'allow-null': false, 'remote-source': ['Employee', 'id', 'first_name+last_name'],
label: 'Employee', type: 'select2', 'allow-null': false, 'remote-source': ['Employee', 'id', 'first_name+last_name', 'getActiveSubordinateEmployees'],
}],
['id', { label: 'ID', type: 'hidden' }],
['in_time', { label: 'Time-In', type: 'datetime' }],
+4
View File
@@ -0,0 +1,4 @@
import { AttendanceGraphAdapter, TimeUtilizationGraphAdapter } from './lib';
window.AttendanceGraphAdapter = AttendanceGraphAdapter;
window.TimeUtilizationGraphAdapter = TimeUtilizationGraphAdapter;
+254
View File
@@ -0,0 +1,254 @@
/*
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
/* global nv, d3 */
import BaseGraphAdapter from '../../../api/BaseGraphAdapter';
/*
* AttendanceGraphAdapter
*/
class AttendanceGraphAdapter extends BaseGraphAdapter {
getFormFields() {
return [];
}
getFilters() {
return [
['employee', {
label: 'Employee', type: 'select2', 'allow-null': true, 'null-label': 'All Employees', 'remote-source': ['Employee', 'id', 'first_name+last_name'],
}],
['start', { label: 'Start Date', type: 'date', validation: '' }],
['end', { label: 'End Date', type: 'date', validation: '' }],
];
}
get() {
this.initFieldMasterData();
this.getTimeUtilization();
}
doCustomFilterValidation(params) {
const $errorElement = $(`#${this.table}_filter_error`);
$errorElement.html('');
$errorElement.hide();
if (Date.parse(params.start).getTime() > Date.parse(params.end).getTime()) {
$errorElement.html('End date should be a later date than start date');
$errorElement.show();
return false;
}
const dateDiff = (Date.parse(params.end).getTime() - Date.parse(params.start).getTime()) / (1000 * 60 * 60 * 24);
if (dateDiff > 45 && (params.employee === undefined || params.employee == null || params.employee === 'NULL')) {
$errorElement.html('Differance between start and end dates should not be more than 45 days, when creating chart for all employees');
$errorElement.show();
return false;
} if (dateDiff > 90) {
$errorElement.html('Differance between start and end dates should not be more than 90 days');
$errorElement.show();
return false;
}
return true;
}
getTimeUtilization(object, callBackData) {
object = {};
if (this.filter != null && this.filter !== undefined) {
if (this.filter.employee !== 'NULL') {
object.employee = this.filter.employee;
}
object.start = this.filter.start;
object.end = this.filter.end;
}
const reqJson = JSON.stringify(object);
callBackData = (callBackData === undefined || callBackData === null) ? [] : callBackData;
callBackData.callBackData = [];
callBackData.callBackSuccess = 'getAttendanceSuccessCallBack';
callBackData.callBackFail = 'getAttendanceFailCallBack';
this.customAction('getAttendance', 'admin=charts', reqJson, callBackData);
}
getAttendanceFailCallBack(callBackData) {
this.showMessage('Error Occured while getting data for chart', callBackData);
}
getAttendanceSuccessCallBack(callBackData) {
const that = this;
const filterHtml = that.getTableTopButtonHtml();
$('#tabPageAttendanceGraph svg').remove();
$('#tabPageAttendanceGraph div').remove();
const $tabPageAttendanceGraph = $('#tabPageAttendanceGraph');
$tabPageAttendanceGraph.html('');
$tabPageAttendanceGraph.html(`${filterHtml}<svg></svg>`);
nv.addGraph(() => {
const chart = nv.models.multiBarChart()
.margin({ bottom: 200 })
.transitionDuration(0)
.reduceXTicks(true) // If 'false', every single x-axis tick label will be rendered.
.rotateLabels(45) // Angle to rotate x-axis labels.
.showControls(false) // Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1); // Distance between each group of bars.
chart.yAxis
.tickFormat(d3.format(',.1f'));
chart.dispatch.on('stateChange', (e) => { nv.log('New State:', JSON.stringify(e)); });
chart.tooltip((key, x, y, e, graph) => `<p><strong>${key}</strong></p>`
+ `<p>${y} on ${x}</p>`);
d3.select('#tabPageAttendanceGraph svg')
.datum(callBackData)
.call(chart);
return chart;
});
}
getHelpLink() {
return 'https://icehrm.gitbook.io/icehrm/insights/analytics';
}
}
/*
* TimeUtilizationGraphAdapter
*/
class TimeUtilizationGraphAdapter extends BaseGraphAdapter {
getFormFields() {
return [];
}
getFilters() {
return [
['employee', {
label: 'Employee', type: 'select2', 'allow-null': true, 'null-label': 'All Employees', 'remote-source': ['Employee', 'id', 'first_name+last_name'],
}],
['start', { label: 'Start Date', type: 'date', validation: '' }],
['end', { label: 'End Date', type: 'date', validation: '' }],
];
}
get() {
this.initFieldMasterData();
this.getTimeUtilization();
}
doCustomFilterValidation(params) {
const $errorElement = $(`#${this.table}_filter_error`);
$errorElement.html('');
$errorElement.hide();
if (Date.parse(params.start).getTime() > Date.parse(params.end).getTime()) {
$errorElement.html('End date should be a later date than start date');
$errorElement.show();
return false;
}
const dateDiff = (Date.parse(params.end).getTime() - Date.parse(params.start).getTime()) / (1000 * 60 * 60 * 24);
if (dateDiff > 45 && (params.employee === undefined || params.employee == null || params.employee === 'NULL')) {
$errorElement.html('Differance between start and end dates should not be more than 45 days, when creating chart for all employees');
$errorElement.show();
return false;
} if (dateDiff > 90) {
$errorElement.html('Differance between start and end dates should not be more than 90 days');
$errorElement.show();
return false;
}
return true;
}
getTimeUtilization(object, callBackData) {
object = {};
if (this.filter != null && this.filter !== undefined) {
if (this.filter.employee !== 'NULL') {
object.employee = this.filter.employee;
}
object.start = this.filter.start;
object.end = this.filter.end;
}
const reqJson = JSON.stringify(object);
callBackData = (callBackData === undefined || callBackData === null) ? [] : callBackData;
callBackData.callBackData = [];
callBackData.callBackSuccess = 'getTimeUtilizationSuccessCallBack';
callBackData.callBackFail = 'getTimeUtilizationFailCallBack';
this.customAction('getTimeUtilization', 'admin=charts', reqJson, callBackData);
}
getTimeUtilizationFailCallBack(callBackData) {
this.showMessage('Error Occured while getting data for chart', callBackData);
}
getTimeUtilizationSuccessCallBack(callBackData) {
const that = this;
const filterHtml = that.getTableTopButtonHtml();
$('#tabPageTimeUtilizationGraph svg').remove();
$('#tabPageTimeUtilizationGraph div').remove();
const $tabPageTimeUtilizationGraph = $('#tabPageTimeUtilizationGraph');
$tabPageTimeUtilizationGraph.html('');
$tabPageTimeUtilizationGraph.html(`${filterHtml}<svg></svg>`);
nv.addGraph(() => {
const chart = nv.models.multiBarChart()
.margin({ bottom: 200 })
.transitionDuration(0)
.reduceXTicks(true) // If 'false', every single x-axis tick label will be rendered.
.rotateLabels(45) // Angle to rotate x-axis labels.
.showControls(true) // Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1); // Distance between each group of bars.
chart.yAxis
.tickFormat(d3.format(',.1f'));
d3.select('#tabPageTimeUtilizationGraph svg')
.datum(callBackData)
.call(chart);
chart.dispatch.on('stateChange', (e) => { nv.log('New State:', JSON.stringify(e)); });
chart.tooltip((key, x, y, e, graph) => `<p><strong>${key}</strong></p>`
+ `<p>${y} on ${x}</p>`);
return chart;
});
}
getHelpLink() {
return 'https://icehrm.gitbook.io/icehrm/insights/analytics';
}
}
module.exports = { AttendanceGraphAdapter, TimeUtilizationGraphAdapter };
+17 -2
View File
@@ -1,4 +1,19 @@
import { CompanyStructureAdapter, CompanyGraphAdapter } from './lib';
import IceDataPipe from "../../../api/IceDataPipe";
import CustomFieldAdapter from "../../../api/ReactCustomFieldAdapter";
window.CompanyStructureAdapter = CompanyStructureAdapter;
window.CompanyGraphAdapter = CompanyGraphAdapter;
function init(data) {
const modJsList = {};
modJsList.tabCompanyStructure = new CompanyStructureAdapter('CompanyStructure');
modJsList.tabCompanyStructure.setObjectTypeName('Company Structure');
modJsList.tabCompanyStructure.setDataPipe(new IceDataPipe(modJsList.tabCompanyStructure));
modJsList.tabCompanyStructure.setAccess(data.permissions.CompanyStructure);
modJsList.tabCompanyGraph = new CompanyGraphAdapter('CompanyStructure');
window.modJs = modJsList.tabCompanyStructure;
window.modJsList = modJsList;
}
window.initAdminCompanyStructure = init;
+77 -2
View File
@@ -5,9 +5,10 @@
*/
/* global d3, nv */
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
import AdapterBase from '../../../api/AdapterBase';
class CompanyStructureAdapter extends AdapterBase {
class CompanyStructureAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -32,6 +33,36 @@ class CompanyStructureAdapter extends AdapterBase {
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'title',
sorter: true,
},
{
title: 'Address',
dataIndex: 'address',
},
{
title: 'Type',
dataIndex: 'type',
},
{
title: 'Country',
dataIndex: 'country',
},
{
title: 'Time Zone',
dataIndex: 'timezone',
},
{
title: 'Parent Structure',
dataIndex: 'parent',
},
];
}
getFormFields() {
return [
['id', { label: 'ID', type: 'hidden', validation: '' }],
@@ -73,12 +104,56 @@ class CompanyStructureAdapter extends AdapterBase {
*/
class CompanyGraphAdapter extends CompanyStructureAdapter {
class CompanyGraphAdapter extends AdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.nodeIdCounter = 0;
}
getDataMapping() {
return [
'id',
'title',
'address',
'type',
'country',
'timezone',
'parent',
];
}
getHeaders() {
return [
{ sTitle: 'ID', bVisible: false },
{ sTitle: 'Name' },
{ sTitle: 'Address', bSortable: false },
{ sTitle: 'Type' },
{ sTitle: 'Country', sClass: 'center' },
{ sTitle: 'Time Zone' },
{ sTitle: 'Parent Structure' },
];
}
getFormFields() {
return [
['id', { label: 'ID', type: 'hidden', validation: '' }],
['title', { label: 'Name', type: 'text', validation: '' }],
['description', { label: 'Details', type: 'textarea', validation: '' }],
['address', { label: 'Address', type: 'textarea', validation: 'none' }],
['type', { label: 'Type', type: 'select', source: [['Company', 'Company'], ['Head Office', 'Head Office'], ['Regional Office', 'Regional Office'], ['Department', 'Department'], ['Unit', 'Unit'], ['Sub Unit', 'Sub Unit'], ['Other', 'Other']] }],
['country', { label: 'Country', type: 'select2', 'remote-source': ['Country', 'code', 'name'] }],
['timezone', {
label: 'Time Zone', type: 'select2', 'allow-null': false, 'remote-source': ['Timezone', 'name', 'details', 'getTimezonesWithOffset'],
}],
['parent', {
label: 'Parent Structure', type: 'select', 'allow-null': true, 'remote-source': ['CompanyStructure', 'id', 'title'],
}],
['heads', {
label: 'Heads', type: 'select2multi', 'allow-null': true, 'remote-source': ['Employee', 'id', 'first_name+last_name'],
}],
];
}
convertToTree(data) {
const ice = {};
ice.id = -1;
+194
View File
@@ -1,8 +1,16 @@
/* global document */
/*
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
import React from 'react';
import {
Space, Card, Spin, Row, Col,
} from 'antd';
import { Donut, Pie } from '@antv/g2plot';
import ReactDOM from 'react-dom';
import AdapterBase from '../../../api/AdapterBase';
import TaskList from '../../../components/TaskList';
class DashboardAdapter extends AdapterBase {
getDataMapping() {
@@ -19,6 +27,7 @@ class DashboardAdapter extends AdapterBase {
get(callBackData) {
this.initializeReactDashboard();
}
@@ -51,6 +60,191 @@ class DashboardAdapter extends AdapterBase {
getInitDataFailCallBack(callBackData) {
}
getSpinner() {
return (
<Row>
<Col span={8}> </Col>
<Col span={8}><Spin size="large" /></Col>
<Col span={8}> </Col>
</Row>
);
}
initializeReactDashboard() {
//this.drawCompanyLeaveEntitlementChart();
this.drawOnlineOfflineEmployeeChart();
this.drawEmployeeDistributionChart();
this.buildTaskList();
}
buildTaskList() {
document.getElementById('TaskListWrap').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
document.getElementById('TaskListLoader'),
);
this.apiClient
.get('tasks')
.then((data) => {
document.getElementById('TaskListWrap').style.display = 'block';
ReactDOM.render(
<TaskList tasks={data.data} />,
document.getElementById('TaskList'),
);
ReactDOM.unmountComponentAtNode(document.getElementById('TaskListLoader'));
});
}
drawEmployeeDistributionChart() {
document.getElementById('EmployeeDistributionChart').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
document.getElementById('EmployeeDistributionChartLoader'),
);
this.apiClient
.get('charts/employees-distribution')
.then((data) => {
const chartData = Object.keys(data.data).map((key) => ({
type: key.charAt(0).toUpperCase() + key.slice(1),
value: data.data[key],
}));
const props = {
forceFit: true,
title: {
visible: true,
text: 'Employee Distribution',
},
description: {
visible: false,
text: '',
},
statistic: {
visible: true,
content: {
value: chartData.reduce((acc, item) => acc + item.value, 0),
name: 'Total',
},
},
legend: {
visible: true,
position: 'bottom-center',
},
radius: 0.8,
padding: 'auto',
data: chartData,
angleField: 'value',
colorField: 'type',
label: {
visible: true,
type: 'outer',
offset: 20,
},
};
ReactDOM.unmountComponentAtNode(document.getElementById('EmployeeDistributionChartLoader'));
document.getElementById('EmployeeDistributionChart').style.display = 'block';
const plot = new Pie(document.getElementById('EmployeeDistributionChart'), props);
plot.render();
});
}
drawOnlineOfflineEmployeeChart() {
document.getElementById('EmployeeOnlineOfflineChart').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
document.getElementById('EmployeeOnlineOfflineChartLoader'),
);
this.apiClient
.get('charts/employee-check-ins')
.then((data) => {
const chartData = Object.keys(data.data).map((key) => ({
type: key,
value: data.data[key],
}));
const props = {
forceFit: true,
title: {
visible: true,
text: 'Employee Check-Ins',
},
description: {
visible: false,
text: '',
},
statistic: {
visible: true,
content: {
value: chartData.reduce((acc, item) => acc + item.value, 0),
name: 'Total',
},
},
legend: {
visible: true,
position: 'bottom-center',
},
radius: 0.8,
padding: 'auto',
data: chartData,
angleField: 'value',
colorField: 'type',
};
ReactDOM.unmountComponentAtNode(document.getElementById('EmployeeOnlineOfflineChartLoader'));
document.getElementById('EmployeeOnlineOfflineChart').style.display = 'block';
const donutPlot = new Donut(document.getElementById('EmployeeOnlineOfflineChart'), props);
donutPlot.render();
});
}
drawCompanyLeaveEntitlementChart() {
document.getElementById('CompanyLeaveEntitlementChart').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
document.getElementById('CompanyLeaveEntitlementChartLoader'),
);
this.apiClient
.get('charts/company-leave-entitlement')
.then((data) => {
const chartData = Object.keys(data.data).map((key) => ({
type: key,
value: data.data[key],
}));
const props = {
forceFit: true,
title: {
visible: true,
text: 'Company Vacation Usage',
},
description: {
visible: false,
text: '',
},
statistic: {
visible: true,
content: {
value: chartData.reduce((acc, item) => acc + item.value, 0),
name: 'Total',
},
},
legend: {
visible: true,
position: 'bottom-center',
},
radius: 0.8,
padding: 'auto',
data: chartData,
angleField: 'value',
colorField: 'type',
};
ReactDOM.unmountComponentAtNode(document.getElementById('CompanyLeaveEntitlementChartLoader'));
document.getElementById('CompanyLeaveEntitlementChart').style.display = 'block';
const donutPlot = new Donut(document.getElementById('CompanyLeaveEntitlementChart'), props);
donutPlot.render();
});
}
}
module.exports = { DashboardAdapter };
+2 -2
View File
@@ -188,7 +188,7 @@ class DataImportAdapter extends AdapterBase {
const deleteButton = '<img class="tableActionButton" src="_BASE_images/delete.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Delete" onclick="modJs.deleteRow(_id_);return false;"></img>';
const cloneButton = '<img class="tableActionButton" src="_BASE_images/clone.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Copy" onclick="modJs.copyRow(_id_);return false;"></img>';
let html = '<div style="width:130px;">_edit__download__clone__delete_</div>';
let html = '<div style="width:132px;">_edit__download__clone__delete_</div>';
if (this.showAddNew) {
@@ -268,7 +268,7 @@ class DataImportFileAdapter extends AdapterBase {
const deleteButton = '<img class="tableActionButton" src="_BASE_images/delete.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Delete" onclick="modJs.deleteRow(_id_);return false;"></img>';
const cloneButton = '<img class="tableActionButton" src="_BASE_images/clone.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Copy" onclick="modJs.copyRow(_id_);return false;"></img>';
let html = '<div style="width:130px;">_edit__process__clone__delete_</div>';
let html = '<div style="width:132px;">_edit__process__clone__delete_</div>';
if (this.showAddNew) {
@@ -0,0 +1,260 @@
import React, {Component} from 'react';
import {Col, Card, Skeleton, Avatar, Input, Row, Descriptions, Typography, Table, Space, Button, Tag, message, Tabs} from 'antd';
import {
FilterOutlined,
EditOutlined,
PhoneTwoTone,
MailTwoTone,
SyncOutlined,
} from '@ant-design/icons';
import TagList from "../../../../components/TagList";
const { Search } = Input;
const { Title, Text } = Typography;
const { TabPane } = Tabs;
class EmployeeProfile extends React.Component {
state = {
loading: true,
};
constructor(props) {
super(props);
}
updateProfileImage() {
showUploadDialog(
`profile_image_${this.props.element.id}_${(new Date()).getTime()}`,
'Upload Profile Image',
'profile_image',
this.props.element.id,
`profile_image_${this.props.element.id}`,
'function',
'reloadCurrentElement',
'image'
);
}
getEditButtonJsx() {
return (<>
{this.props.loading &&
<Tag icon={<SyncOutlined spin/>} color="processing">
{this.props.adapter.gt('Edit')}
</Tag>
}
{!this.props.loading &&
<Tag icon={<EditOutlined/>} color="processing"
onClick={() => modJs.edit(this.props.element.id)}>
{this.props.adapter.gt('Edit')}
</Tag>
}
</>);
}
getTabViewEmployeeFilterButtonJsx(tab) {
return (
<Tag icon={<EditOutlined/>} color="processing"
onClick={() => {switchTab(tab, {employee: this.props.element.id})}}>
{this.props.adapter.gt('Edit')}
</Tag>
);
}
render() {
return (
<>
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
<Col span={24}>
<Card title={this.props.adapter.gt('Employee Profile')}
extra={this.getEditButtonJsx()}
style={{ width: '100%' }}
>
<Space size={'large'}>
<Avatar size={140} src={this.props.element.image} onClick={() => this.updateProfileImage()}/>
<Space direction={'vertical'}>
<Title level={4}>{`${this.props.element.first_name} ${this.props.element.last_name}`}</Title>
<Space>
<PhoneTwoTone />
<Text copyable>{` ${this.props.element.mobile_phone || ''}`}</Text>
</Space>
<Space>
<MailTwoTone />
<Text copyable>{` ${this.props.element.work_email || ''}`}</Text>
</Space>
</Space>
<Descriptions title="" bordered style={{width: '100%', padding: '10px'}}>
<Descriptions.Item label={this.props.adapter.gt('Employee Number')} span={3}>
{this.props.element.employee_id}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('ID Number')} span={3}>
{this.props.element.nic_num || ''}
</Descriptions.Item>
{this.props.element.ssn_num && this.props.element.ssn_num !== '' &&
<Descriptions.Item label={this.props.adapter.gt('Social Security Number')} span={3}>
{this.props.element.ssn_num || ''}
</Descriptions.Item>
}
</Descriptions>
</Space>
</Card>
</Col>
</Row>
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
<Tabs type="card" style={{ width: '100%' }}>
<TabPane tab="Basic Information" key="1" style={{ width: '100%' }}>
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
<Col span={24}>
<Card title={this.props.adapter.gt('Personal Information')}
extra={this.getEditButtonJsx()}
style={{ width: '100%' }}
>
<Descriptions title="" bordered>
<Descriptions.Item label={this.props.adapter.gt('Date of Birth')}>
{this.props.element.birthday || ''}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Gender')}>
{this.props.element.gender}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Nationality')}>
{this.props.element.nationality_Name}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Marital Status')}>
{this.props.element.marital_status}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Joined Date')}>
{this.props.element.joined_date}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Driving License No')}>
{this.props.element.driving_license || ''}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Other ID')}>
{this.props.element.other_id || ''}
</Descriptions.Item>
</Descriptions>
</Card>
</Col>
<Col span={24}>
<Card title={this.props.adapter.gt('Contact Information')}
extra={this.getEditButtonJsx()}
style={{ width: '100%' }}
>
<Descriptions title="" bordered>
<Descriptions.Item label={this.props.adapter.gt('Address')} span={3}>
{`${this.props.element.address1}, ${this.props.element.address2 || ''}`}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('City')}>
{this.props.element.city}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Country')}>
{this.props.element.country_Name}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Postal/Zip Code')}>
{this.props.element.postal_code}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Home Phone')} span={2}>
<Space>
<PhoneTwoTone />
<Text copyable>{` ${this.props.element.home_phone || ''}`}</Text>
</Space>
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Work Phone')} span={2}>
<Space>
<PhoneTwoTone />
<Text copyable>{` ${this.props.element.work_phone || ''}`}</Text>
</Space>
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Private Email')} span={2}>
<Space>
<MailTwoTone />
<Text copyable>{` ${this.props.element.private_email || ''}`}</Text>
</Space>
</Descriptions.Item>
</Descriptions>
</Card>
</Col>
<Col span={24}>
<Card title={this.props.adapter.gt('Job Details')}
extra={this.getEditButtonJsx()}
style={{ width: '100%' }}
>
<Descriptions title="" bordered>
<Descriptions.Item label={this.props.adapter.gt('Job Title')} span={2}>
{this.props.element.job_title_Name}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Employment Status')}>
{this.props.element.employment_status_Name}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Department')}>
{this.props.element.department_Name}
</Descriptions.Item>
<Descriptions.Item label={this.props.adapter.gt('Supervisor')}>
{this.props.element.supervisor_Name}
</Descriptions.Item>
</Descriptions>
</Card>
</Col>
</Row>
</TabPane>
<TabPane tab={this.props.adapter.gt('Qualifications')} key="2" style={{ width: '100%' }}>
<Row style={{width: '100%', padding: '10px'}} gutter={24}>
<Col span={6}>
<Card title={this.props.adapter.gt('Skills')}
extra={this.getTabViewEmployeeFilterButtonJsx('tabEmployeeSkill')}
style={{ width: '100%' }}
>
<TagList
color="geekblue"
apiClient={this.props.adapter.apiClient}
url={`employees/${this.props.element.id}/skills`}
extractTag = {(item) => item.skill_id.display}
/>
</Card>
</Col>
<Col span={6}>
<Card title={this.props.adapter.gt('Education')}
extra={this.getTabViewEmployeeFilterButtonJsx('tabEmployeeEducation')}
style={{ width: '100%' }}
>
<TagList
color="cyan"
apiClient={this.props.adapter.apiClient}
url={`employees/${this.props.element.id}/educations`}
extractTag = {(item) => item.education_id.display}
/>
</Card>
</Col>
<Col span={6}>
<Card title={this.props.adapter.gt('Certifications')}
extra={this.getTabViewEmployeeFilterButtonJsx('tabEmployeeCertification')}
style={{ width: '100%' }}
>
<TagList
color="volcano"
apiClient={this.props.adapter.apiClient}
url={`employees/${this.props.element.id}/certifications`}
extractTag = {(item) => item.certification_id.display}
/>
</Card>
</Col>
<Col span={6}>
<Card title={this.props.adapter.gt('Languages')}
extra={this.getTabViewEmployeeFilterButtonJsx('tabEmployeeLanguage')}
style={{ width: '100%' }}
>
<TagList
color="orange"
apiClient={this.props.adapter.apiClient}
url={`employees/${this.props.element.id}/languages`}
extractTag = {(item) => item.language_id.display}
/>
</Card>
</Col>
</Row>
</TabPane>
</Tabs>
</Row>
</>
)
}
}
export default EmployeeProfile;
+3 -2
View File
@@ -16,8 +16,8 @@ import {
EmployeeSubDependentAdapter,
EmployeeSubEmergencyContactAdapter,
EmployeeSubDocumentAdapter,
EmployeeDocumentAdapter,
} from './lib';
import IceDataPipe from '../../../api/IceDataPipe';
window.EmployeeAdapter = EmployeeAdapter;
window.TerminatedEmployeeAdapter = TerminatedEmployeeAdapter;
@@ -36,4 +36,5 @@ window.EmployeeSubLanguageAdapter = EmployeeSubLanguageAdapter;
window.EmployeeSubDependentAdapter = EmployeeSubDependentAdapter;
window.EmployeeSubEmergencyContactAdapter = EmployeeSubEmergencyContactAdapter;
window.EmployeeSubDocumentAdapter = EmployeeSubDocumentAdapter;
window.EmployeeDocumentAdapter = EmployeeDocumentAdapter;
window.IceDataPipe = IceDataPipe;
File diff suppressed because it is too large Load Diff
+24 -3
View File
@@ -1,5 +1,26 @@
import { JobTitleAdapter, PayGradeAdapter, EmploymentStatusAdapter } from './lib';
import IceDataPipe from '../../../api/IceDataPipe';
window.JobTitleAdapter = JobTitleAdapter;
window.PayGradeAdapter = PayGradeAdapter;
window.EmploymentStatusAdapter = EmploymentStatusAdapter;
function init(data) {
const modJsList = [];
modJsList.tabJobTitle = new JobTitleAdapter('JobTitle', 'JobTitle', '', '');
modJsList.tabJobTitle.setObjectTypeName('Job Titles');
modJsList.tabJobTitle.setDataPipe(new IceDataPipe(modJsList.tabJobTitle));
modJsList.tabJobTitle.setAccess(data.permissions.JobTitle);
modJsList.tabPayGrade = new PayGradeAdapter('PayGrade', 'PayGrade', '', '');
modJsList.tabPayGrade.setObjectTypeName('Pay Grades');
modJsList.tabPayGrade.setDataPipe(new IceDataPipe(modJsList.tabPayGrade));
modJsList.tabPayGrade.setAccess(data.permissions.PayGrade);
modJsList.tabEmploymentStatus = new EmploymentStatusAdapter('EmploymentStatus', 'EmploymentStatus', '', '');
modJsList.tabEmploymentStatus.setObjectTypeName('Employment Status');
modJsList.tabEmploymentStatus.setDataPipe(new IceDataPipe(modJsList.tabEmploymentStatus));
modJsList.tabEmploymentStatus.setAccess(data.permissions.EmploymentStatus);
window.modJs = modJsList.tabJobTitle;
window.modJsList = modJsList;
}
window.initAdminJobs = init;
+53 -6
View File
@@ -3,13 +3,13 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
import AdapterBase from '../../../api/AdapterBase';
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
/**
* JobTitleAdapter
*/
class JobTitleAdapter extends AdapterBase {
class JobTitleAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -36,8 +36,19 @@ class JobTitleAdapter extends AdapterBase {
];
}
getHelpLink() {
return 'http://blog.icehrm.com/docs/jobdetails/';
getTableColumns() {
return [
{
title: 'Job Title Code',
dataIndex: 'code',
sorter: true,
},
{
title: 'Job Title',
dataIndex: 'name',
sorter: true,
},
];
}
}
@@ -46,7 +57,7 @@ class JobTitleAdapter extends AdapterBase {
* PayGradeAdapter
*/
class PayGradeAdapter extends AdapterBase {
class PayGradeAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -77,6 +88,28 @@ class PayGradeAdapter extends AdapterBase {
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Currency',
dataIndex: 'currency',
},
{
title: 'Min Salary',
dataIndex: 'min_salary',
},
{
title: 'Max Salary',
dataIndex: 'max_salary',
},
];
}
doCustomValidation(params) {
try {
if (parseFloat(params.min_salary) > parseFloat(params.max_salary)) {
@@ -94,7 +127,7 @@ class PayGradeAdapter extends AdapterBase {
* EmploymentStatusAdapter
*/
class EmploymentStatusAdapter extends AdapterBase {
class EmploymentStatusAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -118,6 +151,20 @@ class EmploymentStatusAdapter extends AdapterBase {
['description', { label: 'Description', type: 'textarea', validation: '' }],
];
}
getTableColumns() {
return [
{
title: 'Employment Status',
dataIndex: 'name',
sorter: true,
},
{
title: 'Description',
dataIndex: 'description',
},
];
}
}
+2 -1
View File
@@ -40,7 +40,8 @@ class ModuleAdapter extends AdapterBase {
['label', { label: 'Label', type: 'text', validation: '' }],
['status', { label: 'Status', type: 'select', source: [['Enabled', 'Enabled'], ['Disabled', 'Disabled']] }],
['user_levels', { label: 'User Levels', type: 'select2multi', source: [['Admin', 'Admin'], ['Manager', 'Manager'], ['Employee', 'Employee']] }],
['user_roles', { label: 'User Roles', type: 'select2multi', 'remote-source': ['UserRole', 'id', 'name'] }],
['user_roles', { label: 'Allowed User Roles', type: 'select2multi', 'remote-source': ['UserRole', 'id', 'name'] }],
['user_roles_blacklist', { label: 'Disallowed User Roles', type: 'select2multi', 'remote-source': ['UserRole', 'id', 'name'] }],
];
}
+2 -2
View File
@@ -56,7 +56,7 @@ class PaydayAdapter extends AdapterBase {
getActionButtonsHtml(id, data) {
const editButton = '<input type="checkbox" class="paydayCheck" id="payday__id_" name="payday__id_" value="checkbox_payday__id_"/>';
let html = '<div style="width:130px;">_edit_</div>';
let html = '<div style="width:132px;">_edit_</div>';
html = html.replace('_edit_', editButton);
html = html.replace(/_id_/g, id);
@@ -160,7 +160,7 @@ class PayrollAdapter extends AdapterBase {
const deleteButton = '<img class="tableActionButton" src="_BASE_images/delete.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Delete" onclick="modJs.deleteRow(_id_);return false;"></img>';
const cloneButton = '<img class="tableActionButton" src="_BASE_images/clone.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Copy" onclick="modJs.copyRow(_id_);return false;"></img>';
let html = '<div style="width:130px;">_edit__process__clone__delete_</div>';
let html = '<div style="width:132px;">_edit__process__clone__delete_</div>';
if (this.showAddNew) {
+29 -4
View File
@@ -4,8 +4,33 @@ import {
CertificationAdapter,
LanguageAdapter,
} from './lib';
import IceDataPipe from '../../../api/IceDataPipe';
window.SkillAdapter = SkillAdapter;
window.EducationAdapter = EducationAdapter;
window.CertificationAdapter = CertificationAdapter;
window.LanguageAdapter = LanguageAdapter;
function init(data) {
const modJsList = [];
modJsList.tabSkill = new SkillAdapter('Skill');
modJsList.tabSkill.setObjectTypeName('Skills');
modJsList.tabSkill.setDataPipe(new IceDataPipe(modJsList.tabSkill));
modJsList.tabSkill.setAccess(data.permissions.Skill);
modJsList.tabEducation = new EducationAdapter('Education');
modJsList.tabEducation.setObjectTypeName('Education');
modJsList.tabEducation.setDataPipe(new IceDataPipe(modJsList.tabEducation));
modJsList.tabEducation.setAccess(data.permissions.Education);
modJsList.tabCertification = new CertificationAdapter('Certification');
modJsList.tabCertification.setObjectTypeName('Education');
modJsList.tabCertification.setDataPipe(new IceDataPipe(modJsList.tabCertification));
modJsList.tabCertification.setAccess(data.permissions.Certification);
modJsList.tabLanguage = new LanguageAdapter('Language');
modJsList.tabLanguage.setObjectTypeName('Language');
modJsList.tabLanguage.setDataPipe(new IceDataPipe(modJsList.tabLanguage));
modJsList.tabLanguage.setAccess(data.permissions.Language);
window.modJs = modJsList.tabSkill;
window.modJsList = modJsList;
}
window.initAdminQualifications = init;
+59 -6
View File
@@ -4,12 +4,13 @@
*/
import AdapterBase from '../../../api/AdapterBase';
import ReactModalAdapterBase from "../../../api/ReactModalAdapterBase";
/**
* SkillAdapter
*/
class SkillAdapter extends AdapterBase {
class SkillAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -34,8 +35,18 @@ class SkillAdapter extends AdapterBase {
];
}
getHelpLink() {
return 'http://blog.icehrm.com/docs/qualifications/';
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Description',
dataIndex: 'description',
},
];
}
}
@@ -44,7 +55,7 @@ class SkillAdapter extends AdapterBase {
* EducationAdapter
*/
class EducationAdapter extends AdapterBase {
class EducationAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -68,6 +79,20 @@ class EducationAdapter extends AdapterBase {
['description', { label: 'Description', type: 'textarea', validation: '' }],
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Description',
dataIndex: 'description',
},
];
}
}
@@ -75,7 +100,7 @@ class EducationAdapter extends AdapterBase {
* CertificationAdapter
*/
class CertificationAdapter extends AdapterBase {
class CertificationAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -99,6 +124,20 @@ class CertificationAdapter extends AdapterBase {
['description', { label: 'Description', type: 'textarea', validation: '' }],
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Description',
dataIndex: 'description',
},
];
}
}
@@ -106,7 +145,7 @@ class CertificationAdapter extends AdapterBase {
* LanguageAdapter
*/
class LanguageAdapter extends AdapterBase {
class LanguageAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
@@ -130,6 +169,20 @@ class LanguageAdapter extends AdapterBase {
['description', { label: 'Description', type: 'textarea', validation: '' }],
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Description',
dataIndex: 'description',
},
];
}
}
module.exports = {
+62 -4
View File
@@ -5,7 +5,7 @@
import FormValidation from '../../../api/FormValidation';
import AdapterBase from '../../../api/AdapterBase';
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
class UserAdapter extends AdapterBase {
getDataMapping() {
@@ -181,7 +181,12 @@ class UserAdapter extends AdapterBase {
* UserRoleAdapter
*/
class UserRoleAdapter extends AdapterBase {
class UserRoleAdapter extends ReactModalAdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.tables = [];
}
getDataMapping() {
return [
'id',
@@ -196,15 +201,68 @@ class UserRoleAdapter extends AdapterBase {
];
}
getTableColumns() {
return [
{
title: 'ID',
dataIndex: 'id',
sorter: true,
},
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
];
}
postRenderForm(object, $tempDomObj) {
$tempDomObj.find('#changePasswordBtn').remove();
setTables(tables) {
this.tables = tables;
}
getFormFields() {
return [
['id', { label: 'ID', type: 'hidden' }],
['name', { label: 'Name', type: 'text', validation: '' }],
['additional_permissions', {
label: 'Additional Permissions',
type: 'datagroup',
form: [
['table',
{
label: 'Table',
type: 'select2',
source: this.tables,
},
],
['permissions',
{
label: 'Permissions',
type: 'select2multi',
'allow-null': true,
source: [
['get', 'List'],
['element', 'Get Details'],
['save', 'Add/Edit'],
['delete', 'Delete'],
],
},
],
],
columns: [
{
title: 'Table',
dataIndex: 'table',
key: 'table',
},
{
title: 'Permissions',
dataIndex: 'permissions',
key: 'permissions',
},
],
validation: 'none',
}],
];
}
}
+1 -1
View File
@@ -217,7 +217,7 @@ if (typeof define === 'function' && define.amd) define([], () => Aes); // AMD
/* jshint node:true *//* global define, escape, unescape, btoa, atob */
'use strict';
if (typeof module !== 'undefined' && module.exports) var Aes = require('./Aes'); // CommonJS (Node.js)
if (typeof module !== 'undefined' && module.exports) var Aes = require('./aes'); // CommonJS (Node.js)
/**
+46 -28
View File
@@ -7,7 +7,39 @@
* RequestCache
*/
class MemoryStorage {
constructor() {
this.data = {};
}
getItem(key) {
return this.data[key];
}
setItem(key, data) {
this.data[key] = data;
}
removeAllByPrefix(prefix) {
const keys = Object.keys(this.data);
for (let i = 0; i < keys.length; i++) {
if (keys[i].indexOf(prefix) > 0) {
delete this.data[keys[i]];
}
}
}
}
class RequestCache {
constructor(storage) {
if (!storage) {
this.storage = new MemoryStorage();
} else {
this.storage = storage;
}
}
getKey(url, params) {
let key = `${url}|`;
for (const index in params) {
@@ -16,53 +48,39 @@ class RequestCache {
return key;
}
/*
invalidateTable(table) {
let key;
for (let i = 0; i < localStorage.length; i++) {
key = localStorage.key(i);
for (let i = 0; i < this.storage.length; i++) {
key = this.storage.key(i);
if (key.indexOf(`t=${table}`) > 0) {
localStorage.removeItem(key);
this.storage.removeItem(key);
}
}
}
*/
invalidateTable(table) {
this.storage.removeAllByPrefix(`t=${table}`);
}
getData(key) {
let data;
if (typeof (Storage) === 'undefined') {
const data = this.storage.getItem(key);
if (!data) {
return null;
}
const strData = localStorage.getItem(key);
if (strData !== undefined && strData != null && strData !== '') {
data = JSON.parse(strData);
if (data === undefined || data == null) {
return null;
}
if (data.status !== undefined && data.status != null && data.status !== 'SUCCESS') {
return null;
}
return data;
}
return null;
return data;
}
setData(key, data) {
if (typeof (Storage) === 'undefined') {
return null;
}
if (data.status !== undefined && data.status != null && data.status !== 'SUCCESS') {
return null;
}
const strData = JSON.stringify(data);
localStorage.setItem(key, strData);
return strData;
this.storage.setItem(key, data);
return data;
}
}
+13 -3
View File
@@ -59,6 +59,8 @@ function closeUploadDialog(success, error, data) {
$(`#${uploadId}`).show();
$(`#${uploadId}_download`).show();
$(`#${uploadId}_remove`).show();
} else if (uploadAttr === 'function') {
modJs.callFunction(uploadResultAttr);
}
} else {
// popupUpload.close();
@@ -174,9 +176,17 @@ function updateLanguage(language) {
}, 'json');
}
$(document).ready(function() {
function switchTab(name, filter) {
if (filter) {
modJsList[name].setFilter(filter);
}
$('html,body').scrollTop(0);
$(`#${name}`).trigger('click');
}
$(document).ready(() => {
$('.treeview-menu a').hover(
function () { $(this).find('.fa').addClass('faa-horizontal'); $(this).find('.fa').addClass('animated') },
function () { $(this).find('.fa').removeClass('faa-horizontal'); $(this).find('.fa').removeClass('animated');},
function () { $(this).find('.fa').addClass('faa-horizontal'); $(this).find('.fa').addClass('animated'); },
function () { $(this).find('.fa').removeClass('faa-horizontal'); $(this).find('.fa').removeClass('animated'); },
);
});
+81
View File
@@ -0,0 +1,81 @@
$(document).ready(() => {
$(window).keydown((event) => {
if (event.keyCode == 13) {
event.preventDefault();
return false;
}
});
$('#password').keydown((event) => {
if (event.keyCode == 13) {
submitLogin();
return false;
}
});
});
window.showForgotPassword = () => {
$('#loginForm').hide();
$('#requestPasswordChangeForm').show();
};
window.requestPasswordChange = () => {
$('#requestPasswordChangeFormAlert').hide();
const id = $('#usernameChange').val();
if (id === '') {
return false;
}
$.post('service.php', { a: 'rpc', id }, (data) => {
if (data.status == 'SUCCESS') {
$('#requestPasswordChangeFormAlert').show();
$('#requestPasswordChangeFormAlert').html(data.message);
} else {
$('#requestPasswordChangeFormAlert').show();
$('#requestPasswordChangeFormAlert').html(data.message);
}
}, 'json');
};
window.changePassword = (key) => {
$('#newPasswordFormAlert').hide();
const password = $('#password').val();
const passwordValidation = function (str) {
return str.length > 7;
};
if (!passwordValidation(password)) {
$('#newPasswordFormAlert').show();
$('#newPasswordFormAlert').html('Password should be longer than 7 characters');
return;
}
$.post('service.php', {
a: 'rsp', key, pwd: password, now: '1',
}, (data) => {
if (data.status == 'SUCCESS') {
top.location.href = 'login.php?c=1';
} else {
$('#newPasswordFormAlert').show();
$('#newPasswordFormAlert').html(data.message);
}
}, 'json');
};
window.submitLogin = () => {
const username = $('#username').val();
const password = $('#password').val();
if (username === '' || password === '') {
return false;
}
try {
localStorage.clear();
} catch (e) {}
$('#loginForm').submit();
};
window.authGoogle = () => {
window.location.href = `${window.location.href.split('login.php')[0]}login.php?google=1`;
};
+76 -25
View File
@@ -4,8 +4,10 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
import IceApiClient from './IceApiClient';
import ModuleBase from './ModuleBase';
import RequestCache from '../api-common/RequestCache';
import MasterDataReader from './MasterDataReader';
class AdapterBase extends ModuleBase {
constructor(endPoint, tab, filter, orderBy) {
@@ -48,6 +50,18 @@ class AdapterBase extends ModuleBase {
this.requestCache = new RequestCache();
}
initMasterDataReader() {
this.masterDataReader = new MasterDataReader(this);
}
setupApiClient(token) {
this.apiClient = new IceApiClient(this.apiUrl, token);
}
setApiUrl(apiUrl) {
this.apiUrl = apiUrl;
}
setFilter(filter) {
this.filter = filter;
}
@@ -84,6 +98,19 @@ class AdapterBase extends ModuleBase {
return this.orderBy;
}
getFile(name) {
this.trackEvent('file', name);
return new Promise((resolve, reject) => {
$.getJSON(this.moduleRelativeURL, { a: 'file', name }, (data) => {
if (data.status === 'SUCCESS') {
resolve(data.data);
} else {
reject();
}
}).fail(() => reject());
});
}
/**
* @method add
* @param object {Array} object data to be added to database
@@ -108,7 +135,13 @@ class AdapterBase extends ModuleBase {
} else {
that.addFailCallBack(getFunctionCallBackData, data.object);
}
}, 'json').always(() => { that.hideLoader(); });
}, 'json')
.fail((e) => {
if (e.status === 403) {
that.showMessage('Access Forbidden', e.responseJSON.message);
}
})
.always(() => { that.hideLoader(); });
this.trackEvent('add', this.tab, this.table);
}
@@ -143,7 +176,13 @@ class AdapterBase extends ModuleBase {
} else {
that.deleteFailCallBack(callBackData, data.object);
}
}, 'json').always(() => { that.hideLoader(); });
}, 'json')
.fail((e) => {
if (e.status === 403) {
that.showMessage('Access Forbidden', e.responseJSON.message);
}
})
.always(() => { that.hideLoader(); });
this.trackEvent('delete', this.tab, this.table);
}
@@ -192,7 +231,13 @@ class AdapterBase extends ModuleBase {
} else {
that.getFailCallBack(callBackData, data.object);
}
}, 'json').always(() => { that.hideLoader(); });
}, 'json')
.fail((e) => {
if (e.status === 403) {
that.showMessage('Access Forbidden', e.responseJSON.message);
}
})
.always(() => { that.hideLoader(); });
that.initFieldMasterData();
@@ -299,7 +344,13 @@ class AdapterBase extends ModuleBase {
} else {
that.getElementFailCallBack.apply(that, [callBackData, data.object]);
}
}, 'json').always(() => { that.hideLoader(); });
}, 'json')
.fail((e) => {
if (e.status === 403) {
that.showMessage('Access Forbidden', e.responseJSON.message);
}
})
.always(() => { that.hideLoader(); });
this.trackEvent('getElement', this.tab, this.table);
}
@@ -357,30 +408,30 @@ class AdapterBase extends ModuleBase {
}
that.callFunction(callBackData.callBack, callBackData.callBackData);
}
}
const callbackWraper = function (data) {
if (data.status === 'SUCCESS') {
that.requestCache.setData(this.success.key, data);
const localCallBackData = callBackData;
localCallBackData.callBackData = [callBackData.callBackData[0]];
localCallBackData.callBackData.push(data.data);
if (localCallBackData.callBackSuccess !== null && localCallBackData.callBackSuccess !== undefined) {
localCallBackData.callBackData.push(callBackData.callBackSuccess);
} else {
const callbackWraper = function (data) {
if (data.status === 'SUCCESS') {
that.requestCache.setData(this.success.key, data);
const localCallBackData = callBackData;
localCallBackData.callBackData = [callBackData.callBackData[0]];
localCallBackData.callBackData.push(data.data);
if (localCallBackData.callBackSuccess !== null && localCallBackData.callBackSuccess !== undefined) {
localCallBackData.callBackData.push(callBackData.callBackSuccess);
}
that.callFunction(localCallBackData.callBack, localCallBackData.callBackData);
} else if (data.message === 'Access violation') {
alert(`Error : ${callbackWraper.table} ${data.message}`);
}
that.callFunction(localCallBackData.callBack, localCallBackData.callBackData);
} else if (data.message === 'Access violation') {
alert(`Error : ${callbackWraper.table} ${data.message}`);
}
};
};
callbackWraper.key = key;
// eslint-disable-next-line prefer-destructuring
callbackWraper.table = fieldMaster[0];
callbackWraper.key = key;
// eslint-disable-next-line prefer-destructuring
callbackWraper.table = fieldMaster[0];
$.post(this.moduleRelativeURL, {
t: fieldMaster[0], key: fieldMaster[1], value: fieldMaster[2], method, methodParams, a: 'getFieldValues',
}, callbackWraper, 'json');
$.post(this.moduleRelativeURL, {
t: fieldMaster[0], key: fieldMaster[1], value: fieldMaster[2], method, methodParams, a: 'getFieldValues',
}, callbackWraper, 'json');
}
}
setAdminProfile(empId) {
-2
View File
@@ -56,8 +56,6 @@ const ValidationRules = {
}
return false;
},
};
class FormValidation {
+18
View File
@@ -0,0 +1,18 @@
const axios = require('axios');
class IceApiClient {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.token = token;
}
get(endpoint) {
return axios.get(this.baseUrl + endpoint, {
headers: {
Authorization: `Bearer ${this.token}`,
},
});
}
}
export default IceApiClient;
+114
View File
@@ -0,0 +1,114 @@
const axios = require('axios');
class IceDataPipe {
constructor(adapter, pageSize = 10) {
this.adapter = adapter;
this.pageSize = pageSize;
}
readMetaData() {
this.adapter.initFieldMasterData();
}
get({
page, search, sortField, sortOrder, filters, limit,
}) {
const pageSize = limit || this.pageSize;
const start = (page - 1) * pageSize;
const dataUrl = this.getDataUrl(
this.adapter.getDataMapping(),
search,
filters,
);
let url = `${dataUrl}&iDisplayStart=${start}&iDisplayLength=${pageSize}`;
url = this.applySortingData(this.adapter.getDataMapping(), url, sortField, sortOrder);
// $.post(url, (data) => {
// that.getSuccessCallBack(callBackData, data);
// }, 'json').always(() => { that.hideLoader(); });
url = `${url}&version=v2`;
return axios.post(url, {})
.then((data) => {
const key = this.getRequestKey(page, search, sortField, sortOrder, filters, limit);
const response = {
items: data.data.objects,
total: data.data.totalRecords,
};
if (this.adapter.localStorageEnabled) {
window.localStorage.setItem(key, JSON.stringify(response));
}
return response;
});
}
getCachedResponse({
page, search, sortField, sortOrder, filters, limit,
}) {
const key = this.getRequestKey(page, search, sortField, sortOrder, filters, limit);
const cachedResponse = window.localStorage.getItem(key);
if (!cachedResponse) {
return null;
}
return JSON.parse(cachedResponse);
}
clearCachedResponse({
page, search, sortField, sortOrder, filters, limit,
}) {
const key = this.getRequestKey(page, search, sortField, sortOrder, filters, limit);
window.localStorage.setItem(key, null);
}
getRequestKey(page, search, sortField, sortOrder, filters, limit) {
return `${this.adapter.table}|${page}|${search}|${sortField}|${sortOrder}|${filters}|${limit}`;
}
applySortingData(columns, url, sortField, sortOrder) {
let orderBy = '';
if (sortField) {
url = `${url}&sorting=1`;
url = `${url}&iSortCol_0=${columns.indexOf(sortField)}`;
url = `${url}&sSortDir_0=${(sortOrder === 'descend') ? 'DESC' : 'ASC'}`;
} else if (this.adapter.getOrderBy() !== null) {
// Setting the fix ordering
orderBy = this.adapter.getOrderBy();
url = `${url}&ob=${orderBy}`;
}
return url;
}
getDataUrl(_columns, searchTerm, filters) {
const sourceMappingJson = JSON.stringify(this.adapter.getSourceMapping());
const columns = JSON.stringify(_columns);
let filterJson = '';
if (this.adapter.getFilter() !== null) {
filterJson = JSON.stringify(this.adapter.getFilter());
}
let url = this.adapter.moduleRelativeURL.replace('service.php', 'data.php');
url = `${url}?t=${this.adapter.table}`;
url = `${url}&sm=${sourceMappingJson}`;
url = `${url}&cl=${columns}`;
url = `${url}&ft=${filterJson}`;
if (searchTerm && searchTerm.trim() !== '') {
url += `&sSearch=${searchTerm}`;
}
if (this.adapter.isSubProfileTable()) {
url = `${url}&type=sub`;
}
if (this.adapter.remoteTableSkipProfileRestriction()) {
url = `${url}&skip=1`;
}
return url;
}
}
export default IceDataPipe;
+75
View File
@@ -0,0 +1,75 @@
const axios = require('axios');
class MasterDataReader {
constructor(adapter) {
this.adapter = adapter;
this.requestCache = this.adapter.requestCache;
}
fetchMasterData(fieldMaster) {
let method = '';
let methodParams = '';
if (fieldMaster[3] != null) {
method = fieldMaster[3];
}
if (fieldMaster[4] != null) {
methodParams = JSON.stringify(fieldMaster[4]);
}
const key = this.requestCache.getKey(this.adapter.moduleRelativeURL, {
t: fieldMaster[0], key: fieldMaster[1], value: fieldMaster[2], method, methodParams, a: 'getFieldValues',
});
const cacheData = this.requestCache.getData(key);
if (cacheData != null && cacheData.status === 'SUCCESS') {
return new Promise((resolve, reject) => resolve(cacheData.data));
}
const urlData = {
t: fieldMaster[0],
key: fieldMaster[1],
value: fieldMaster[2],
method,
methodParams,
a: 'getFieldValues',
};
let url = `${this.adapter.moduleRelativeURL}?_url=1`;
for (const index in urlData) {
url = `${url}&${index}=${encodeURIComponent(urlData[index])}`;
}
// TODO - Should be a get request
return axios.post(url, {})
.then((response) => {
if (response.data.status !== 'SUCCESS') {
throw Error(`Response for ${key} failed`);
}
this.requestCache.setData(key, response.data);
return response.data.data;
});
}
updateAllMasterData() {
const remoteSourceFields = this.adapter.getRemoteSourceFields();
const promiseList = [];
for (let i = 0; i < remoteSourceFields.length; i++) {
const fieldRemote = remoteSourceFields[i];
if (fieldRemote[1]['remote-source'] !== undefined && fieldRemote[1]['remote-source'] != null) {
let key = `${fieldRemote[1]['remote-source'][0]}_${fieldRemote[1]['remote-source'][1]}_${fieldRemote[1]['remote-source'][2]}`;
if (fieldRemote[1]['remote-source'].length === 4) {
key = `${key}_${fieldRemote[1]['remote-source'][3]}`;
}
const masterDataPromise = this.fetchMasterData(fieldRemote[1]['remote-source'])
.then((data) => {
this.adapter.fieldMasterData[key] = data;
});
promiseList.push(masterDataPromise);
}
}
return Promise.all(promiseList);
}
}
export default MasterDataReader;
+148 -51
View File
@@ -16,6 +16,7 @@ class ModuleBase {
this.createRemoteTable = false;
this.instanceId = 'None';
this.ga = [];
this.showAddNew = true;
this.showEdit = true;
this.showDelete = true;
this.showSave = true;
@@ -33,8 +34,8 @@ class ModuleBase {
this.templates = null;
this.customTemplates = null;
this.emailTemplates = null;
this.fieldMasterData = null;
this.fieldMasterDataKeys = null;
this.fieldMasterData = {};
this.fieldMasterDataKeys = {};
this.fieldMasterDataCallback = null;
this.sourceMapping = null;
this.currentId = null;
@@ -43,6 +44,7 @@ class ModuleBase {
this.currentProfile = null;
this.permissions = {};
this.baseUrl = null;
this.that = this;
}
// eslint-disable-next-line no-unused-vars
@@ -50,6 +52,14 @@ class ModuleBase {
}
initForm() {
}
setObjectTypeName(objectTypeName) {
this.objectTypeName = objectTypeName;
}
/**
* Some browsers do not support sending JSON in get parameters. Set this to true to avoid sending JSON
* @method setNoJSONRequests
@@ -216,15 +226,58 @@ class ModuleBase {
* }
*/
initFieldMasterData(callback, loadAllCallback, loadAllCallbackData) {
let values;
if (this.showAddNew === undefined || this.showAddNew == null) {
this.showAddNew = true;
}
this.fieldMasterData = {};
this.fieldMasterDataKeys = {};
this.fieldMasterDataCallback = loadAllCallback;
this.fieldMasterDataCallbackData = loadAllCallbackData;
const remoteSourceFields = this.getRemoteSourceFields();
for (let i = 0; i < remoteSourceFields.length; i++) {
const fieldRemote = remoteSourceFields[i];
if (fieldRemote[1]['remote-source'] !== undefined && fieldRemote[1]['remote-source'] != null) {
let key = `${fieldRemote[1]['remote-source'][0]}_${fieldRemote[1]['remote-source'][1]}_${fieldRemote[1]['remote-source'][2]}`;
if (fieldRemote[1]['remote-source'].length === 4) {
key = `${key}_${fieldRemote[1]['remote-source'][3]}`;
}
this.fieldMasterDataKeys[key] = false;
const callBackData = {};
callBackData.callBack = 'initFieldMasterDataResponse';
callBackData.callBackData = [key];
if (callback !== null && callback !== undefined) {
callBackData.callBackSuccess = callback;
}
this.getFieldValues(fieldRemote[1]['remote-source'], callBackData);
}
}
}
initSourceMappings() {
this.sourceMapping = {};
const remoteSourceFields = this.getRemoteSourceFields();
for (let i = 0; i < remoteSourceFields.length; i++) {
const fieldRemote = remoteSourceFields[i];
if (fieldRemote[1]['remote-source'] !== undefined && fieldRemote[1]['remote-source'] != null) {
this.sourceMapping[fieldRemote[0]] = fieldRemote[1]['remote-source'];
}
}
}
getRemoteSourceKey(field) {
let key = `${field[1]['remote-source'][0]}_${field[1]['remote-source'][1]}_${field[1]['remote-source'][2]}`;
if (field[1]['remote-source'].length > 3) {
key = `${key}_${field[1]['remote-source'][3]}`;
}
return key;
}
getRemoteSourceFields() {
let values;
const fields = this.getFormFields();
const filterFields = this.getFilters();
@@ -237,7 +290,6 @@ class ModuleBase {
}
}
const remoteSourceFields = [];
const remoteSourceFieldKeys = [];
let field = null;
@@ -245,16 +297,16 @@ class ModuleBase {
for (let i = 0; i < fields.length; i++) {
field = fields[i];
if (field[1]['remote-source'] !== undefined && field[1]['remote-source'] !== null) {
const key = `${field[1]['remote-source'][0]}_${field[1]['remote-source'][1]}_${field[1]['remote-source'][2]}`;
// if(remoteSourceFieldKeys.indexOf(key) < 0){
remoteSourceFields.push(field);
remoteSourceFieldKeys.push(key);
// }
const key = this.getRemoteSourceKey(field);
if (remoteSourceFieldKeys.indexOf(key) < 0) {
remoteSourceFields.push(field);
remoteSourceFieldKeys.push(key);
}
} else if (field[1].form !== undefined && field[1].form !== null) {
for (let j = 0; j < field[1].form.length; j++) {
fieldSub = field[1].form[j];
if (fieldSub[1]['remote-source'] !== undefined && fieldSub[1]['remote-source'] !== null) {
const key = `${fieldSub[1]['remote-source'][0]}_${fieldSub[1]['remote-source'][1]}_${fieldSub[1]['remote-source'][2]}`;
const key = this.getRemoteSourceKey(fieldSub);
if (remoteSourceFieldKeys.indexOf(key) < 0) {
remoteSourceFields.push(fieldSub);
remoteSourceFieldKeys.push(key);
@@ -264,22 +316,7 @@ class ModuleBase {
}
}
for (let i = 0; i < remoteSourceFields.length; i++) {
const fieldRemote = remoteSourceFields[i];
if (fieldRemote[1]['remote-source'] !== undefined && fieldRemote[1]['remote-source'] != null) {
const key = `${fieldRemote[1]['remote-source'][0]}_${fieldRemote[1]['remote-source'][1]}_${fieldRemote[1]['remote-source'][2]}`;
this.fieldMasterDataKeys[key] = false;
this.sourceMapping[fieldRemote[0]] = fieldRemote[1]['remote-source'];
const callBackData = {};
callBackData.callBack = 'initFieldMasterDataResponse';
callBackData.callBackData = [key];
if (callback !== null && callback !== undefined) {
callBackData.callBackSuccess = callback;
}
this.getFieldValues(fieldRemote[1]['remote-source'], callBackData);
}
}
return remoteSourceFields;
}
/**
@@ -761,6 +798,10 @@ class ModuleBase {
}
getTableColumns() {
return [];
}
getTableData() {
}
@@ -1120,6 +1161,9 @@ class ModuleBase {
for (const prop in filters) {
if (filters.hasOwnProperty(prop)) {
values = this.getMetaFieldValues(prop, filterFields);
if (!values) {
continue;
}
value = '';
valueOrig = null;
@@ -1622,7 +1666,7 @@ class ModuleBase {
$(`#${field[0]}_div`).html('');
}
showDataGroup(field, object) {
showDataGroup(field, object, callback) {
let formHtml = this.templates.datagroupTemplate;
let html = '';
const fields = field[1].form;
@@ -1698,7 +1742,7 @@ class ModuleBase {
e.preventDefault();
e.stopPropagation();
try {
modJs.editDataGroup();
modJs.editDataGroup(callback);
} catch (err) {
console.log(`Error editing data group: ${err.message}`);
}
@@ -1709,7 +1753,7 @@ class ModuleBase {
e.preventDefault();
e.stopPropagation();
try {
modJs.addDataGroup();
modJs.addDataGroup(callback);
} catch (err) {
console.log(`Error adding data group: ${err.message}`);
}
@@ -1718,9 +1762,9 @@ class ModuleBase {
}
}
addDataGroup() {
const field = this.currentDataGroupField; let
tempParams;
addDataGroup(callback, existingData) {
const field = this.currentDataGroupField;
let tempParams;
$(`#${this.getTableName()}_field_${field[0]}_error`).html('');
$(`#${this.getTableName()}_field_${field[0]}_error`).hide();
const validator = new FormValidation(`${this.getTableName()}_field_${field[0]}`, true, { ShowPopup: false, LabelErrorClass: 'error' });
@@ -1736,10 +1780,14 @@ class ModuleBase {
return false;
}
}
let val = $(`#${field[0]}`).val();
if (val === '') {
val = '[]';
let val = '[]';
if (existingData) {
val = existingData;
} else {
val = $(`#${field[0]}`).val();
if (val === '' || val == null) {
val = '[]';
}
}
const data = JSON.parse(val);
@@ -1754,6 +1802,9 @@ class ModuleBase {
val = JSON.stringify(data);
const html = this.dataGroupToHtml(val, field);
if (callback) {
callback(val);
}
$(`#${field[0]}_div`).html('');
$(`#${field[0]}_div`).append(html);
@@ -1823,12 +1874,12 @@ class ModuleBase {
});
}
orderDataGroup(field) {
orderDataGroup(field, callback) {
const newArr = []; let
id;
const list = $(`#${field[0]}_div_inner [fieldid='${field[0]}_div']`);
let val = $(`#${field[0]}`).val();
if (val === '') {
if (val === '' || val == null) {
val = '[]';
}
const data = JSON.parse(val);
@@ -1843,10 +1894,14 @@ class ModuleBase {
});
$(`#${field[0]}`).val(JSON.stringify(newArr));
if (callback != null) {
callback(newArr);
}
}
editDataGroup() {
editDataGroup(callback, existingData) {
const field = this.currentDataGroupField;
const id = this.currentDataGroupItemId;
const validator = new FormValidation(`${this.getTableName()}_field_${field[0]}`, true, { ShowPopup: false, LabelErrorClass: 'error' });
@@ -1866,9 +1921,14 @@ class ModuleBase {
if (this.doCustomFilterValidation(params)) {
let val = $(`#${field[0]}`).val();
if (val === '') {
val = '[]';
let val = '[]';
if (existingData) {
val = existingData;
} else {
val = $(`#${field[0]}`).val();
if (val === '' || val == null) {
val = '[]';
}
}
const data = JSON.parse(val);
@@ -1897,6 +1957,10 @@ class ModuleBase {
const html = this.dataGroupToHtml(val, field);
if (callback) {
callback(newVals);
}
this.orderDataGroup(field);
$(`#${field[0]}_div`).html('');
@@ -1914,10 +1978,15 @@ class ModuleBase {
return true;
}
editDataGroupItem(id) {
editDataGroupItem(id, existingData, field) {
const fieldId = id.substring(0, id.lastIndexOf('_'));
const val = $(`#${fieldId}`).val();
let val;
if (existingData) {
val = decodeURI(existingData);
} else {
val = $(`#${fieldId}`).val();
}
const data = JSON.parse(val);
let editVal = {};
@@ -1929,7 +1998,13 @@ class ModuleBase {
}
}
this.showDataGroup($(`#${fieldId}`).data('field'), editVal);
if (field) {
field = JSON.parse(decodeURI(field));
} else {
field = $(`#${fieldId}`).data('field');
}
this.showDataGroup(field, editVal);
}
dataGroupGetNextAutoIncrementId(data) {
@@ -1950,10 +2025,15 @@ class ModuleBase {
}
deleteDataGroupItem(id) {
deleteDataGroupItem(id, existingData) {
const fieldId = id.substring(0, id.lastIndexOf('_'));
const val = $(`#${fieldId}`).val();
let val;
if (existingData) {
val = decodeURI(existingData);
} else {
val = $(`#${fieldId}`).val();
}
const data = JSON.parse(val);
const newVal = [];
@@ -2138,7 +2218,10 @@ class ModuleBase {
if (field[1].source !== undefined && field[1].source != null) {
t = t.replace('_options_', this.renderFormSelectOptions(field[1].source, field));
} else if (field[1]['remote-source'] !== undefined && field[1]['remote-source'] != null) {
const key = `${field[1]['remote-source'][0]}_${field[1]['remote-source'][1]}_${field[1]['remote-source'][2]}`;
let key = `${field[1]['remote-source'][0]}_${field[1]['remote-source'][1]}_${field[1]['remote-source'][2]}`;
if (field[1]['remote-source'].length === 4) {
key = `${key}_${field[1]['remote-source'][3]}`;
}
t = t.replace('_options_', this.renderFormSelectOptionsRemote(this.fieldMasterData[key], field));
}
} else if (field[1].type === 'colorpick') {
@@ -2576,6 +2659,20 @@ class ModuleBase {
return fields;
}
getImageUrlFromName(firstName, lastName) {
let seed = firstName.substring(0, 1);
if (!lastName && lastName.length > 0) {
seed += firstName.substring(firstName.length - 1, 1);
} else {
seed += lastName.substring(0, 1);
}
const arr = `${firstName}${lastName}`.split('');
seed += arr.reduce((acc, item) => parseInt(item.charCodeAt(0), 10) + acc, 0);
return `https://avatars.dicebear.com/api/initials/:${seed}.svg`;
}
}
export default ModuleBase;
+137
View File
@@ -0,0 +1,137 @@
/*
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
import React from 'react';
import { Space } from 'antd';
import AdapterBase from './ReactModalAdapterBase';
/*
* CustomFieldAdapter
*/
class ReactCustomFieldAdapter extends AdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.tableType = '';
}
getDataMapping() {
return [
'id',
'name',
'display',
'display_order',
];
}
getHeaders() {
return [
{ sTitle: 'ID', bVisible: false },
{ sTitle: 'Name' },
{ sTitle: 'Display Status' },
{ sTitle: 'Priority' },
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
width: '25%',
},
{
title: 'Display Status',
dataIndex: 'display',
width: '35%',
},
{
title: 'Priority',
dataIndex: 'display_order',
width: '10%',
},
];
}
getFormFields() {
return [
['id', { label: 'ID', type: 'hidden' }],
['name', { label: 'Name', type: 'text', validation: '' }],
['display', { label: 'Display Status', type: 'select', source: [['Form', 'Show'], ['Hidden', 'Hidden']] }],
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time']] }],
['field_label', { label: 'Field Label', type: 'text', validation: '' }],
['field_validation', {
label: 'Validation', type: 'select2', validation: 'none', sort: 'none', 'null-label': 'Required', 'allow-null': true, source: [['none', 'None'], ['number', 'Number'], ['numberOrEmpty', 'Number or Empty'], ['float', 'Decimal'], ['email', 'Email'], ['emailOrEmpty', 'Email or Empty']],
}],
['field_options', {
label: 'Field Options',
type: 'datagroup',
form: [
['label', { label: 'Label', type: 'text', validation: '' }],
['value', { label: 'Value', type: 'text', validation: 'none' }],
],
html: '<div id="#_id_#" class="panel panel-default"><div class="panel-body">#_delete_##_edit_#<span style="color:#999;font-size:13px;font-weight:bold">#_label_#</span>:#_value_#</div></div>',
columns: [
{
title: 'Label',
dataIndex: 'label',
key: 'label',
},
{
title: 'Option Value',
dataIndex: 'value',
key: 'value',
},
],
validation: 'none',
}],
['display_order', { label: 'Priority', type: 'text', validation: 'number' }],
['display_section', { label: 'Display Section', type: 'text', validation: 'none' }],
];
}
setTableType(type) {
this.tableType = type;
}
doCustomValidation(params) {
const validateName = function (str) {
const name = /^[a-z][a-z0-9._]+$/;
return str != null && name.test(str);
};
if (!validateName(params.name)) {
return 'Invalid name for custom field';
}
return null;
}
forceInjectValuesBeforeSave(params) {
const data = [params.name]; const options = []; let
optionsData;
data.push({});
data[1].label = params.field_label;
data[1].type = params.field_type;
data[1].validation = params.field_validation;
if (['select', 'select2', 'select2multi'].indexOf(params.field_type) >= 0) {
optionsData = (params.field_options === '' || params.field_options === undefined)
? [] : JSON.parse(params.field_options);
for (const index in optionsData) {
options.push([optionsData[index].value, optionsData[index].label]);
}
data[1].source = options;
}
if (params.field_validation == null || params.field_validation === undefined) {
params.field_validation = '';
}
params.data = JSON.stringify(data);
params.type = this.tableType;
return params;
}
}
export default ReactCustomFieldAdapter;
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import ReactDOM from 'react-dom';
import AdapterBase from './AdapterBase';
import IceFormDrawer from '../components/IceFormDrawer';
class ReactDrawerAdapterBase extends AdapterBase {
initForm() {
this.formContainer = React.createRef();
ReactDOM.render(
<IceFormDrawer
ref={this.formContainer}
fields={this.getFormFields()}
adapter={this}
formReference={this.formReference}
/>,
document.getElementById(`${this.tab}FormReact`),
);
}
renderForm(object) {
this.initForm();
this.formContainer.current.show(object);
}
}
export default ReactDrawerAdapterBase;
+150
View File
@@ -0,0 +1,150 @@
import ReactModalAdapterBase from './ReactModalAdapterBase';
class ReactLegacyModalAdapterBase extends ReactModalAdapterBase {
// Due to having same method in ReactModalAdapterBase
get(callBackData) {
const that = this;
if (this.getRemoteTable()) {
this.createTableServer(this.getTableName());
$(`#${this.getTableName()}Form`).hide();
$(`#${this.getTableName()}`).show();
return;
}
let sourceMappingJson = JSON.stringify(this.getSourceMapping());
let filterJson = '';
if (this.getFilter() !== null) {
filterJson = JSON.stringify(this.getFilter());
}
let orderBy = '';
if (this.getOrderBy() !== null) {
orderBy = this.getOrderBy();
}
sourceMappingJson = this.fixJSON(sourceMappingJson);
filterJson = this.fixJSON(filterJson);
that.showLoader();
$.post(this.moduleRelativeURL, {
t: this.table, a: 'get', sm: sourceMappingJson, ft: filterJson, ob: orderBy,
}, (data) => {
if (data.status === 'SUCCESS') {
that.getSuccessCallBack(callBackData, data.object);
} else {
that.getFailCallBack(callBackData, data.object);
}
}, 'json')
.fail((e) => {
if (e.status === 403) {
that.showMessage('Access Forbidden', e.responseJSON.message);
}
})
.always(() => { that.hideLoader(); });
that.initFieldMasterData();
this.trackEvent('get', this.tab, this.table);
// var url = this.getDataUrl();
// console.log(url);
}
showFilters(object) {
let formHtml = this.templates.filterTemplate;
let html = '';
const fields = this.getFilters();
for (let i = 0; i < fields.length; i++) {
const metaField = this.getMetaFieldForRendering(fields[i][0]);
if (metaField === '' || metaField === undefined) {
html += this.renderFormField(fields[i]);
} else {
const metaVal = object[metaField];
if (metaVal !== '' && metaVal != null && metaVal !== undefined && metaVal.trim() !== '') {
html += this.renderFormField(JSON.parse(metaVal));
} else {
html += this.renderFormField(fields[i]);
}
}
}
formHtml = formHtml.replace(/_id_/g, `${this.getTableName()}_filter`);
formHtml = formHtml.replace(/_fields_/g, html);
const randomFormId = this.generateRandom(14);
const $tempDomObj = $('<div class="reviewBlock popupForm" data-content="Form"></div>');
$tempDomObj.attr('id', randomFormId);
$tempDomObj.html(formHtml);
$tempDomObj.find('.datefield').datepicker({ viewMode: 2 });
$tempDomObj.find('.timefield').datetimepicker({
language: 'en',
pickDate: false,
});
$tempDomObj.find('.datetimefield').datetimepicker({
language: 'en',
});
$tempDomObj.find('.colorpick').colorpicker();
tinymce.init({
selector: `#${$tempDomObj.attr('id')} .tinymce`,
height: '400',
});
$tempDomObj.find('.simplemde').each(function () {
const simplemde = new SimpleMDE({ element: $(this)[0] });
$(this).data('simplemde', simplemde);
// simplemde.value($(this).val());
});
// $tempDomObj.find('.select2Field').select2();
$tempDomObj.find('.select2Field').each(function () {
$(this).select2().select2('val', $(this).find('option:eq(0)').val());
});
$tempDomObj.find('.select2Multi').each(function () {
$(this).select2().on('change', function (e) {
const parentRow = $(this).parents('.row');
const height = parentRow.find('.select2-choices').height();
parentRow.height(parseInt(height, 10));
});
});
/*
$tempDomObj.find('.signatureField').each(function() {
$(this).data('signaturePad',new SignaturePad($(this)));
});
*/
// var tHtml = $tempDomObj.wrap('<div>').parent().html();
this.showDomElement('Edit', $tempDomObj, null, null, true);
$('.filterBtn').off();
$('.filterBtn').on('click', (e) => {
e.preventDefault();
e.stopPropagation();
try {
modJs.filterQuery();
} catch (err) {
// Do Nothing
}
return false;
});
if (this.filter !== undefined && this.filter != null && this.filter !== '') {
this.fillForm(this.filter, `#${this.getTableName()}_filter`, this.getFilters());
}
}
resetFilters() {
this.filter = this.origFilter;
this.filtersAlreadySet = false;
$(`#${this.getTableName()}_resetFilters`).hide();
this.currentFilterString = '';
this.get([]);
}
}
export default ReactLegacyModalAdapterBase;
+262
View File
@@ -0,0 +1,262 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Space, Tag } from 'antd';
import {
EditOutlined, DeleteOutlined, CopyOutlined, MonitorOutlined,
} from '@ant-design/icons';
import AdapterBase from './AdapterBase';
import IceFormModal from '../components/IceFormModal';
import IceStepFormModal from '../components/IceStepFromModal';
import IceTable from '../components/IceTable';
import MasterDataReader from './MasterDataReader';
class ReactModalAdapterBase extends AdapterBase {
static get MODAL_TYPE_NORMAL() { return 'Normal'; }
static get MODAL_TYPE_STEPS() { return 'Steps'; }
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.modalType = this.MODAL_TYPE_NORMAL;
this.dataPipe = null;
this.formInitialized = false;
this.tableInitialized = false;
this.access = [];
this.localStorageEnabled = false;
this.isV2 = true;
this.masterDataReader = new MasterDataReader(this);
}
enableLocalStorage() {
this.localStorageEnabled = true;
}
setModalType(type) {
this.modalType = type;
}
setDataPipe(dataPipe) {
this.dataPipe = dataPipe;
}
setAccess(access) {
this.access = access;
}
hasAccess(type) {
return this.access.indexOf(type) > 0;
}
initTable() {
if (this.tableInitialized) {
return false;
}
const tableDom = document.getElementById(`${this.tab}Table`);
if (tableDom) {
this.tableContainer = React.createRef();
let columns = this.getTableColumns();
if (this.hasAccess('save') || this.hasAccess('delete') || this.hasAccess('element')) {
columns.push({
title: 'Actions',
key: 'actions',
render: this.getTableActionButtonJsx(this),
});
}
columns = columns.map((item) => {
item.title = this.gt(item.title);
return item;
});
ReactDOM.render(
<IceTable
ref={this.tableContainer}
reader={this.dataPipe}
columns={columns}
adapter={this}
>
{this.getTableChildComponents()}
</IceTable>,
tableDom,
);
}
this.tableInitialized = true;
return true;
}
initForm() {
if (this.formInitialized) {
return false;
}
this.formContainer = React.createRef();
if (this.modalType === this.MODAL_TYPE_NORMAL) {
ReactDOM.render(
<IceFormModal
ref={this.formContainer}
fields={this.getFormFields()}
adapter={this}
formReference={this.formReference}
/>,
document.getElementById(`${this.tab}Form`),
);
} else {
ReactDOM.render(
<IceStepFormModal
ref={this.formContainer}
fields={this.getMappedFields()}
adapter={this}
formReference={this.formReference}
/>,
document.getElementById(`${this.tab}Form`),
);
}
const filterDom = document.getElementById(`${this.tab}FilterForm`);
if (filterDom && this.getFilters()) {
this.filtersContainer = React.createRef();
ReactDOM.render(
<IceFormModal
ref={this.filtersContainer}
fields={this.getFilters()}
adapter={this}
saveCallback={(values, showError, closeModal) => {
this.setFilter(values);
this.filtersAlreadySet = true;
this.get([]);
this.tableContainer.current.setFilterData(values);
closeModal();
}}
/>,
filterDom,
);
}
this.formInitialized = true;
return true;
}
getTableChildComponents() {
return false;
}
reloadCurrentElement() {
this.viewElement(this.currentId);
}
getTableActionButtonJsx(adapter) {
return (text, record) => (
<Space size="middle">
{adapter.hasAccess('save') && adapter.showEdit
&& (
<Tag color="green" onClick={() => modJs.edit(record.id)} style={{ cursor: 'pointer' }}>
<EditOutlined />
{` ${adapter.gt('Edit')}`}
</Tag>
)}
{adapter.hasAccess('element')
&& (
<Tag color="blue" onClick={() => modJs.viewElement(record.id)} style={{ cursor: 'pointer' }}>
<MonitorOutlined />
{` ${adapter.gt('View')}`}
</Tag>
)}
{adapter.hasAccess('delete') && adapter.showDelete
&& (
<Tag color="volcano" onClick={() => modJs.deleteRow(record.id)} style={{ cursor: 'pointer' }}>
<DeleteOutlined />
{` ${adapter.gt('Delete')}`}
</Tag>
)}
{adapter.hasAccess('save')
&& (
<Tag color="cyan" onClick={() => modJs.copyRow(record.id)} style={{ cursor: 'pointer' }}>
<CopyOutlined />
{` ${adapter.gt('Copy')}`}
</Tag>
)}
</Space>
);
}
setTableLoading(value) {
this.tableContainer.current.setLoading(value);
}
/**
* Show the view form for an item
* @method viewElement
* @param id {int} id of the item to view
*/
viewElement(id) {
this.setTableLoading(true);
this.currentId = id;
this.getElement(id, {
noRender: true,
callBack: (element) => {
this.showElement(element);
this.setTableLoading(false);
},
});
}
showElement(element) {
this.renderForm(element, true);
}
/**
* Show the edit form for an item
* @method edit
* @param id {int} id of the item to edit
*/
edit(id) {
this.setTableLoading(true);
this.currentId = id;
this.getElement(id, []);
}
renderForm(object = null, viewOnly = false) {
if (object == null) {
this.currentId = null;
this.currentElement = null;
}
this.setTableLoading(false);
this.initForm();
this.formContainer.current.setViewOnly(viewOnly);
this.formContainer.current.show(object);
}
showFilters() {
this.initForm();
this.filtersContainer.current.show(this.filter);
}
resetFilters() {
this.filter = this.origFilter;
this.filtersAlreadySet = false;
this.currentFilterString = '';
this.get([]);
this.tableContainer.current.setFilterData(this.filter);
}
get() {
if (this.tableContainer && this.tableContainer.current) {
this.tableContainer.current.setCurrentElement(null);
}
this.initTable();
this.masterDataReader.updateAllMasterData()
.then(() => {
this.tableContainer.current.reload();
});
this.trackEvent('get', this.tab, this.table);
}
showLoader() {
// $('#iceloader').show();
}
}
export default ReactModalAdapterBase;
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

+244
View File
@@ -0,0 +1,244 @@
import React from "react";
import {Button, Form, Select, Space, Card, Modal, Table} from "antd";
// import IceDataGroupModal from "./IceDataGroupModal";
import IceFormModal from "./IceFormModal";
import ReactDOM from "react-dom";
const { Option } = Select;
class IceDataGroup extends React.Component {
state = {};
constructor(props) {
super(props);
this.onChange = props.onChange;
this.formReference = React.createRef();
}
render() {
const { field, adapter } = this.props;
let { value } = this.props;
value = this.parseValue(value);
value = value.map(item => ({ ...item, key:item.id } ));
const columns = JSON.parse(JSON.stringify(field[1].columns));
if (!this.props.readOnly) {
columns.push({
title: 'Action',
key: 'action',
render: (text, record) => (
this.getDefaultButtons(record.id)
),
});
}
return (
<>
{!this.props.readOnly &&
<Space direction="horizontal">
<Button type="link" htmlType="button" onClick={() => {
this.createForm(field, adapter, {})
}}>
Add
</Button>
<Button type="link" htmlType="button" danger onClick={() => {
this.resetDataGroup()
}}>
Reset
</Button>
</Space>
}
<Table columns={columns} dataSource={value} />
</>
);
}
createForm(field, adapter, object) {
this.formContainer = React.createRef();
const formFields = field[1].form;
formFields.unshift(['id', { label: 'ID', type: 'hidden' }]);
ReactDOM.render(
<IceFormModal
ref={this.formContainer}
fields={formFields}
title={this.props.title}
adapter={adapter}
formReference={this.formReference}
saveCallback={this.save.bind(this)}
cancelCallback={this.unmountForm.bind(this)}
/>,
document.getElementById('dataGroup'),
);
this.formContainer.current.show(object);
}
unmountForm() {
ReactDOM.unmountComponentAtNode(document.getElementById('dataGroup'));
}
show(data) {
if (!data) {
this.setState({ visible: true });
this.updateFields(data);
} else {
this.setState({ visible: true });
if (this.formReference.current) {
this.updateFields(data);
} else {
this.waitForIt(
() => this.formReference.current != null,
() => { this.updateFields(data); },
100,
);
}
}
}
parseValue(value) {
try {
value = JSON.parse(value);
} catch (e) {
value = [];
}
if (value == null) {
value = [];
}
return value;
}
save(params, errorCallback, closeCallback) {
const {field, value } = this.props;
if (field[1]['custom-validate-function'] != null) {
let tempParams = field[1]['custom-validate-function'].apply(this, [params]);
if (tempParams.valid) {
params = tempParams.params;
} else {
errorCallback(tempParams.message);
return false;
}
}
const data = this.parseValue(value);
let newData = [];
if (!params.id) {
params.id = `${field[0]}_${this.dataGroupGetNextAutoIncrementId(data)}`;
data.push(params);
newData = data;
} else {
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.id !== params.id) {
newData.push(item);
} else {
newData.push(params);
}
}
}
if (field[1]['sort-function'] != null) {
newData.sort(field[1]['sort-function']);
}
const val = JSON.stringify(newData);
this.onChange(val);
this.unmountForm();
}
createCard(item) {
const { field } = this.props;
if (field[1]['pre-format-function'] != null) {
item = field[1]['pre-format-function'].apply(this, [item]);
}
const template = field[1].html;
let t = template.replace('#_delete_#', '');
t = t.replace('#_edit_#', '');
t = t.replace(/#_id_#/g, item.id);
for (const key in item) {
let itemVal = item[key];
if (itemVal !== undefined && itemVal != null && typeof itemVal === 'string') {
itemVal = itemVal.replace(/(?:\r\n|\r|\n)/g, '<br />');
}
t = t.replace(`#_${key}_#`, itemVal);
}
if (field[1].render !== undefined && field[1].render != null) {
t = t.replace('#_renderFunction_#', field[1].render(item));
}
return (
<Card key={item.id} title="" extra={this.getDefaultButtons(item.id)}>
<div dangerouslySetInnerHTML={{ __html: t }}></div>
</Card>
);
}
getDefaultButtons(id) {
return (
<Space>
<a href="#" onClick={() => {this.editDataGroupItem(id)}}><li className="fa fa-edit"/></a>
<a href="#" onClick={() => {this.deleteDataGroupItem(id)}}><li className="fa fa-times"/></a>
</Space>
);
}
deleteDataGroupItem(id) {
const {value} = this.props;
const data = this.parseValue(value);
const newVal = [];
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.id !== id) {
newVal.push(item);
}
}
const val = JSON.stringify(newVal);
this.onChange(val);
}
editDataGroupItem(id) {
const { field, adapter, value } = this.props;
const data = this.parseValue(value);
let editVal = {};
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.id === id) {
editVal = item;
}
}
this.createForm(field, adapter, editVal);
}
resetDataGroup() {
this.onChange('[]');
}
dataGroupGetNextAutoIncrementId(data) {
let autoId = 1; let id;
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.id === undefined || item.id == null) {
item.id = 1;
}
id = item.id.substring(item.id.lastIndexOf('_') + 1, item.id.length);
if (id >= autoId) {
autoId = parseInt(id, 10) + 1;
}
}
return autoId;
}
}
export default IceDataGroup;
+454
View File
@@ -0,0 +1,454 @@
import React from 'react';
import {
Alert, Col, DatePicker, TimePicker, Form, Input, Row,
} from 'antd';
import moment from 'moment';
import IceUpload from './IceUpload';
import IceDataGroup from './IceDataGroup';
import IceSelect from './IceSelect';
import IceLabel from './IceLabel';
const ValidationRules = {
float(str) {
const floatstr = /^[-+]?[0-9]+(\.[0-9]+)?$/;
if (str != null && str.match(floatstr)) {
return true;
}
return false;
},
number(str) {
const numstr = /^[0-9]+$/;
if (str != null && str.match(numstr)) {
return true;
}
return false;
},
numberOrEmpty(str) {
if (str === '') {
return true;
}
const numstr = /^[0-9]+$/;
if (str != null && str.match(numstr)) {
return true;
}
return false;
},
email(str) {
const emailPattern = /^\s*[\w\-+_]+(\.[\w\-+_]+)*@[\w\-+_]+\.[\w\-+_]+(\.[\w\-+_]+)*\s*$/;
return str != null && emailPattern.test(str);
},
emailOrEmpty(str) {
if (str === '') {
return true;
}
const emailPattern = /^\s*[\w\-+_]+(\.[\w\-+_]+)*@[\w\-+_]+\.[\w\-+_]+(\.[\w\-+_]+)*\s*$/;
return str != null && emailPattern.test(str);
},
username(str) {
const username = /^[a-zA-Z0-9.-]+$/;
return str != null && username.test(str);
},
};
class IceForm extends React.Component {
constructor(props) {
super(props);
this.validationRules = {};
this.state = {
validations: {},
errorMsg: false,
};
this.formReference = React.createRef();
}
showError(errorMsg) {
this.setState({ errorMsg });
}
hideError() {
this.setState({ errorMsg: false });
}
isReady() {
return this.formReference.current != null;
}
validateFields() {
return this.formReference.current.validateFields();
}
render() {
const { fields, twoColumnLayout } = this.props;
const formInputs1 = [];
const formInputs2 = [];
const columns = !twoColumnLayout ? 1 : 2;
for (let i = 0; i < fields.length; i++) {
const formInput = this.createFromField(fields[i], this.props.viewOnly);
if (formInput != null) {
if (columns === 1) {
formInputs1.push(formInput);
} else if (i % 2 === 0) {
formInputs1.push(formInput);
} else {
formInputs2.push(formInput);
}
}
}
const onFormLayoutChange = () => {};
return (
<Form
ref={this.formReference}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
layout={this.props.layout || 'horizontal'}
initialValues={{ size: 'middle' }}
onValuesChange={onFormLayoutChange}
size="middle"
>
{this.state.errorMsg
&& (
<>
<Alert message={this.state.errorMsg} type="error" showIcon />
<br />
</>
)}
{columns === 1 && formInputs1}
{columns === 2 && (
<Row gutter={16}>
<Col className="gutter-row" span={12}>
{formInputs1}
</Col>
<Col className="gutter-row" span={12}>
{formInputs2}
</Col>
</Row>
)}
</Form>
);
}
isValid() {
return Object.keys(this.validationRules).reduce((acc, fieldName) => acc && (this.state[fieldName] === 'success' || this.state[fieldName] == null), true);
}
validateOnChange(event) {
const validationRule = this.validationRules[event.target.id];
const { validations } = this.state;
if (validationRule) {
if (validationRule.rule(event.target.value)) {
this.state[event.target.id] = 'success';
this.state[`${event.target.id}_message`] = null;
} else {
this.state[event.target.id] = 'error';
this.state[`${event.target.id}_message`] = validationRule.message;
}
}
this.setState({ validations });
}
createFromField(field, viewOnly = false) {
let userId = 0;
const rules = [];
const requiredRule = { required: true };
const [name, data] = field;
const { adapter, layout } = this.props;
let validationRule = null;
data.label = adapter.gt(data.label);
const labelSpan = layout === 'vertical' ? { span: 24 } : { span: 6 };
const tempSelectBoxes = ['select', 'select2', 'select2multi'];
if (tempSelectBoxes.indexOf(data.type) >= 0 && data['allow-null'] === true) {
requiredRule.required = false;
} else if (data.validation === 'none'
|| data.validation === 'emailOrEmpty'
|| data.validation === 'numberOrEmpty'
) {
requiredRule.required = false;
} else {
requiredRule.required = true;
requiredRule.message = this.generateFieldMessage(data.label);
}
rules.push(requiredRule);
if (data.type === 'hidden') {
requiredRule.required = false;
return (
<Form.Item
labelCol={labelSpan}
style={{ display: 'none' }}
label={data.label}
key={name}
name={name}
rules={rules}
>
<Input />
</Form.Item>
);
} if (data.type === 'text') {
if (data.validation) {
data.validation = data.validation.replace('OrEmpty', '');
validationRule = this.getValidationRule(data);
if (validationRule) {
this.validationRules[name] = {
rule: validationRule,
message: `Invalid value for ${data.label}`,
};
}
}
if (validationRule != null) {
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
validateStatus={this.state[name]}
help={this.state[`${name}_message`]}
>
{viewOnly
? <IceLabel />
: <Input onChange={this.validateOnChange.bind(this)} />}
</Form.Item>
);
}
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
>
{viewOnly
? <IceLabel />
: <Input />}
</Form.Item>
);
} if (data.type === 'textarea') {
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
>
{viewOnly
? <IceLabel />
: <Input.TextArea />}
</Form.Item>
);
} if (data.type === 'date') {
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
>
<DatePicker disabled={viewOnly} />
</Form.Item>
);
} if (data.type === 'datetime') {
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
>
<DatePicker format="YYYY-MM-DD HH:mm:ss" disabled={viewOnly} />
</Form.Item>
);
} if (data.type === 'time') {
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
>
<TimePicker
format="HH:mm"
disabled={viewOnly}
/>
</Form.Item>
);
} if (data.type === 'fileupload') {
const currentEmployee = adapter.getCurrentProfile();
if (currentEmployee != null) {
userId = currentEmployee.id;
} else {
userId = adapter.getUser().id * -1;
}
if (data.filetypes == null) {
data.filetypes = '.doc,.docx,.xml,'
+ 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,'
+ 'image/*,'
+ '.pdf';
}
return (
<Form.Item
labelCol={labelSpan}
name={name}
key={name}
label={data.label}
>
<IceUpload
user={userId}
fileGroup={adapter.tab}
fileName={name}
adapter={adapter}
accept={data.filetypes}
readOnly={viewOnly}
/>
</Form.Item>
);
} if (data.type === 'datagroup') {
return (
<Form.Item
labelCol={labelSpan}
name={name}
key={name}
label={data.label}
>
<IceDataGroup
adapter={adapter}
field={field}
title={data.label}
readOnly={viewOnly}
/>
</Form.Item>
);
} if (data.type === 'select2' || data.type === 'select' || data.type === 'select2multi') {
return (
<Form.Item
labelCol={labelSpan}
label={data.label}
key={name}
name={name}
rules={rules}
>
<IceSelect
adapter={adapter}
field={field}
readOnly={viewOnly}
/>
</Form.Item>
);
}
return null;
}
generateFieldMessage(label) {
return `${label}: ${this.props.adapter.gt('is required')}`;
}
getValidationRule(data) {
if (ValidationRules[data.validation] == null) {
return null;
}
return ValidationRules[data.validation];
}
dataToFormFields(data, fields) {
for (let i = 0; i < fields.length; i++) {
const [key, formInputData] = fields[i];
if (formInputData.type === 'date') {
data[key] = data[key] ? moment(data[key], 'YYYY-MM-DD') : null;
} else if (formInputData.type === 'datetime') {
data[key] = data[key] ? moment(data[key], 'YYYY-MM-DD HH:mm:ss') : null;
} else if (formInputData.type === 'time') {
data[key] = data[key] ? moment(data[key], 'HH:mm') : null;
}
}
return data;
}
formFieldsToData(params, fields) {
for (let i = 0; i < fields.length; i++) {
const [key, formInputData] = fields[i];
if (formInputData.type === 'date') {
params[key] = params[key] ? params[key].format('YYYY-MM-DD') : 'NULL';
} else if (formInputData.type === 'datetime') {
params[key] = params[key] ? params[key].format('YYYY-MM-DD HH:mm:ss') : 'NULL';
} else if (formInputData.type === 'time') {
params[key] = params[key] ? params[key].format('HH:mm') : 'NULL';
} else if ((formInputData.type === 'select' || formInputData.type === 'select2') && params[key] == null) {
params[key] = 'NULL';
}
}
return params;
}
updateFields(data) {
const { fields } = this.props;
data = this.dataToFormFields(data, fields);
this.formReference.current.resetFields();
if (data == null) {
return;
}
try {
this.formReference.current.setFieldsValue(data);
} catch (e) {
console.log(e);
}
}
resetFields() {
this.formReference.current.resetFields();
}
setFieldsValue(data) {
this.formReference.current.setFieldsValue(data);
}
save(params, success) {
const { adapter, fields } = this.props;
let values = params;
values = adapter.forceInjectValuesBeforeSave(values);
const msg = adapter.doCustomValidation(values);
if (msg !== null) {
this.showError(msg);
return;
}
if (adapter.csrfRequired) {
values.csrf = $(`#${adapter.getTableName()}Form`).data('csrf');
}
const id = (adapter.currentElement != null) ? adapter.currentElement.id : null;
if (id != null && id !== '') {
values.id = id;
}
values = this.formFieldsToData(values, fields);
adapter.add(values, [], () => adapter.get([]), () => {
this.formReference.current.resetFields();
this.showError(false);
success();
});
}
}
export default IceForm;
+55
View File
@@ -0,0 +1,55 @@
import React from 'react';
import { Button, Drawer } from 'antd';
import IceFormModal from './IceFormModal';
class IceFormDrawer extends IceFormModal {
render() {
const { fields, adapter } = this.props;
return (
<Drawer
title={this.props.adapter.gt(adapter.objectTypeName)}
width={720}
onClose={() => this.hide()}
visible={this.state.visible}
bodyStyle={{ paddingBottom: 80 }}
zIndex={1200}
maskClosable={false}
footer={(
<div
style={{
textAlign: 'right',
}}
>
<Button
onClick={() => this.hide()}
style={{ marginRight: 8 }}
>
Cancel
</Button>
<Button
onClick={() => {
const form = this.formReference.current;
form
.validateFields()
.then((values) => {
this.save(values);
})
.catch((info) => {
// this.showError(`Validate Failed: ${info.errorFields[0].errors[0]}`);
});
}}
type="primary"
>
Submit
</Button>
</div>
)}
>
{this.createForm(fields, adapter)}
</Drawer>
);
}
}
export default IceFormDrawer;
+150
View File
@@ -0,0 +1,150 @@
import React from 'react';
import {
Button, Col, Modal, Row, Space,
} from 'antd';
import IceForm from './IceForm';
class IceFormModal extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
viewOnly: false,
loading: false,
};
this.iceFormReference = React.createRef();
this.width = 800;
}
setViewOnly(value) {
this.setState({ viewOnly: value });
}
show(data) {
if (!data) {
this.setState({ visible: true });
if (this.iceFormReference.current) {
this.iceFormReference.current.resetFields();
}
} else {
this.setState({ visible: true });
if (this.iceFormReference.current && this.iceFormReference.current.isReady()) {
this.iceFormReference.current.updateFields(data);
} else {
this.waitForIt(
() => this.iceFormReference.current && this.iceFormReference.current.isReady(),
() => { this.iceFormReference.current.updateFields(data); },
1000,
);
}
}
}
waitForIt(condition, callback, time) {
setTimeout(() => {
if (condition()) {
callback();
} else {
this.waitForIt(condition, callback, time);
}
}, time);
}
hide() {
this.setState({ visible: false });
}
save(params) {
this.iceFormReference.current.save(params, () => { this.closeModal(); });
}
closeModal() {
this.hide();
this.iceFormReference.current.showError(false);
}
render() {
const {
fields, adapter, saveCallback, cancelCallback,
} = this.props;
const additionalProps = {};
additionalProps.footer = (
<Row gutter={16}>
<Col className="gutter-row" span={12} style={{}} />
<Col className="gutter-row" span={12} style={{ textAlign: 'right' }}>
<Space>
<Button onClick={() => {
if (cancelCallback) {
cancelCallback();
} else {
this.closeModal();
}
}}
>
{this.props.adapter.gt('Cancel')}
</Button>
<Button
loading={this.state.loading}
type="primary"
onClick={() => {
this.setState({ loading: true });
const iceFrom = this.iceFormReference.current;
iceFrom
.validateFields()
.then((values) => {
if (!iceFrom.isValid()) {
this.setState({ loading: false });
return;
}
if (saveCallback) {
saveCallback(values, iceFrom.showError.bind(this), this.closeModal.bind(this));
} else {
this.save(values);
}
this.setState({ loading: false });
})
.catch((info) => {
this.setState({ loading: false });
});
}}
>
{this.state.viewOnly ? this.props.adapter.gt('Done') : this.props.adapter.gt('Save')}
</Button>
</Space>
</Col>
</Row>
);
if (this.state.viewOnly) {
additionalProps.footer = null;
}
return (
<Modal
visible={this.state.visible}
title={this.props.adapter.gt(this.props.title || adapter.objectTypeName)}
maskClosable={false}
width={this.width}
onCancel={() => {
if (cancelCallback) {
cancelCallback();
} else {
this.closeModal();
}
}}
{...additionalProps}
>
<IceForm
ref={this.iceFormReference}
adapter={adapter}
fields={fields}
viewOnly={this.state.viewOnly}
/>
</Modal>
);
}
}
export default IceFormModal;
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { Space } from 'antd';
class IceLabel extends React.Component {
constructor(props) {
super(props);
}
render() {
const { value } = this.props;
return (
<Space>
<div contentEditable='true' dangerouslySetInnerHTML={{ __html: this.nl2br(value || '') }}></div>
</Space>
);
}
nl2br(str) {
return (`${str}`).replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '<br />');
}
}
export default IceLabel;
+113
View File
@@ -0,0 +1,113 @@
import React from 'react';
import { Form, Select } from 'antd';
const { Option } = Select;
class IceSelect extends React.Component {
constructor(props) {
super(props);
this.onChange = props.onChange;
}
render() {
let options;
const { field, adapter } = this.props;
let { value } = this.props;
const data = field[1];
if (data['remote-source'] != null) {
let key = `${data['remote-source'][0]}_${data['remote-source'][1]}_${data['remote-source'][2]}`;
if (data['remote-source'].length === 4) {
key = `${key}_${data['remote-source'][3]}`;
}
options = adapter.fieldMasterData[key];
} else {
options = data.source;
}
const optionData = this.getFormSelectOptionsRemote(options, field, adapter);
// value should be an array if multi-select
if (data.type === 'select2multi') {
try {
value = JSON.parse(value);
if (value == null) {
value = [];
}
value = value.map((item) => `${item}`);
} catch (e) {
value = [];
}
}
return (
<Select
mode={data.type === 'select2multi' ? 'multiple' : undefined}
showSearch
placeholder={`Select ${data.label}`}
optionFilterProp="children"
filterOption={
(input, option) => input != null
&& option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
value={value}
options={optionData}
allowClear
onChange={this.handleChange.bind(this)}
disabled={this.props.readOnly}
/>
);
}
handleChange(value) {
const { field } = this.props;
const data = field[1];
if (data.type === 'select2multi') {
this.onChange(JSON.stringify(value));
} else {
this.onChange(value);
}
}
makeOption(option) {
return <Option key={`${option[0]}`} value={`${option[0]}`}>{option[1]}</Option>;
}
getFormSelectOptionsRemote(options, field, adapter) {
const optionData = [];
if (Array.isArray(options)) {
for (let i = 0; i < options.length; i++) {
optionData.push({
label: options[i][1],
value: options[i][0],
});
}
} else {
for (const key in options) {
optionData.push({
label: options[key],
value: key,
});
}
}
// if (field[1].sort === 'true') {
// tuples.sort((a, b) => {
// a = a[1];
// b = b[1];
//
// // eslint-disable-next-line no-nested-ternary
// return a < b ? -1 : (a > b ? 1 : 0);
// });
// }
// for (let i = 0; i < tuples.length; i++) {
// const prop = tuples[i][0];
// const value = tuples[i][1];
// optionData.push([prop, adapter.gt(value)]);
// }
return optionData;
}
}
export default IceSelect;
+242
View File
@@ -0,0 +1,242 @@
import React from 'react';
import {
Button, Divider, Steps, Row, Col, Space,
} from 'antd';
import IceForm from './IceForm';
const { Step } = Steps;
class IceStepForm extends IceForm {
constructor(props) {
super(props);
this.onChange = props.onChange;
let steps = this.props.fields.map((item) => ({
...item,
ref: React.createRef(),
}));
steps = steps.map((item) => {
const { ref, fields } = item;
item.content = (
<IceForm
ref={ref}
adapter={props.adapter}
fields={fields}
twoColumnLayout={props.twoColumnLayout}
width={props.width}
layout={props.layout || 'horizontal'}
/>
);
return item;
});
this.state = {
current: 0,
steps,
loading: false,
};
}
moveToStep(current) {
this.setState({ current });
}
next() {
if (this.validateFields(false) === false) {
return;
}
this.showError(false);
const current = this.state.current + 1;
this.setState({ current });
}
prev() {
const current = this.state.current - 1;
if (current < 0) {
return;
}
this.setState({ current });
}
render() {
const { adapter } = this.props;
const { current, steps } = this.state;
return (
<>
<Steps current={current}>
{steps.map((item, index) => (
<Step key={item.title} title={item.title} onClick={() => this.moveToStep(index)} />
))}
</Steps>
<Divider />
<div className="steps-content">
{steps.map((item, index) => (
<div style={{ display: index === current ? 'block' : 'none' }}>
{item.content}
</div>
))}
</div>
<Divider />
<div className="steps-action">
<Row gutter={16}>
<Col className="gutter-row" span={12} style={{}}>
<Space>
{current < steps.length - 1 && (
<Button type="primary" onClick={() => this.next()}>
{adapter.gt('Next')}
</Button>
)}
{current > 0 && (
<Button onClick={() => this.prev()}>
{adapter.gt('Previous')}
</Button>
)}
</Space>
</Col>
<Col className="gutter-row" span={12} style={{ textAlign: 'right' }}>
<Space>
<Button onClick={() => this.props.closeModal()}>
{adapter.gt('Cancel')}
</Button>
<Button type="primary" loading={this.state.loading} onClick={() => this.saveData()}>
{adapter.gt('Save')}
</Button>
</Space>
</Col>
</Row>
</div>
</>
);
}
async saveData() {
this.setState({ loading: true });
const data = await this.validateFields(true);
if (data) {
this.save(data, () => this.props.closeModal());
}
this.setState({ loading: false });
}
save(params, success) {
const { adapter } = this.props;
adapter.add(params, [], () => adapter.get([]), () => {
this.resetFields();
this.showError(false);
success();
});
}
updateFields(data) {
this.state.steps.forEach((item) => {
const subData = {};
item.fields.forEach(([key]) => {
subData[key] = data[key];
});
this.updateFieldsSubForm(item.ref, item.fields, subData);
});
}
updateFieldsSubForm(ref, fields, data) {
data = this.dataToFormFields(data, fields);
ref.current.resetFields();
if (data == null) {
return;
}
try {
ref.current.setFieldsValue(data);
} catch (e) {
console.log(e);
}
}
async validateFields(all) {
const { adapter } = this.props;
const steps = all ? this.state.steps : this.state.steps.slice(0, this.state.current + 1);
const promiseList = steps.map(
(item) => item.ref.current.validateFields()
.then((values) => {
if (!item.ref.current.isValid()) {
return false;
}
return values;
})
.catch(() => false),
);
const allData = await Promise.all(promiseList);
const failedIndex = allData.findIndex((item) => item === false);
if (failedIndex >= 0) {
this.setState({ current: failedIndex });
return false;
}
let values = Object.assign({}, ...allData);
values = adapter.forceInjectValuesBeforeSave(values);
const msg = adapter.doCustomValidation(values);
if (msg !== null) {
this.showError(msg);
return false;
}
if (adapter.csrfRequired) {
values.csrf = $(`#${adapter.getTableName()}Form`).data('csrf');
}
const id = (adapter.currentElement != null) ? adapter.currentElement.id : null;
if (id != null && id !== '') {
values.id = id;
}
const fields = [].concat.apply([], this.state.steps.map((item) => item.fields));
return this.formFieldsToData(values, fields);
}
getSubFormData(ref, fields, params) {
const { adapter } = this.props;
let values = params;
values = adapter.forceInjectValuesBeforeSave(values);
const msg = adapter.doCustomValidation(values);
if (msg !== null) {
ref.current.showError(msg);
return;
}
if (adapter.csrfRequired) {
values.csrf = $(`#${adapter.getTableName()}Form`).data('csrf');
}
const id = (adapter.currentElement != null) ? adapter.currentElement.id : null;
if (id != null && id !== '') {
values.id = id;
}
return this.formFieldsToData(values, fields);
}
showError(errorMsg) {
this.state.steps.forEach((item) => item.ref.current.showError(errorMsg));
}
resetFields() {
this.state.steps.forEach((item) => item.ref.current.resetFields());
}
hideError() {
this.state.steps.forEach((item) => item.ref.current.hideError());
}
isReady() {
return this.state.steps.reduce((acc, item) => acc && item.ref.current != null, true);
}
}
export default IceStepForm;
+68
View File
@@ -0,0 +1,68 @@
import React from 'react';
import { Modal } from 'antd';
import IceFormModal from './IceFormModal';
import IceStepForm from './IceStepForm';
class IceStepFormModal extends IceFormModal {
constructor(props) {
super(props);
this.width = 850;
}
show(data) {
if (!data) {
this.setState({ visible: true });
if (this.iceFormReference.current) {
this.iceFormReference.current.resetFields();
}
} else {
this.setState({ visible: true });
if (this.iceFormReference.current && this.iceFormReference.current.isReady()) {
this.iceFormReference.current.moveToStep(0);
this.iceFormReference.current.updateFields(data);
} else {
this.waitForIt(
() => this.iceFormReference.current && this.iceFormReference.current.isReady(),
() => {
this.iceFormReference.current.updateFields(data);
this.iceFormReference.current.moveToStep(0);
},
1000,
);
}
}
}
hide() {
this.iceFormReference.current.moveToStep(0);
this.setState({ visible: false });
}
render() {
const { fields, adapter } = this.props;
const { width, twoColumnLayout, layout } = this.props.adapter.getFormOptions();
return (
<Modal
visible={this.state.visible}
title={this.props.adapter.gt(this.props.title || adapter.objectTypeName)}
maskClosable={false}
width={width || this.width}
footer={[]}
onCancel={() => {
this.closeModal();
}}
>
<IceStepForm
ref={this.iceFormReference}
adapter={adapter}
fields={fields}
closeModal={() => { this.closeModal(); }}
twoColumnLayout={twoColumnLayout || false}
layout={layout}
/>
</Modal>
);
}
}
export default IceStepFormModal;
+250
View File
@@ -0,0 +1,250 @@
import React, {Component} from 'react';
import {Col, Form, Input, Row, Table, Space, Button, Tag, message} from 'antd';
import {
FilterOutlined,
PlusCircleOutlined,
} from '@ant-design/icons';
const { Search } = Input;
class IceTable extends React.Component {
state = {
data: [],
pagination: {},
loading: true,
fetchConfig: false,
//filter: null,
showLoading: true,
currentElement: null,
fetchCompleted: false,
};
constructor(props) {
super(props);
}
componentDidMount() {
const fetchConfig = {
page: 1,
};
message.config({
top: 40,
});
this.setState({
fetchConfig,
//filter: this.props.adapter.filter,
pagination: { 'pageSize': this.props.reader.pageSize }
});
//this.fetch(fetchConfig);
}
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
const { search } = this.state;
pager.current = pagination.current;
this.setState({
pagination: pager,
});
const fetchConfig = {
limit: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
filters: JSON.stringify(filters),
search: search,
};
this.setState({
fetchConfig
});
this.fetch(fetchConfig);
};
reload = () => {
const fetchConfig = this.state.fetchConfig;
if (fetchConfig) {
this.fetch(fetchConfig)
}
};
search = (value) => {
console.log('search table:' + value);
this.setState({ search: value });
const fetchConfig = this.state.fetchConfig;
console.log(fetchConfig);
if (fetchConfig) {
fetchConfig.search = value;
this.setState({
fetchConfig
});
this.fetch(fetchConfig)
}
}
addNew = () => {
this.props.adapter.renderForm();
}
showFilters = () => {
this.props.adapter.showFilters();
}
setFilterData = (filter) => {
this.setState({
filter,
});
}
setCurrentElement = (currentElement) => {
this.setState({currentElement});
}
setLoading(value) {
this.setState({ loading: value });
}
fetch = (params = {}) => {
console.log('params:', params);
//this.setState({ loading: this.state.showLoading });
this.setState({ loading: true });
//const hideMessage = message.loading({ content: 'Loading Latest Data ...', key: 'loadingTable', duration: 1});
const pagination = { ...this.state.pagination };
console.log('pagination:', pagination);
if (this.props.adapter.localStorageEnabled) {
try {
const cachedResponse = this.props.reader.getCachedResponse(params);
if (cachedResponse.items) {
this.setState({
loading: false,
data: cachedResponse.items,
pagination,
showLoading: false,
});
} else {
this.props.reader.clearCachedResponse(params);
}
} catch (e) {
this.props.reader.clearCachedResponse(params);
}
}
this.props.reader.get(params)
.then(data => {
// Read total count from server
// pagination.total = data.totalCount;
pagination.total = data.total;
//hideMessage();
// setTimeout(
// () => message.success({ content: 'Loading Completed!', key: 'loadingSuccess', duration: 1 }),
// 600
// );
this.setState({
loading: false,
data: data.items,
pagination,
showLoading: false,
fetchCompleted: true,
});
});
};
getChildrenWithProps(element) {
const childrenWithProps = React.Children.map(this.props.children, child => {
// checking isValidElement is the safe way and avoids a typescript error too
const props = {
element,
adapter: this.props.adapter,
loading: this.state.loading,
};
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
}
return child;
});
return childrenWithProps;
}
render() {
return (
<Row direction="vertical" style={{ width: '100%' }}>
{!this.state.currentElement &&
<Col span={24}>
<Row gutter={24}>
<Col span={18}>
<Space>
{this.props.adapter.hasAccess('save') && this.props.adapter.getShowAddNew() &&
<Button type="primary" onClick={this.addNew}><PlusCircleOutlined/> Add New</Button>
}
{this.props.adapter.getFilters() &&
<Button onClick={this.showFilters}><FilterOutlined/> Filters</Button>
}
{this.state.fetchCompleted
&& this.props.adapter.getFilters()
&& this.props.adapter.filter != null
&& this.props.adapter.filter !== []
&& this.props.adapter.filter !== ''
&& this.props.adapter.getFilterString(this.props.adapter.filter) !== '' &&
<Tag closable
style={{'lineHeight': '30px'}}
color="blue"
onClose={() => this.props.adapter.resetFilters()}
visible={this.props.adapter.filter != null && this.props.adapter.filter !== [] && this.props.adapter.filter !== ''}
>
{this.props.adapter.getFilterString(this.props.adapter.filter)}
</Tag>
}
</Space>
</Col>
<Col span={6}>
<Form
ref={(formRef) => this.form = formRef}
name="advanced_search"
className="ant-advanced-search-form"
>
<Form.Item name="searchTerm" label=""
rules={[
{
required: false,
},
]}
>
<Search
placeholder="input search text"
enterButton="Search"
onSearch={value => this.search(value)}
/>
</Form.Item>
</Form>
</Col>
</Row>
<Row gutter={24}>
<Col span={24}>
<Table
// bordered
rowClassName={(record, index) => index % 2 === 0 ? 'table-row-light' : 'table-row-dark'}
columns={this.props.columns}
rowKey={record => record.id}
dataSource={this.state.data}
pagination={this.state.pagination}
loading={this.state.loading}
onChange={this.handleTableChange}
reader={this.props.dataPipe}
/>
</Col>
</Row>
</Col>
}
{this.state.currentElement &&
this.getChildrenWithProps(this.state.currentElement)
}
</Row>
);
}
}
export default IceTable;
+157
View File
@@ -0,0 +1,157 @@
import React from "react";
import {Button, message, Space, Upload, Tag} from "antd";
import { UploadOutlined, DownloadOutlined, DeleteOutlined } from '@ant-design/icons';
class IceUpload extends React.Component {
state = {
fileList: [],
uploaded: false,
};
_isMounted = false;
constructor(props) {
super(props);
this.onChange = props.onChange;
}
componentDidMount() {
this._isMounted = true;
message.config({
top: 55,
duration: 2,
});
}
componentWillUnmount() {
this._isMounted = false;
}
handleDelete = () => {
this.setState({ fileList: [], value: null, uploaded: false});
this.onChange(null);
};
handleView = () => {
let currentValue = this.props.value;
if (this.state.value != null && this.state.value !== '') {
currentValue = this.state.value;
}
if (currentValue == null || currentValue === '') {
message.error('File not found');
return;
}
const { adapter } = this.props;
adapter.getFile(currentValue)
.then((data) => {
const file = {
key: data.uid,
uid: data.uid,
name: data.name,
status: data.status,
url: data.filename,
};
window.open(file.url);
}).catch((e) => {
});
};
handleChange = info => {
let fileList = [...info.fileList];
if (fileList.length === 0) {
this.setState({ value: null });
this.onChange(null);
this.setState({fileList: []});
this.setState({uploaded: false});
return;
}
fileList = fileList.slice(-1);
if (fileList[0].response && fileList[0].response.status === 'error') {
this.setState({ value: null });
this.onChange(null);
this.setState({fileList: []});
this.setState({uploaded: false});
message.error(`Error: ${fileList[0].response.message}`);
return;
}
fileList = fileList.map(file => {
if (file.response) {
// Component will show file.url as link
file.name = file.response.name;
file.url = file.response.url;
}
return file;
});
this.setState({fileList});
this.setState({ value: this.getFileName(fileList), uploaded: true });
this.onChange(this.getFileName(fileList));
};
getFileName(fileList) {
let file = null;
if (fileList) {
file = fileList[0];
}
return file ? file.name : '';
}
generateRandom(length) {
const d = new Date();
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
for (let i = length; i > 0; --i) result += chars[Math.round(Math.random() * (chars.length - 1))];
return result + d.getTime();
}
render() {
let fileName = this.generateRandom(14);
const props = {
action: `${window.CLIENT_BASE_URL}fileupload-new.php?user=${this.props.user}&file_group=${this.props.fileGroup}&file_name=${fileName}`,
onChange: this.handleChange,
onRemove: this.handleDelete,
multiple: false,
listType: 'picture',
};
return (
<Space direction={'vertical'}>
{!this.props.readOnly &&
<Space>
<Upload {...props} fileList={this.state.fileList}>
<Tag color="blue" style={{ cursor: 'pointer' }}>
<UploadOutlined />
{' '}
Upload
</Tag>
</Upload>
</Space>
}
<Space>
{ (((this.props.value != null && this.props.value !== '') || (this.state.value != null && this.state.value !== '')) && !this.state.uploaded) &&
<Button type="link" htmlType="button" onClick={this.handleView}>
<DownloadOutlined/> View File
</Button>
}
{ (((this.props.value != null && this.props.value !== '') || (this.state.value != null && this.state.value !== '')) && !this.state.uploaded && !this.props.readOnly) &&
<Button type="link" htmlType="button" danger onClick={this.handleDelete}>
<DeleteOutlined/> Delete
</Button>
}
</Space>
</Space>
);
}
}
export default IceUpload;
+55
View File
@@ -0,0 +1,55 @@
import React from 'react';
import {Skeleton, Tag} from 'antd';
class TagList extends React.Component {
state = {
tags: [],
loading: true,
}
constructor(props) {
super(props);
}
componentDidMount() {
this.fetch();
}
fetch() {
this.setState({
loading: true,
});
this.props.apiClient
.get(this.props.url)
.then((response) => {
const tags = response.data.data.map(this.props.extractTag);
this.setState({
tags: tags,
loading: false,
});
});
}
render() {
return (
<div style={{
display: 'inline-block',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
width: '100%',
}}>
{this.state.loading &&
<Skeleton active={true}/>
}
{!this.state.loading && this.state.tags.map((tag, index) =>
this.props.render ? this.props.render(tag) : <div key={`p${index}`}><Tag color={this.props.color} key={index} style={{margin: '10px'}}>{tag}</Tag><br/></div>
)}
</div>
);
}
}
export default TagList;
+187
View File
@@ -0,0 +1,187 @@
import React from 'react';
import {
Timeline, Drawer, Empty, Button, Space, Typography, Popover,
} from 'antd';
import {
ClockCircleOutlined,
PlusCircleOutlined,
InfoCircleOutlined,
PauseCircleOutlined,
MedicineBoxOutlined,
} from '@ant-design/icons';
const { Paragraph } = Typography;
class TaskList extends React.Component {
state = {
tasks: [],
showAll: false,
}
constructor(props) {
super(props);
this.state.tasks = this.props.tasks.map(item => false);
}
render() {
return this.createTaskList(4);
}
createTaskList(maxNumberOfTasks) {
const tasks = this.props.tasks.slice(0, maxNumberOfTasks);
return (
<>
<Space direction="vertical" size="large" style={{ width: '100%' }}>
{this.props.tasks && this.props.tasks.length > 0
&& (
<Space direction="vertical" style={{ width: '100%' }}>
<Timeline style={{ width: '100%' }}>
{tasks.map(
(task, index) => (
this.createTask(task, index)
),
)}
</Timeline>
{this.props.tasks.length > maxNumberOfTasks &&
<Button type="primary" onClick={() => this.showAllTasks()}>
View All
{' '}
{this.props.tasks.length}
{' '}
Tasks
</Button>
}
</Space>
)}
{this.props.tasks && this.props.tasks.length === 0
&& <Empty description="You're all caught up!" />}
</Space>
<Drawer
title="Task List"
width={470}
onClose={() => this.hideAllTasks()}
visible={this.state.showAll}
bodyStyle={{ paddingBottom: 80 }}
zIndex={1200}
maskClosable={false}
>
<Timeline style={{ width: '100%' }}>
{this.props.tasks.map(
(task, index) => (
this.createTask(task, index)
),
)}
</Timeline>
</Drawer>
</>
);
}
visitLink(link) {
setTimeout(() => {window.open(link);}, 100);
}
handleTaskHover(index) {
this.setState({tasks : this.props.tasks.map((item,i) => index === i)})
}
createTask(task, index) {
if (task.priority === 100) {
return (
<Timeline.Item onMouseEnter={() => this.handleTaskHover(index)}
dot={<ClockCircleOutlined style={{ fontSize: '16px' }} />} color="red" >
{this.getText(task)}
{task.link && this.state.tasks[index]
&& (
<Button type="link" onClick={() => this.visitLink(task.link)}>
<MedicineBoxOutlined style={{ fontSize: '16px' }} />
{' '}
{task.action}
</Button>
)}
</Timeline.Item>
);
} if (task.priority === 50) {
return (
<Timeline.Item onMouseEnter={() => this.handleTaskHover(index)}
dot={<InfoCircleOutlined style={{ fontSize: '16px' }} />} color="blue">
{this.getText(task)}
{task.link && this.state.tasks[index]
&& (
<Button type="link" onClick={() => this.visitLink(task.link)}>
<MedicineBoxOutlined style={{ fontSize: '16px' }} />
{' '}
{task.action}
</Button>
)}
</Timeline.Item>
);
} if (task.priority === 20) {
return (
<Timeline.Item onMouseEnter={() => this.handleTaskHover(index)}
dot={<PlusCircleOutlined style={{ fontSize: '16px' }} />} color="blue">
{this.getText(task)}
{task.link && this.state.tasks[index]
&& (
<Button type="link" onClick={() => this.visitLink(task.link)}>
<MedicineBoxOutlined style={{ fontSize: '16px' }} />
{' '}
{task.action}
</Button>
)}
</Timeline.Item>
);
} if (task.priority === 10) {
return (
<Timeline.Item onMouseEnter={() => this.handleTaskHover(index)}
dot={<PauseCircleOutlined style={{ fontSize: '16px' }} />} color="green">
{this.getText(task)}
{task.link && this.state.tasks[index]
&& (
<Button type="link" onClick={() => this.visitLink(task.link)}>
<MedicineBoxOutlined style={{ fontSize: '16px' }} />
{' '}
{task.action}
</Button>
)}
</Timeline.Item>
);
}
}
getText(task) {
if (!task.details) {
return (<Paragraph
ellipsis={{
rows: 1,
expandable: true,
}}
>
{task.text}
</Paragraph>);
}
return (
<Popover content={task.details}>
<Paragraph
ellipsis={{
rows: 1,
expandable: true,
}}
>
{task.text}
</Paragraph>
</Popover>
);
}
showAllTasks() {
this.setState({showAll: true});
}
hideAllTasks() {
this.setState({showAll: false});
}
}
export default TaskList;
+32664
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+35 -2
View File
@@ -630,7 +630,7 @@ table.dataTable{
}
.navbar, .right-side{
box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);
/*box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);*/
border: none;
}
@@ -678,7 +678,7 @@ table.dataTable{
}
.logoResponsive{
background: #3c8dbc !important;
background: #141414 !important;
text-align: left !important;
width:50% !important;
}
@@ -909,4 +909,37 @@ table.dataTable{
}
}
.wrap {
white-space: pre-wrap; /* CSS3 */
white-space: -moz-pre-wrap; /* Firefox */
white-space: -pre-wrap; /* Opera <7 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* IE */
}
.ant-table table {
font-size: 13px;
}
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th {
padding: 8px 16px !important;
}
.ant-table-column-sorters {
padding: 6px 0px !important;
}
.content {
padding: 70px 15px;
background: #fff;
}
.skin-blue .navbar {
background-color: #141414;
}
.table-row-light {
background-color: #ffffff;
}
.table-row-dark {
background-color: #fbfbfb;
}
+30986
View File
File diff suppressed because it is too large Load Diff
+3108 -2241
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
File diff suppressed because one or more lines are too long
+18
View File
File diff suppressed because one or more lines are too long
+21
View File
File diff suppressed because one or more lines are too long
+7
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
File diff suppressed because one or more lines are too long
+62 -52
View File
@@ -19430,16 +19430,16 @@ unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b
* Licensed MIT © Zeno Rocha
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var matches = require('matches-selector')
module.exports = function (element, selector, checkYoSelf) {
var parent = checkYoSelf ? element : element.parentNode
while (parent && parent !== document) {
if (matches(parent, selector)) return parent;
parent = parent.parentNode
}
}
var matches = require('matches-selector')
module.exports = function (element, selector, checkYoSelf) {
var parent = checkYoSelf ? element : element.parentNode
while (parent && parent !== document) {
if (matches(parent, selector)) return parent;
parent = parent.parentNode
}
}
},{"matches-selector":5}],2:[function(require,module,exports){
var closest = require('closest');
@@ -19636,45 +19636,45 @@ function listenSelector(selector, type, callback) {
module.exports = listen;
},{"./is":3,"delegate":2}],5:[function(require,module,exports){
/**
* Element prototype.
*/
var proto = Element.prototype;
/**
* Vendor function.
*/
var vendor = proto.matchesSelector
|| proto.webkitMatchesSelector
|| proto.mozMatchesSelector
|| proto.msMatchesSelector
|| proto.oMatchesSelector;
/**
* Expose `match()`.
*/
module.exports = match;
/**
* Match `el` to `selector`.
*
* @param {Element} el
* @param {String} selector
* @return {Boolean}
* @api public
*/
function match(el, selector) {
if (vendor) return vendor.call(el, selector);
var nodes = el.parentNode.querySelectorAll(selector);
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i] == el) return true;
}
return false;
/**
* Element prototype.
*/
var proto = Element.prototype;
/**
* Vendor function.
*/
var vendor = proto.matchesSelector
|| proto.webkitMatchesSelector
|| proto.mozMatchesSelector
|| proto.msMatchesSelector
|| proto.oMatchesSelector;
/**
* Expose `match()`.
*/
module.exports = match;
/**
* Match `el` to `selector`.
*
* @param {Element} el
* @param {String} selector
* @return {Boolean}
* @api public
*/
function match(el, selector) {
if (vendor) return vendor.call(el, selector);
var nodes = el.parentNode.querySelectorAll(selector);
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i] == el) return true;
}
return false;
}
},{}],6:[function(require,module,exports){
function select(element) {
@@ -34715,6 +34715,8 @@ function closeUploadDialog(success, error, data) {
$(`#${uploadId}`).show();
$(`#${uploadId}_download`).show();
$(`#${uploadId}_remove`).show();
} else if (uploadAttr === 'function') {
modJs.callFunction(uploadResultAttr);
}
} else {
// popupUpload.close();
@@ -34830,9 +34832,17 @@ function updateLanguage(language) {
}, 'json');
}
$(document).ready(function() {
function switchTab(name, filter) {
if (filter) {
modJsList[name].setFilter(filter);
}
$('html,body').scrollTop(0);
$(`#${name}`).trigger('click');
}
$(document).ready(() => {
$('.treeview-menu a').hover(
function () { $(this).find('.fa').addClass('faa-horizontal'); $(this).find('.fa').addClass('animated') },
function () { $(this).find('.fa').removeClass('faa-horizontal'); $(this).find('.fa').removeClass('animated');},
function () { $(this).find('.fa').addClass('faa-horizontal'); $(this).find('.fa').addClass('animated'); },
function () { $(this).find('.fa').removeClass('faa-horizontal'); $(this).find('.fa').removeClass('animated'); },
);
});
+27
View File
File diff suppressed because one or more lines are too long
+7
View File
File diff suppressed because one or more lines are too long
+54
View File
File diff suppressed because one or more lines are too long
+3
View File
File diff suppressed because one or more lines are too long
+79
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Regular → Executable
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 476 KiB

+49 -49
View File
@@ -5,16 +5,16 @@
* Licensed MIT © Zeno Rocha
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var matches = require('matches-selector')
module.exports = function (element, selector, checkYoSelf) {
var parent = checkYoSelf ? element : element.parentNode
while (parent && parent !== document) {
if (matches(parent, selector)) return parent;
parent = parent.parentNode
}
}
var matches = require('matches-selector')
module.exports = function (element, selector, checkYoSelf) {
var parent = checkYoSelf ? element : element.parentNode
while (parent && parent !== document) {
if (matches(parent, selector)) return parent;
parent = parent.parentNode
}
}
},{"matches-selector":5}],2:[function(require,module,exports){
var closest = require('closest');
@@ -211,45 +211,45 @@ function listenSelector(selector, type, callback) {
module.exports = listen;
},{"./is":3,"delegate":2}],5:[function(require,module,exports){
/**
* Element prototype.
*/
var proto = Element.prototype;
/**
* Vendor function.
*/
var vendor = proto.matchesSelector
|| proto.webkitMatchesSelector
|| proto.mozMatchesSelector
|| proto.msMatchesSelector
|| proto.oMatchesSelector;
/**
* Expose `match()`.
*/
module.exports = match;
/**
* Match `el` to `selector`.
*
* @param {Element} el
* @param {String} selector
* @return {Boolean}
* @api public
*/
function match(el, selector) {
if (vendor) return vendor.call(el, selector);
var nodes = el.parentNode.querySelectorAll(selector);
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i] == el) return true;
}
return false;
/**
* Element prototype.
*/
var proto = Element.prototype;
/**
* Vendor function.
*/
var vendor = proto.matchesSelector
|| proto.webkitMatchesSelector
|| proto.mozMatchesSelector
|| proto.msMatchesSelector
|| proto.oMatchesSelector;
/**
* Expose `match()`.
*/
module.exports = match;
/**
* Match `el` to `selector`.
*
* @param {Element} el
* @param {String} selector
* @return {Boolean}
* @api public
*/
function match(el, selector) {
if (vendor) return vendor.call(el, selector);
var nodes = el.parentNode.querySelectorAll(selector);
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i] == el) return true;
}
return false;
}
},{}],6:[function(require,module,exports){
function select(element) {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-9
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More