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

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' }],

View File

@@ -0,0 +1,4 @@
import { AttendanceGraphAdapter, TimeUtilizationGraphAdapter } from './lib';
window.AttendanceGraphAdapter = AttendanceGraphAdapter;
window.TimeUtilizationGraphAdapter = TimeUtilizationGraphAdapter;

254
web/admin/src/charts/lib.js Normal file
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 };

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;

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;

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 };

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) {

View File

@@ -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;

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

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;

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',
},
];
}
}

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'] }],
];
}

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) {

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;

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 = {

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',
}],
];
}
}