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

@@ -0,0 +1,284 @@
import React, {Component} from 'react';
import {
Col,
Card,
Badge,
Avatar,
Input,
Row,
Descriptions,
Typography,
Table,
Space,
Button,
Tag,
message,
Tabs,
Spin,
Skeleton
} 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);
}
setLoading(value) {
this.setState({ loading: value });
}
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.state.loading &&
<Tag icon={<SyncOutlined spin/>} color="processing">
{this.props.adapter.gt('Edit')}
</Tag>
}
{!this.state.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() {
if (this.state.loading || !this.props.element) {
return <div style={{padding: '20px'}}><Skeleton active /></div>
}
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

@@ -6,12 +6,15 @@
/* eslint-disable camelcase,no-underscore-dangle */
/* global d3 */
import React from 'react';
import ReactDOM from 'react-dom';
import QRCode from 'qrcode';
import AdapterBase from '../../../api/AdapterBase';
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
import EmployeeProfile from './components/EmployeeProfile';
class EmployeeAdapter extends AdapterBase {
class EmployeeAdapter extends ReactModalAdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.fieldNameMap = {};
@@ -62,6 +65,48 @@ class EmployeeAdapter extends AdapterBase {
];
}
initTable() {
this.initProfile();
}
initProfile(employee) {
const tableDom = document.getElementById(`${this.tab}`);
this.tableContainer = React.createRef();
ReactDOM.render(
<EmployeeProfile
ref={this.tableContainer}
adapter={this}
element={employee}
/>,
tableDom,
);
this.tableContainer.current.setLoading(!employee);
}
get() {
this.initTable();
this.masterDataReader.updateAllMasterData()
.then(() => {
this.viewElement();
});
this.trackEvent('get', this.tab, this.table);
}
edit(id) {
this.setTableLoading(true);
this.currentId = id;
this.getElement(id, []);
}
getFormOptions() {
return {
width: 1024,
twoColumnLayout: false,
};
}
getFormFields() {
const newFields = [];
let employee_id; let ssn_num; let employment_status; let job_title; let pay_grade; let joined_date; let department; let work_email; let
@@ -133,7 +178,7 @@ class EmployeeAdapter extends AdapterBase {
['last_name', { label: 'Last Name', type: 'text', validation: '' }],
['nationality', { label: 'Nationality', type: 'select2', 'remote-source': ['Nationality', 'id', 'name'] }],
['birthday', { label: 'Date of Birth', type: 'date', validation: '' }],
['gender', { label: 'Gender', type: 'select', source: [['Male', 'Male'], ['Female', 'Female'], ['Divers', 'Divers']] }],
['gender', { label: 'Gender', type: 'select', source: [['Male', 'Male'], ['Female', 'Female'], ['Other', 'Other']] }],
['marital_status', { label: 'Marital Status', type: 'select', source: [['Married', 'Married'], ['Single', 'Single'], ['Divorced', 'Divorced'], ['Widowed', 'Widowed'], ['Other', 'Other']] }],
ssn_num,
['nic_num', { label: 'NIC', type: 'text', validation: 'none' }],
@@ -179,14 +224,96 @@ class EmployeeAdapter extends AdapterBase {
return newFields;
}
getMappedFields() {
const fields = this.getFormFields();
const steps = [
{
title: this.gt('Personal'),
description: this.gt('Personal Information'),
fields: [
'id',
'employee_id',
'first_name',
'middle_name',
'last_name',
'nationality',
'birthday',
'gender',
'marital_status',
'ethnicity',
],
},
{
title: this.gt('Identification'),
description: this.gt('Personal Information'),
fields: [
'immigration_status',
'ssn_num',
'nic_num',
'other_id',
'driving_license',
],
},
{
title: this.gt('Work'),
description: this.gt('Work related details'),
fields: [
'employment_status',
'department',
'job_title',
'pay_grade',
'joined_date',
'confirmation_date',
'termination_date',
'work_station_id',
],
},
{
title: this.gt('Contact'),
description: this.gt('Contact details'),
fields: [
'address1', 'address2',
'city', 'country',
'province', 'postal_code',
'home_phone', 'mobile_phone',
'work_phone', 'work_email',
'private_email',
],
},
];
if (this.customFields.length > 0) {
steps.push({
title: this.gt('Other'),
description: this.gt('Additional details'),
fields: this.customFields.map((item) => item[0]),
});
}
return this.addActualFields(steps, fields);
}
addActualFields(steps, fields) {
return steps.map((item) => {
item.fields = item.fields.reduce((acc, fieldName) => {
const field = fields.find(([name]) => name === fieldName);
if (field) {
acc.push(field);
}
return acc;
}, []);
return item;
});
}
getSourceMapping() {
const k = this.sourceMapping;
k.supervisor = ['Employee', 'id', 'first_name+last_name'];
return k;
}
get() {
viewElement(id) {
const sourceMappingJson = JSON.stringify(this.getSourceMapping());
const req = { map: sourceMappingJson };
@@ -218,6 +345,13 @@ class EmployeeAdapter extends AdapterBase {
}
modEmployeeGetSuccessCallBack(data) {
const currentEmpId = data[1];
const userEmpId = data[2];
const [element] = data;
this.initProfile(element);
}
modEmployeeGetSuccessCallBack1(data) {
const fields = this.getFormFields();
const currentEmpId = data[1];
const userEmpId = data[2];
@@ -694,10 +828,6 @@ class ApiAccessAdapter extends AdapterBase {
];
}
setApiUrl(apiUrl) {
this.apiUrl = apiUrl; '';
}
setToken(token) {
this.token = token;
}