Browse Source

Merge branch 'staging' of https://git.oslog.id/ibnu/generic-ospro-frontend into dev-wahyun

pull/1/head
wahyuun 10 months ago
parent
commit
48d38ffe46
  1. 1
      package.json
  2. 270
      src/components/wj/App.jsx
  3. 11
      src/components/wj/components/BarChart.jsx
  4. 3
      src/components/wj/components/Blank.jsx
  5. 9
      src/components/wj/components/BubbleChart.jsx
  6. 22
      src/components/wj/components/BulletGraph.jsx
  7. 11
      src/components/wj/components/ColumnChart.jsx
  8. 12
      src/components/wj/components/Grid.jsx
  9. 10
      src/components/wj/components/LineChart.jsx
  10. 26
      src/components/wj/components/LinearGauge.jsx
  11. 12
      src/components/wj/components/RadialGauge.jsx
  12. 14
      src/components/wj/components/Tile.jsx
  13. 394
      src/components/wj/index.css
  14. 10
      src/components/wj/index.jsx
  15. 2
      src/components/wj/license.js
  16. 370
      src/components/wj/utils/DragDropTouch.js
  17. 61
      src/const/ApiConst.js
  18. 40
      src/const/CustomFunc.js
  19. 7
      src/const/en.json
  20. 7
      src/const/id.json
  21. 2
      src/containers/DefaultLayout/DefaultLayout.js
  22. 15
      src/routes.js
  23. 296
      src/views/Dashboard/DashboardDyna.js
  24. 237
      src/views/Master/MasterRoles/DialogMenuRoles.js
  25. 42
      src/views/Master/MasterRoles/index.js
  26. 17
      src/views/Master/MenuCompany/index.js
  27. 28
      src/views/Master/ProjectPhase/index.js
  28. 23
      src/views/Master/RoleProject/index.js
  29. 30
      src/views/SimproV2/ChecklistK3/index.js
  30. 41
      src/views/SimproV2/CreatedProyek/index.js
  31. 0
      src/views/SimproV2/DemoRequest/DialogForm.js
  32. 4
      src/views/SimproV2/DemoRequest/index.js
  33. 37
      src/views/SimproV2/Divisi/index.js
  34. 22
      src/views/SimproV2/ProjectType/index.js
  35. 190
      src/views/SimproV2/ReferralCode/DialogForm.js
  36. 372
      src/views/SimproV2/ReferralCode/index.js
  37. 36
      src/views/SimproV2/ResourceWorker/index.js
  38. 201
      src/views/SimproV2/SalesContact/DialogForm.js
  39. 374
      src/views/SimproV2/SalesContact/index.js
  40. 29
      src/views/SimproV2/Satuan/index.js

1
package.json

@ -21,6 +21,7 @@
"@dabeng/react-orgchart": "^1.0.0",
"@develoka/angka-rupiah-js": "^1.0.3",
"@faker-js/faker": "^7.3.0",
"@grapecity/wijmo.react.all": "5.20213",
"@iconify/icons-ant-design": "^1.0.6",
"@iconify/icons-fa-solid": "^1.0.6",
"@iconify/icons-fe": "^1.0.3",

270
src/components/wj/App.jsx

@ -0,0 +1,270 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcCore from '@grapecity/wijmo';
import { Grid } from './components/Grid';
import { RadialGauge } from './components/RadialGauge';
import { LinearGauge } from './components/LinearGauge';
import { BarChart } from './components/BarChart';
import { ColumnChart } from './components/ColumnChart';
import { LineChart } from './components/LineChart';
import { BubbleChart } from './components/BubbleChart';
import { BulletGraph } from './components/BulletGraph';
import { Blank } from './components/Blank';
import { Tile } from './components/Tile';
// Wijmo and Material Design Lite
import '@grapecity/wijmo.styles/themes/material/wijmo.theme.material.indigo-amber.css';
// Styles
import './index.css';
import './license'
export default class App extends React.Component {
constructor() {
super(...arguments);
// Color palette
this.palette = ['#8e99f3', '#ffca28', '#5c6bc0', '#bbdefb'];
// Icons assets
this.icons = {
grid: [
<path d="M57,5H7A2,2,0,0,0,5,7v7H59V7A2,2,0,0,0,57,5Zm1,19V23H46V15H45v8H33V15H32v8H19V15H18v8H6v1H18v7H6v1H18v8H6v1H18v8H6v1H18v8h1V50H32v8h1V50H45v8h1V50H58V49H46V41H58V40H46V32H58V31H46V24ZM19,24H32v7H19Zm0,8H32v8H19Zm0,17V41H32v8Zm26,0H33V41H45Zm0-9H33V32H45Zm0-9H33V24H45Z" fill={this.palette[3]}/>,
<path d="M57,5H7A2,2,0,0,0,5,7V57a2,2,0,0,0,2,2H57a2,2,0,0,0,2-2V7A2,2,0,0,0,57,5ZM7,6H57a1,1,0,0,1,1,1v7H6V7A1,1,0,0,1,7,6ZM57,58H7a1,1,0,0,1-1-1V15H58V57A1,1,0,0,1,57,58Z" fill={this.palette[0]}/>,
],
barChart: [
<rect x="12" y="34" width="40" height="7" fill={this.palette[1]}/>,
<rect x="12" y="46" width="33" height="7" fill={this.palette[2]}/>,
<rect x="12" y="11" width="32" height="7" fill={this.palette[3]}/>,
<path d="M36,22v7H12V22ZM10,14H8v1h2Zm0,11H8v1h2Zm0,12H8v1h2Zm0,12H8v1h2Zm49,9H7a.94.94,0,0,1-1-1V5H5V57a2,2,0,0,0,2,2H59Z" fill={this.palette[0]}/>,
],
columnChart: [
<rect x="23" y="12.02" width="7" height="40" fill={this.palette[1]}/>,
<rect x="11" y="19.02" width="7" height="33" fill={this.palette[2]}/>,
<rect x="46" y="20.02" width="7" height="32" fill={this.palette[3]}/>,
<path d="M41,52H34V26h7Zm8,2v2h1V54ZM37,54v2h1V54ZM26,54v2h1V54ZM14,54v2h1V54ZM5,5V57a2,2,0,0,0,2,2H58V58H7a1,1,0,0,1-1-1V5Z" fill={this.palette[0]}/>,
],
bubbleChart: [
<path d="M59,58H7a.94.94,0,0,1-1-1V5H5V57a2,2,0,0,0,2,2H59Z" fill={this.palette[0]}/>,
<path d="M36,23a2,2,0,1,1,2,2A2,2,0,0,1,36,23ZM13.63,29.07a2,2,0,1,0-2-2A2,2,0,0,0,13.63,29.07Zm9,12a2,2,0,1,0-2-2A2,2,0,0,0,22.63,41.07Zm24-5a2,2,0,1,0-2-2A2,2,0,0,0,46.63,36.07Zm-2.5,17a1.5,1.5,0,1,0-1.5-1.5A1.5,1.5,0,0,0,44.13,53.07Z" fill={this.palette[2]}/>,
<path d="M19,12a4,4,0,1,1,4,4A4,4,0,0,1,19,12Zm6.63,16.07a3,3,0,1,0-3-3A3,3,0,0,0,25.63,28.07Zm11.5,8a3.5,3.5,0,1,0-3.5-3.5A3.5,3.5,0,0,0,37.13,36.07Zm-1,10a2.5,2.5,0,1,0-2.5-2.5A2.5,2.5,0,0,0,36.13,46.07Zm14,0a2.5,2.5,0,1,0-2.5-2.5A2.5,2.5,0,0,0,50.13,46.07Z" fill={this.palette[3]}/>,
],
lineChart: [
<polygon points="51 20.41 49.59 19 32.5 36.97 27.5 31.97 11 48.48 12.5 49.98 27.5 34.97 32.5 39.97 51 20.41" fill={this.palette[3]}/>,
<path d="M6,5V57a1,1,0,0,0,1,1H58v1H7a2,2,0,0,1-2-2V5Z" fill={this.palette[0]}/>,
<polygon points="34.92 30.42 27.5 23 11 39.51 12.5 41.01 27.5 26 33.42 31.92 34.92 30.42" fill={this.palette[2]}/>,
<polygon points="40.58 36.08 39.08 37.58 45.97 44.47 47.38 42.88 40.58 36.08" fill={this.palette[2]}/>,
],
radialGauge: [
<circle cx="32" cy="32" r="4" fill={this.palette[1]}/>,
<path d="M32,6A26,26,0,1,1,6,32,26,26,0,0,1,32,6m0-1A27,27,0,1,0,59,32,27,27,0,0,0,32,5ZM43.37,20.63a1.49,1.49,0,0,0-2.12,0l-6.84,6.84a6.51,6.51,0,0,1,2.12,2.12l6.84-6.84A1.49,1.49,0,0,0,43.37,20.63Z" fill={this.palette[0]}/>,
<path d="M34,11a2,2,0,1,1-2-2A2,2,0,0,1,34,11ZM17,15a2,2,0,1,0,2,2A2,2,0,0,0,17,15Zm30,0a2,2,0,1,0,2,2A2,2,0,0,0,47,15ZM11,28a2,2,0,1,0,2,2A2,2,0,0,0,11,28Zm42,.91a2,2,0,1,0,2,2A2,2,0,0,0,53,28.91ZM32,40.76A25.87,25.87,0,0,0,14.09,48a23.95,23.95,0,0,0,35.83,0A25.88,25.88,0,0,0,32,40.76Z" fill={this.palette[3]}/>,
],
linearGauge: [
<path d="M29.5,19A4.5,4.5,0,1,1,34,23.5,4.49,4.49,0,0,1,29.5,19Zm-1.15-2H11a2,2,0,0,0-1.9,2.65,2,2,0,0,0,2,1.35H28.21A5.72,5.72,0,0,1,28,19.5c0-.08,0-.15,0-.23s0-.18,0-.27A6,6,0,0,1,28.35,17ZM54.9,18.35a2,2,0,0,0-2-1.35H39.65a5.89,5.89,0,0,1,0,4H53A2,2,0,0,0,54.9,18.35Z" fill={this.palette[2]}/>,
<path d="M53,36H29.05a6,6,0,0,0,.29-1.85,6.13,6.13,0,0,0-.4-2.15h24a2,2,0,0,1,2,1.35A2,2,0,0,1,53,36ZM17.74,32H11a2,2,0,0,0-1.9,2.65,2,2,0,0,0,2,1.35h6.55a6.28,6.28,0,0,1-.29-1.85A6.13,6.13,0,0,1,17.74,32Zm5.6,6.65a4.5,4.5,0,1,0-4.5-4.5A4.5,4.5,0,0,0,23.34,38.65Z" fill={this.palette[3]}/>,
<path d="M38.34,49.15A6.28,6.28,0,0,0,38.63,51H11.08a2,2,0,0,1-2-1.35A2,2,0,0,1,11,47H38.74A6.13,6.13,0,0,0,38.34,49.15Zm16.56-.8a2,2,0,0,0-2-1.35h-3a6.13,6.13,0,0,1,.4,2.15A6,6,0,0,1,50.05,51h3A2,2,0,0,0,54.9,48.35Zm-10.56,5.3a4.5,4.5,0,1,0-4.5-4.5A4.5,4.5,0,0,0,44.34,53.65Z" fill={this.palette[1]}/>,
],
bulletGraph: [
<rect x="41" y="17" width="14" height="11" fill={this.palette[2]}/>,
<rect x="40.89" y="33.96" width="14" height="11" fill={this.palette[2]}/>,
<polygon points="25 17 25 19 34 19 34 26 25 26 25 28 39 28 39 17 25 17" fill={this.palette[1]}/>,
<polygon points="25 36 29 36 29 43 25 43 25 45 39 45 39 34 25 34 25 36" fill={this.palette[1]}/>,
<rect x="9" y="26" width="14" height="2" fill={this.palette[3]}/>,
<rect x="9" y="17" width="14" height="2" fill={this.palette[3]}/>,
<rect x="9" y="34" width="14" height="2" fill={this.palette[3]}/>,
<rect x="9" y="43" width="14" height="2" fill={this.palette[3]}/>,
<path d="M49,53v2h1V53ZM37,53v2h1V53ZM26,53v2h1V53ZM14,53v2h1V53ZM31,24H9V21H31ZM26,41H9V38H26ZM60,58H6V57H60Z" fill={this.palette[0]}/>,
],
blank: [
<path d="M40,5V6H33V5ZM24,6h7V5H24ZM15,6h7V5H15Zm7,53V58H15v1Zm9-1H24v1h7ZM6,49V42H5v7ZM5,22H6V15H5ZM51,6h6a1,1,0,0,1,1,1v6h1V7a2,2,0,0,0-2-2H51ZM6,31V24H5v7Zm0,9V33H5v7ZM58,15v7h1V15ZM6,13V7A1,1,0,0,1,7,6h6V5H7A2,2,0,0,0,5,7v6ZM58,51v6a1,1,0,0,1-1,1H51v1h6a2,2,0,0,0,2-2V51ZM13,58H7a1,1,0,0,1-1-1V51H5v6a2,2,0,0,0,2,2h6ZM58,24v7h1V24Zm1,18H58v7h1ZM49,58H42v1h7ZM42,5V6h7V5ZM40,58H33v1h7ZM58,33v7h1V33Z" fill={this.palette[0]}/>,
],
};
// Tile names and types
this.tileCatalog = [
{ name: 'Grid', tile: Grid, icon: this.icons.grid },
{ name: 'Radial Gauge', tile: RadialGauge, icon: this.icons.radialGauge },
{ name: 'Linear Gauge', tile: LinearGauge, icon: this.icons.linearGauge },
{ name: 'Bar Chart', tile: BarChart, icon: this.icons.barChart },
{ name: 'Column Chart', tile: ColumnChart, icon: this.icons.columnChart },
{ name: 'Line Chart', tile: LineChart, icon: this.icons.lineChart },
{ name: 'Bubble Chart', tile: BubbleChart, icon: this.icons.bubbleChart },
{ name: 'Bullet Graph', tile: BulletGraph, icon: this.icons.bulletGraph },
{ name: 'Blank', tile: Blank, icon: this.icons.blank },
];
this.key = 0;
this.state = {
isWideMenu: false,
tileCatalog: new wjcCore.CollectionView(this.tileCatalog),
tiles: this.getTiles(),
key: this.key,
data: this.getData(),
};
}
// tiles currently in use
getTiles() {
return [
{ name: this.tileCatalog[1].name, key: this.key += 1 },
{ name: this.tileCatalog[2].name, key: this.key += 1 },
{ name: this.tileCatalog[5].name, key: this.key += 1 },
{ name: this.tileCatalog[7].name, key: this.key += 1 },
{ name: this.tileCatalog[0].name, key: this.key += 1 },
];
}
// generate some data to show in the tiles
getData() {
let data = [];
const today = new Date();
for (let i = 0; i < 12; i++) {
const sales = 100 + Math.random() * 800 + i * 50;
const expenses = 50 + Math.random() * 300 + i * 5;
data.push({
id: i,
date: wjcCore.DateTime.addMonths(today, 12 - i),
sales: sales,
expenses: expenses,
profit: sales - expenses,
});
}
return data;
}
// gets a tile content by name
getTileContent(name) {
const { data, tileCatalog } = this.state;
const arr = tileCatalog.items;
for (let i = 0; i < arr.length; i++) {
if (arr[i].name == name) {
return React.createElement(arr[i].tile, {
data: new wjcCore.CollectionView(data),
palette: this.palette
});
}
}
throw '*** tile not found: ' + name;
}
// adds a tile to the dashboard
addTile(name) {
const { tiles, key: stateKey } = this.state;
const key = stateKey + 1;
this.setState({ tiles: [{ name, key }, ...tiles], key });
}
// removes a tile from the dashboard
removeTile(tileIndex) {
const tiles = this.state.tiles.filter((item, index) => index != tileIndex);
this.setState({ tiles: tiles });
}
// initialize component after it has been mounted
componentDidMount() {
// enable tile drag/drop
const panel = document.querySelector('.dashboard');
this.enableItemReorder(panel);
}
// allow users to re-order elements within a panel element
// we work with the DOM elements and update the state when done.
enableItemReorder(panel) {
let dragSource = null;
let dropTarget = null;
// add drag/drop event listeners
panel.addEventListener('dragstart', (e) => {
const target = wjcCore.closest(e.target, '.tile');
if (target && target.parentElement == panel) {
dragSource = target;
wjcCore.addClass(dragSource, 'drag-source');
const dt = e.dataTransfer;
dt.effectAllowed = 'move';
dt.setData('text', dragSource.innerHTML);
}
});
panel.addEventListener('dragover', (e) => {
if (dragSource) {
let tile = wjcCore.closest(e.target, '.tile');
if (tile == dragSource) {
tile = null;
}
if (dragSource && tile && tile != dragSource) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
if (dropTarget != tile) {
wjcCore.removeClass(dropTarget, 'drag-over');
dropTarget = tile;
wjcCore.addClass(dropTarget, 'drag-over');
}
}
});
panel.addEventListener('drop', (e) => {
if (dragSource && dropTarget) {
// finish drag/drop
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
// re-order HTML elements (optional here, we're updating the state later)
const srcIndex = getIndex(dragSource);
const dstIndex = getIndex(dropTarget);
const refChild = srcIndex > dstIndex ? dropTarget : dropTarget.nextElementSibling;
dragSource.parentElement.insertBefore(dragSource, refChild);
// focus and view on the tile that was dragged
dragSource.focus();
// update state
let tiles = this.state.tiles.slice();
tiles.splice(srcIndex, 1);
tiles.splice(dstIndex, 0, this.state.tiles[srcIndex]);
this.setState({ tiles: tiles });
}
});
panel.addEventListener('dragend', () => {
wjcCore.removeClass(dragSource, 'drag-source');
wjcCore.removeClass(dropTarget, 'drag-over');
dragSource = dropTarget = null;
});
function getIndex(e) {
const p = e.parentElement;
for (let i = 0; i < p.children.length; i++) {
if (p.children[i] == e)
return i;
}
return -1;
}
}
// render the dashboard
render() {
const { tiles, isWideMenu } = this.state;
// animated toggle menu
const renderMenuToggle = (<div className={'menu-toggle' + (this.state.isWideMenu ? ' menu--open' : '')} onClick={() => this.setState({ isWideMenu: !isWideMenu })}>
<svg width="30" height="20" viewBox="0 0 30 20" fill={this.palette[2]}>
<rect x="10" y="5" width="11" height="1"/>
<rect x="10" y="15" width="11" height="1"/>
<polygon points="29.48 10.27 24.23 5.03 23.52 5.73 27.79 10 10 10 10 11 27.79 11 23.52 15.27 24.23 15.97 29.48 10.73 29.7 10.5 29.48 10.27"/>
</svg>
</div>);
// menu items
const renderMenuItems = (<React.Fragment>
{this.tileCatalog.map((item) => (<div key={`Menu ${item.name}`} className="menu-item" title={item.name} onClick={() => this.addTile(item.name)}>
<svg width="64" height="64" viewBox="0 0 64 64">
{item.icon.map((entity, key) => (<React.Fragment key={`Menu Item ${key}`}>{entity}</React.Fragment>))}
</svg>
<div className="menu-item-name">{item.name}</div>
</div>))}
</React.Fragment>);
// displayed when the dashboard is empty
const renderBlankTile = (<div className="blank">
<svg width="24" height="24" viewBox="0 0 24 24" fill={this.palette[0]}>
<path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M4,4V16H8.83L12,19.17L15.17,16H20V4H4M11,6H13V9H16V11H13V14H11V11H8V9H11V6Z"/>
</svg>
<div>Click on an item on the menu bar to add the new tile to the dashboard.</div>
</div>);
// list of tiles
const renderTiles = (<React.Fragment>
{tiles.map((item, index) => (<Tile header={item.name} content={this.getTileContent(item.name)} onRemove={this.removeTile.bind(this)} index={index} key={item.key}/>))}
</React.Fragment>);
const renderDashboard = tiles.length ? renderTiles : renderBlankTile;
return (<div className="container">
<div className={`menu ${isWideMenu ? 'menu--open' : ''}`}>
{renderMenuToggle}
{renderMenuItems}
</div>
<div className="hr"/>
<div className="content">
<div className="dashboard">{renderDashboard}</div>
</div>
</div>);
}
}

11
src/components/wj/components/BarChart.jsx

@ -0,0 +1,11 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcChart from '@grapecity/wijmo.chart';
import * as wjChart from '@grapecity/wijmo.react.chart';
export const BarChart = ({ data, palette }) => (<wjChart.FlexChart chartType={wjcChart.ChartType.Bar} itemsSource={data} palette={palette} bindingX="date">
<wjChart.FlexChartAxis wjProperty="axisX" format="MMM-yy"/>
<wjChart.FlexChartSeries name="Sales" binding="sales"/>
<wjChart.FlexChartSeries name="Expenses" binding="expenses"/>
<wjChart.FlexChartSeries name="Profit" binding="profit" chartType={wjcChart.ChartType.LineSymbols}/>
</wjChart.FlexChart>);

3
src/components/wj/components/Blank.jsx

@ -0,0 +1,3 @@
// React
import * as React from 'react';
export const Blank = () => <div className="blank-tile">This is an empty tile.</div>;

9
src/components/wj/components/BubbleChart.jsx

@ -0,0 +1,9 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcChart from '@grapecity/wijmo.chart';
import * as wjChart from '@grapecity/wijmo.react.chart';
export const BubbleChart = ({ data, palette }) => (<wjChart.FlexChart chartType={wjcChart.ChartType.Bubble} itemsSource={data} palette={palette} bindingX="date">
<wjChart.FlexChartAxis wjProperty="axisX" format="MMM yy"/>
<wjChart.FlexChartSeries name="Sales/Profit" binding="sales,profit"/>
</wjChart.FlexChart>);

22
src/components/wj/components/BulletGraph.jsx

@ -0,0 +1,22 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcCore from '@grapecity/wijmo';
import * as wjGauge from '@grapecity/wijmo.react.gauge';
export const BulletGraph = ({ data }) => (<div style={{ width: '100%', padding: '0 1rem', overflow: 'hidden' }}>
<table className="table">
<tbody>
{data.items.map((item, index) => (<tr key={index}>
<td>{wjcCore.Globalize.format(item.date, 'MMM yyyy')}</td>
<td>
<svg width="16" height="16" viewBox="0 0 24 24" fill="orange" style={{ display: item.profit <= 400 ? 'block' : 'none' }}>
<path d="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"/>
</svg>
</td>
<td>
<wjGauge.BulletGraph hasShadow={false} value={item.profit} min={0} bad={400} target={600} good={600} max={1000}/>
</td>
</tr>))}
</tbody>
</table>
</div>);

11
src/components/wj/components/ColumnChart.jsx

@ -0,0 +1,11 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcChart from '@grapecity/wijmo.chart';
import * as wjChart from '@grapecity/wijmo.react.chart';
export const ColumnChart = ({ data, palette }) => (<wjChart.FlexChart chartType={wjcChart.ChartType.Column} itemsSource={data} palette={palette} bindingX="date" axisX={{ format: 'MMM-yy' }}>
<wjChart.FlexChartAxis wjProperty="axisX" format="MMM yy"/>
<wjChart.FlexChartSeries name="Sales" binding="sales"/>
<wjChart.FlexChartSeries name="Expenses" binding="expenses"/>
<wjChart.FlexChartSeries name="Profit" binding="profit" chartType={wjcChart.ChartType.LineSymbols}/>
</wjChart.FlexChart>);

12
src/components/wj/components/Grid.jsx

@ -0,0 +1,12 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcGrid from '@grapecity/wijmo.grid';
import * as wjGrid from '@grapecity/wijmo.react.grid';
export const Grid = ({ data, palette }) => (<wjGrid.FlexGrid isReadOnly={true} headersVisibility={wjcGrid.HeadersVisibility.Column} selectionMode={wjcGrid.SelectionMode.ListBox} itemsSource={data}>
<wjGrid.FlexGridColumn header="ID" binding="id" width={50}/>
<wjGrid.FlexGridColumn header="Date" width="*" binding="date" format="MMM yyyy"/>
<wjGrid.FlexGridColumn header="Sales" binding="sales" format="c"/>
<wjGrid.FlexGridColumn header="Expenses" binding="expenses" format="c"/>
<wjGrid.FlexGridColumn header="Profit" binding="profit" format="c"/>
</wjGrid.FlexGrid>);

10
src/components/wj/components/LineChart.jsx

@ -0,0 +1,10 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcChart from '@grapecity/wijmo.chart';
import * as wjChart from '@grapecity/wijmo.react.chart';
export const LineChart = ({ data, palette }) => (<wjChart.FlexChart chartType={wjcChart.ChartType.Line} itemsSource={data} palette={palette} bindingX="date">
<wjChart.FlexChartAxis wjProperty="axisX" format="MMM yy"/>
<wjChart.FlexChartSeries name="Sales" binding="sales"/>
<wjChart.FlexChartSeries name="Profit" binding="profit" chartType={wjcChart.ChartType.LineSymbols}/>
</wjChart.FlexChart>);

26
src/components/wj/components/LinearGauge.jsx

@ -0,0 +1,26 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcCore from '@grapecity/wijmo';
import * as wjGauge from '@grapecity/wijmo.react.gauge';
export const LinearGauge = ({ data, palette }) => {
const lastItem = data.items[data.items.length - 1];
return (<div style={{ width: '100%' }}>
<div className="flex-row">
<h4>Sales: {wjcCore.Globalize.format(lastItem.sales, 'c')}</h4>
<wjGauge.LinearGauge min={0} max={1000} thickness={0.15} thumbSize={9} value={lastItem.sales} pointer={{ color: palette[0] }}/>
</div>
<div className="flex-row">
<h4>Expenses: {wjcCore.Globalize.format(lastItem.expenses, 'c')}</h4>
<wjGauge.LinearGauge min={0} max={1000} thickness={0.15} thumbSize={9} value={lastItem.expenses} pointer={{ color: palette[1] }}/>
</div>
<div className="flex-row">
<h4>Profit: {wjcCore.Globalize.format(lastItem.profit, 'c')}</h4>
<wjGauge.LinearGauge min={0} max={1000} thickness={0.15} thumbSize={9} value={lastItem.profit} pointer={{ color: palette[2] }}/>
</div>
<h3>KPIs for {wjcCore.Globalize.format(lastItem.date, 'MMMM yyyy')}</h3>
</div>);
};

12
src/components/wj/components/RadialGauge.jsx

@ -0,0 +1,12 @@
// React
import * as React from 'react';
// Wijmo
import * as wjcCore from '@grapecity/wijmo';
import * as wjGauge from '@grapecity/wijmo.react.gauge';
export const RadialGauge = ({ data, palette }) => {
const lastItem = data.items[data.items.length - 1];
return (<React.Fragment>
<wjGauge.RadialGauge min={0} max={1000} format="c0" value={lastItem.profit}/>
<h3>Profit for {wjcCore.Globalize.format(lastItem.date, 'MMMM yyyy')}</h3>
</React.Fragment>);
};

14
src/components/wj/components/Tile.jsx

@ -0,0 +1,14 @@
import * as React from 'react';
export const Tile = ({ header, content, index, onRemove }) => (<div className="tile" draggable={true}>
<div className="tile-container">
<div className="tile-header">{header}</div>
<div className="buttons">
<div className="button" title="Close Tile" onClick={() => onRemove(index)}>
<svg width="24" height="24" viewBox="0 0 24 24">
<path d="M12.71,12l4.64-4.65a.49.49,0,1,0-.7-.7L12,11.29,7.35,6.65a.49.49,0,0,0-.7.7L11.29,12,6.65,16.65a.48.48,0,0,0,0,.7.48.48,0,0,0,.7,0L12,12.71l4.65,4.64a.48.48,0,0,0,.7,0,.48.48,0,0,0,0-.7Z"/>
</svg>
</div>
</div>
</div>
<div className="tile-content">{content}</div>
</div>);

394
src/components/wj/index.css

@ -0,0 +1,394 @@
/* app */
*, *::before, *::after {
box-sizing: border-box;
}
/* customize the browser's scrollbar: */
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
border-radius: 0.25rem;
background: rgba(0, 0, 0, 0.1);
}
*::-webkit-scrollbar-thumb {
border-radius: 0.25rem;
background: rgba(0, 0, 0, 0.2);
}
*::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.4);
}
*::-webkit-scrollbar-thumb:active {
background: rgba(0, 0, 0, 0.9);
}
html,
body {
height: 100%;
}
body {
background-color: #f7faff;
font-size: 0.875rem;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', sans-serif;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
}
h3 {
font-size: 0.875rem;
font-weight: 500;
text-align: center;
}
h4 {
font-size: 0.875rem;
font-size: 0.75rem;
font-weight: 400;
min-width: 7rem;
}
.flex-row {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 1rem;
}
.button {
cursor: pointer;
}
#app {
flex: 1 1 auto;
overflow: hidden;
}
.container {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
max-width: 100%;
background: #f2f6fe;
}
.hr {
width: 1px;
height: 100%;
position: relative;
background: #e4ecfb;
z-index: 1;
}
.hr::before {
content: '';
display: block;
width: 1px;
height: 100%;
background-color: #f6f9fe;
}
.blank {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
justify-content: center;
text-align: center;
}
.blank svg {
width: 3rem;
height: 3rem;
margin-bottom: 1rem;
}
.content {
flex: 1 1 auto;
overflow: auto;
background-color: #f7faff;
width: 100%;
padding: 2rem;
box-sizing: border-box;
}
@media only screen and (max-width: 811px) {
.content {
padding: 0.25rem;
}
}
.menu {
display: flex;
flex-direction: column;
font-size: 0.85rem;
justify-content: flex-start;
flex: 0 0 auto;
padding: 0;
}
@media only screen and (max-width: 840px), (max-height: 840px) {
.menu {
overflow: auto;
font: 0.75rem;
}
}
.menu.menu--open .menu-item-name {
display: block;
}
.menu-toggle {
transition: all 500ms;
padding: 1rem;
text-align: center;
border-bottom: 1px solid #e6ecf1;
cursor: pointer;
}
.menu-toggle svg {
transition: all 250ms ease-out 50ms;
transform-origin: center center;
transform: scaleX(1);
}
.menu.menu--open .menu-toggle svg {
transform: scaleX(-1);
}
.menu-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 0.5rem 1rem;
cursor: pointer;
user-select: none;
position: relative;
text-align: left;
}
.menu-item:hover {
background: #f7faff;
}
.menu-item:hover:before {
content: '';
display: inline-block;
width: 1rem;
height: 1rem;
bottom: 2rem;
left: 2.5rem;
position: absolute;
background: linear-gradient(#fff, #fff), linear-gradient(#fff, #fff), #0085c7;
background-position: center;
background-size: 50% 2px, 2px 50%; /*thickness = 2px, length = 50% (25px)*/
background-repeat: no-repeat;
border-radius: 50%;
box-shadow: 0 1px 2px rgba(55, 63, 66, 0.07), 0 2px 4px rgba(55, 63, 66, 0.07), 0 4px 8px rgba(55, 63, 66, 0.07),
0 8px 16px rgba(55, 63, 66, 0.07), 0 16px 24px rgba(55, 63, 66, 0.07), 0 24px 32px rgba(55, 63, 66, 0.07);
}
.menu-item-name {
margin: 0 0.5rem 0 1rem;
white-space: nowrap;
display: none;
}
@media only screen and (max-width: 840px), (max-height: 840px) {
.menu-toggle {
padding: 1rem 0;
}
.menu-item {
padding: 0.5rem;
}
.menu-item:hover:before {
bottom: 1rem;
left: 1rem;
}
.menu-item svg {
width: 32px;
height: 32px;
}
}
/* dashboard and tiles */
.dashboard {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin: auto;
height: 100%;
}
.table {
margin: 0;
table-layout: fixed;
width: 100%;
}
.table td {
padding: 0.15rem 0.5rem;
font-size: 0.75rem;
white-space: nowrap;
width: 1.5rem;
}
.table td:first-child {
width: 4rem;
}
.table td:last-child {
width: auto;
}
.tile {
display: flex;
flex-direction: column;
flex: 0 0 auto;
width: calc(25% - 1rem);
margin: 0.5rem;
height: 50vh;
max-height: calc(50% - 1rem);
overflow: hidden;
background: white;
page-break-inside: avoid; /* important when printing the dashboard */
transition: all 250ms;
border-radius: 0.5rem;
box-sizing: border-box;
box-shadow: 0 1px 2px rgba(55, 63, 66, 0.07), 0 2px 4px rgba(55, 63, 66, 0.07), 0 4px 8px rgba(55, 63, 66, 0.07),
0 8px 16px rgba(55, 63, 66, 0.07), 0 16px 24px rgba(55, 63, 66, 0.07), 0 24px 32px rgba(55, 63, 66, 0.07);
}
@media only screen and (max-width: 1599px) {
.tile {
width: calc(33.33% - 1rem);
}
}
@media only screen and (max-width: 1079px) {
.tile {
width: calc(50% - 1rem);
}
}
@media only screen and (max-width: 1023px) {
.tile {
width: calc(100% - 1rem);
}
}
@media only screen and (max-height: 800px) {
.tile {
max-height: 400px;
}
}
.tile:last-child {
flex: 1 1 auto;
}
.tile:hover {
border-color: #adb7bd;
}
.tile.drag-over {
border: 2px dashed #000;
}
.tile .buttons {
transition: all 250ms;
opacity: 0;
}
@media (hover: none) and (pointer: coarse) {
.tile .buttons {
opacity: 1;
}
}
.tile:hover .buttons {
opacity: 1;
}
.tile .buttons > span {
padding: 0 0.5rem;
cursor: pointer;
}
.tile.drag-over {
border: 2px dashed #000;
background-color: rgba(0, 0, 0, 0.1);
transition: all 250ms;
}
.tile.drag-source {
opacity: 0.4;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
background-color: rgba(145, 200, 248, 0.75);
transform: scale(0.9);
transition: all 250ms;
}
.tile .tile-container {
border-bottom: 1px solid #e0e0e0;
padding: 0.75rem 1rem;
display: flex;
cursor: move;
}
.tile .tile-header {
flex-grow: 1;
font-size: 1rem;
font-weight: 400;
padding: 0.125rem;
opacity: 0.75;
}
.tile .tile-content {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
flex: 1 1 auto;
overflow: auto;
height: 100%;
}
.tile .blank-tile {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* tile content */
.wj-flexgrid {
border: none;
height: 100%;
}
.wj-flexgrid .wj-cell {
border-right: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding: 0.35rem 1rem;
font-size: 0.8125rem;
}
.wj-flexchart {
background: transparent;
height: calc(100%);
width: 100%;
border: none;
padding: 1rem;
margin: 0;
overflow: hidden;
}
.wj-radialgauge {
width: 60%;
max-width: 300px;
padding: 1rem;
overflow: hidden;
}
.wj-radialgauge .wj-value {
font-size: 0.75rem;
font-weight: 500;
}
.wj-lineargauge {
max-height: 1rem;
width: 100%;
overflow: hidden;
}
.wj-gauge .wj-face path {
stroke: none;
}
.wj-ranges {
opacity: 0.15;
}

10
src/components/wj/index.jsx

@ -0,0 +1,10 @@
import './license';
// Wijmo and Material Design Lite
import '@grapecity/wijmo.styles/themes/material/wijmo.theme.material.indigo-amber.css';
// Styles
import './index.css';
//React
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './App';
ReactDOM.render(React.createElement(App), document.getElementById('app'));

2
src/components/wj/license.js

@ -0,0 +1,2 @@
import { setLicenseKey } from '@grapecity/wijmo';
setLicenseKey('GrapeCity,427351286422477#B0JoIIklkIs4nIzYXMyAjMiojIyVmdiwSZzxWYmpjIyNHZisnOiwmbBJye0ICRiwiI34TQvAVW7ZWbqNjeWZzRh9Ucl5UTuZVaCR7ZpB5UH9EVC3kaqJWZ0pnasJ7Q9I4bB3GR0F6aIt6NZ96MFN4aotEZrUXe4kHUlllerlGb9dDSPhEcFFmclJXd8syNEtENzMTOBNVYpFje6lDUlBlbkdmcNNGOrAHR9pXNSl6NpVkbCNWUxlkZ7o7QT3icHNGavFXdapWQDZ5KsN5N7dDcyRUW85ESFBlb4JUZq3GWlBldXBzU0hjW9wkb8cncopnSOBjNkJFdyNmSqB7YVlnNzpnb9BDSMllTSFDaNFjca54dtBXatlEdS5mVNV6dxIEN7RUNxYGSvU7KIdndPpENvlXW6hzbCh7RsZ7YLJzboNnU7ZERTRkVBNlN7RWSGhDVrdmZqZ4cq94aFpkbWZDMGBHdVZ5YwUTTIdlN0RnVvMDdFJzbQt6bolDM5NkdrsUO536LrNWSotmI0IyUiwiIDRTR4MjM9QjI0ICSiwSMyIjM9UTMwEjM0IicfJye35XX3JSSwIjUiojIDJCLi86bpNnblRHeFBCI4VWZoNFelxmRg2Wbql6ViojIOJyes4nI5kkTRJiOiMkIsIibvl6cuVGd8VEIgIXZ7VWaWRncvBXZSBybtpWaXJiOi8kI1xSfis4N8gkI0IyQiwiIu3Waz9WZ4hXRgAydvJVa4xWdNBybtpWaXJiOi8kI1xSfiQjR6QkI0IyQiwiIu3Waz9WZ4hXRgACUBx4TgAybtpWaXJiOi8kI1xSfiMzQwIkI0IyQiwiIlJ7bDBybtpWaXJiOi8kI1xSfiUFO7EkI0IyQiwiIu3Waz9WZ4hXRgACdyFGaDxWYpNmbh9WaGBybtpWaXJiOi8kI1tlOiQmcQJCLiETMwAzNwACOwcDMwIDMyIiOiQncDJCLi46bj9idlRWe4l6YlBXYydmLqwibj9SbvNmL9RXajVGchJ7ZuoCLt36YukHdpNWZwFmcn9iKsI7au26YukHdpNWZwFmcn9iKsAnau26YukHdpNWZwFmcn9iKiojIz5GRiwiI9RXaDVGchJ7RiojIh94QiwiI7cDNyIDN6gjMxUzMdI6N');

370
src/components/wj/utils/DragDropTouch.js

@ -0,0 +1,370 @@
import * as wjcCore from '@grapecity/wijmo';
/**
* Object used to hold the data that is being dragged during drag and drop operations.
*
* It may hold one or more data items of different types. For more information about
* drag and drop operations and data transfer objects, see
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer">HTML Drag and Drop API</a>.
*
* This object is created automatically by the @see:DragDropTouch singleton and is
* accessible through the @see:dataTransfer property of all drag events.
*/
export class DataTransfer {
constructor() {
this._dropEffect = 'move';
this._effectAllowed = 'all';
this._data = {};
}
/**
* Gets or sets the type of drag-and-drop operation currently selected.
* The value must be 'none', 'copy', 'link', or 'move'.
*/
get dropEffect() {
return this._dropEffect;
}
set dropEffect(value) {
this._dropEffect = wjcCore.asString(value);
}
/**
* Gets or sets the types of operations that are possible.
* Must be one of 'none', 'copy', 'copyLink', 'copyMove', 'link',
* 'linkMove', 'move', 'all' or 'uninitialized'.
*/
get effectAllowed() {
return this._effectAllowed;
}
set effectAllowed(value) {
this._effectAllowed = wjcCore.asString(value);
}
/**
* Gets an array of strings giving the formats that were set in the @see:dragstart event.
*/
get types() {
return Object.keys(this._data);
}
/**
* Removes the data associated with a given type.
*
* The type argument is optional. If the type is empty or not specified, the data
* associated with all types is removed. If data for the specified type does not exist,
* or the data transfer contains no data, this method will have no effect.
*
* @param type Type of data to remove.
*/
clearData(type) {
if (type != null) {
delete this._data[type];
}
else {
this._data = null;
}
}
/**
* Retrieves the data for a given type, or an empty string if data for that type does
* not exist or the data transfer contains no data.
*
* @param type Type of data to retrieve.
*/
getData(type) {
return this._data[type] || '';
}
/**
* Set the data for a given type.
*
* For a list of recommended drag types, please see
* https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types.
*
* @param type Type of data to add.
* @param value Data to add.
*/
setData(type, value) {
this._data[type] = value;
}
/**
* Set the image to be used for dragging if a custom one is desired.
*
* @param img An image element to use as the drag feedback image.
* @param offsetX The horizontal offset within the image.
* @param offsetY The vertical offset within the image.
*/
setDragImage(img, offsetX, offsetY) {
var ddt = DragDropTouch._instance;
ddt._imgCustom = img;
ddt._imgOffset = new wjcCore.Point(offsetX, offsetY);
}
}
/**
* Defines a class that adds support for touch-based HTML5 drag/drop operations.
*
* The @see:DragDropTouch class listens to touch events and raises the
* appropriate HTML5 drag/drop events as if the events had been caused
* by mouse actions.
*
* The purpose of this class is to enable using existing, standard HTML5
* drag/drop code on mobile devices running IOS or Android.
*
* To use, include the DragDropTouch.js file on the page. The class will
* automatically start monitoring touch events and will raise the HTML5
* drag drop events (dragstart, dragenter, dragleave, drop, dragend) which
* should be handled by the application.
*
* For details and examples on HTML drag and drop, see
* https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_operations.
*/
export class DragDropTouch {
/**
* Initializes the single instance of the @see:DragDropTouch class.
*/
constructor() {
this._lastClick = 0;
// enforce singleton pattern
wjcCore.assert(!DragDropTouch._instance, 'DragDropTouch instance already created.');
// listen to touch events
if ('ontouchstart' in document) {
var d = document, ts = this._touchstart.bind(this), tm = this._touchmove.bind(this), te = this._touchend.bind(this);
d.addEventListener('touchstart', ts);
d.addEventListener('touchmove', tm);
d.addEventListener('touchend', te);
d.addEventListener('touchcancel', te);
}
}
/**
* Gets a reference to the @see:DragDropTouch singleton.
*/
static getInstance() {
return DragDropTouch._instance;
}
// ** event handlers
_touchstart(e) {
if (this._shouldHandle(e)) {
// raise double-click and prevent zooming
if (Date.now() - this._lastClick < DragDropTouch._DBLCLICK) {
if (this._dispatchEvent(e, 'dblclick', e.target)) {
e.preventDefault();
this._reset();
return;
}
}
// clear all variables
this._reset();
// get nearest draggable element
var src = wjcCore.closest(e.target, '[draggable]');
if (src) {
// give caller a chance to handle the hover/move events
if (!this._dispatchEvent(e, 'mousemove', e.target) &&
!this._dispatchEvent(e, 'mousedown', e.target)) {
// get ready to start dragging
this._dragSource = src;
this._ptDown = this._getPoint(e);
this._lastTouch = e;
e.preventDefault();
// show context menu if the user hasn't started dragging after a while
setTimeout(() => {
if (this._dragSource == src && this._img == null) {
if (this._dispatchEvent(e, 'contextmenu', src)) {
this._reset();
}
}
}, DragDropTouch._CTXMENU);
}
}
}
}
_touchmove(e) {
if (this._shouldHandle(e)) {
// see if target wants to handle move
var target = this._getTarget(e);
if (this._dispatchEvent(e, 'mousemove', target)) {
this._lastTouch = e;
e.preventDefault();
return;
}
// start dragging
if (this._dragSource && !this._img) {
var delta = this._getDelta(e);
if (delta > DragDropTouch._THRESHOLD) {
this._dispatchEvent(e, 'dragstart', this._dragSource);
this._createImage(e);
this._dispatchEvent(e, 'dragenter', target);
}
}
// continue dragging
if (this._img) {
this._lastTouch = e;
e.preventDefault(); // prevent scrolling
if (target != this._lastTarget) {
this._dispatchEvent(this._lastTouch, 'dragleave', this._lastTarget);
this._dispatchEvent(e, 'dragenter', target);
this._lastTarget = target;
}
this._moveImage(e);
this._dispatchEvent(e, 'dragover', target);
}
}
}
_touchend(e) {
if (this._shouldHandle(e)) {
// see if target wants to handle up
if (this._dispatchEvent(this._lastTouch, 'mouseup', e.target)) {
e.preventDefault();
return;
}
// user clicked the element but didn't drag, so clear the source and simulate a click
if (!this._img) {
this._dragSource = null;
this._dispatchEvent(this._lastTouch, 'click', e.target);
this._lastClick = Date.now();
}
// finish dragging
this._destroyImage();
if (this._dragSource) {
if (e.type.indexOf('cancel') < 0) {
this._dispatchEvent(this._lastTouch, 'drop', this._lastTarget);
}
this._dispatchEvent(this._lastTouch, 'dragend', this._dragSource);
this._reset();
}
}
}
// ** utilities
// ignore events that have been handled or that involve more than one touch
_shouldHandle(e) {
return e &&
!e.defaultPrevented &&
e.touches && e.touches.length < 2;
}
// clear all members
_reset() {
this._destroyImage();
this._dragSource = null;
this._lastTouch = null;
this._lastTarget = null;
this._ptDown = null;
this._dataTransfer = new DataTransfer();
}
// get point for a touch event
_getPoint(e, page) {
if (e && e.touches) {
e = e.touches[0];
}
wjcCore.assert(e && ('clientX' in e), 'invalid event?');
if (page == true) {
return new wjcCore.Point(e.pageX, e.pageY);
}
else {
return new wjcCore.Point(e.clientX, e.clientY);
}
}
// get distance between the current touch event and the first one
_getDelta(e) {
var p = this._getPoint(e);
return Math.abs(p.x - this._ptDown.x) + Math.abs(p.y - this._ptDown.y);
}
// get the element at a given touch event
_getTarget(e) {
var pt = this._getPoint(e), el = document.elementFromPoint(pt.x, pt.y);
while (el && getComputedStyle(el).pointerEvents == 'none') {
el = el.parentElement;
}
return el;
}
// create drag image from source element
_createImage(e) {
// just in case...
if (this._img) {
this._destroyImage();
}
// create drag image from custom element or drag source
var src = this._imgCustom || this._dragSource;
this._img = src.cloneNode(true);
this._copyStyle(src, this._img);
this._img.style.top = this._img.style.left = '-9999px';
// if creating from drag source, apply offset and opacity
if (!this._imgCustom) {
var rc = src.getBoundingClientRect(), pt = this._getPoint(e);
this._imgOffset = new wjcCore.Point(pt.x - rc.left, pt.y - rc.top);
this._img.style.opacity = DragDropTouch._OPACITY.toString();
}
// add image to document
this._moveImage(e);
document.body.appendChild(this._img);
}
// dispose of drag image element
_destroyImage() {
if (this._img && this._img.parentElement) {
this._img.parentElement.removeChild(this._img);
}
this._img = null;
this._imgCustom = null;
}
// move the drag image element
_moveImage(e) {
requestAnimationFrame(() => {
var pt = this._getPoint(e, true);
wjcCore.setCss(this._img, {
position: 'absolute',
pointerEvents: 'none',
zIndex: 999999,
left: Math.round(pt.x - this._imgOffset.x),
top: Math.round(pt.y - this._imgOffset.y)
});
});
}
// copy properties from an object to another
_copyProps(dst, src, props) {
for (var i = 0; i < props.length; i++) {
var p = props[i];
dst[p] = src[p];
}
}
_copyStyle(src, dst) {
// remove potentially troublesome attributes
DragDropTouch._rmvAtts.forEach(function (att) {
dst.removeAttribute(att);
});
// copy canvas content
if (src instanceof HTMLCanvasElement) {
var cSrc = src, cDst = dst;
cDst.width = cSrc.width;
cDst.height = cSrc.height;
cDst.getContext('2d').drawImage(cSrc, 0, 0);
}
// copy style
var cs = getComputedStyle(src);
for (var i = 0; i < cs.length; i++) {
var key = cs[i];
dst.style[key] = cs[key];
}
dst.style.pointerEvents = 'none';
// and repeat for all children
for (var i = 0; i < src.children.length; i++) {
this._copyStyle(src.children[i], dst.children[i]);
}
}
_dispatchEvent(e, type, target) {
if (e && target) {
var evt = document.createEvent('Event'), t = e.touches ? e.touches[0] : e;
evt.initEvent(type, true, true);
evt.button = 0;
evt.which = evt.buttons = 1;
this._copyProps(evt, e, DragDropTouch._kbdProps);
this._copyProps(evt, t, DragDropTouch._ptProps);
evt.dataTransfer = this._dataTransfer;
target.dispatchEvent(evt);
return evt.defaultPrevented;
}
return false;
}
}
/*private*/ DragDropTouch._instance = new DragDropTouch(); // singleton
// constants
DragDropTouch._THRESHOLD = 5; // pixels to move before drag starts
DragDropTouch._OPACITY = 0.5; // drag image opacity
DragDropTouch._DBLCLICK = 500; // max ms between clicks in a double click
DragDropTouch._CTXMENU = 900; // ms to hold before raising 'contextmenu' event
// copy styles/attributes from drag source to drag image element
DragDropTouch._rmvAtts = 'id,class,style,draggable'.split(',');
// synthesize and dispatch an event
// returns true if the event has been handled (e.preventDefault == true)
DragDropTouch._kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(',');
DragDropTouch._ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY'.split(',');

61
src/const/ApiConst.js

@ -118,46 +118,15 @@ export const TOKEN_ADW =
// export let BASE_OSPRO = "https://ospro-api.ospro.id";
// export let BASE_OSPRO = "https://project-api.ospro.id";
export let BASE_OSPRO = "http://localhost:8444/generic-ospro-backend";
// export let BASE_OSPRO = "http://103.73.125.81:8444"; // ip public adw
export let BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`;
export let BASE_SIMPRO_LUMEN_IMAGE = `${BASE_OSPRO}/assets/image`;
export let BASE_SIMPRO_LUMEN_FILE = `${BASE_OSPRO}/assets/file/project`;
export let BASE_SIMPRO_LUMEN_FILE_COMPANY = (file, company_name)=>{
export let BASE_SIMPRO_LUMEN_FILE_COMPANY = (file, company_name) => {
return `${BASE_OSPRO}/assets/${company_name}/file/project/${file}`;
}
export let BASE_SIMPRO_LUMEN_IMAGE_COMPANY = (file, company_name)=>{
export let BASE_SIMPRO_LUMEN_IMAGE_COMPANY = (file, company_name) => {
return `${BASE_OSPRO}/assets/${company_name}/image/${file}`;
}
// switch (APP_MODE) {
// case 'KIT':
// BASE_OSPRO = "https://kit-api.oslogdev.com"
// //for KIT server
// // BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`;
// // BASE_SIMPRO_LUMEN_IMAGE = `${BASE_SIMPRO_LUMEN}/assets/image`;
// // BASE_SIMPRO_LUMEN_FILE = `${BASE_SIMPRO_LUMEN}/assets/file/project`;
// break;
// case 'ADW':
// // BASE_OSPRO = "https://adw-api.ospro.id"
// BASE_OSPRO = "http://103.73.125.81:8444";
// BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`;
// BASE_SIMPRO_LUMEN_IMAGE = `${BASE_OSPRO}/assets/image`;
// BASE_SIMPRO_LUMEN_FILE = `${BASE_OSPRO}/assets/file/project`;
// break;
// case 'IU':
// BASE_OSPRO = "https://api-iu.ospro.id"
// BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`;
// BASE_SIMPRO_LUMEN_IMAGE = `${BASE_OSPRO}/assets/image`;
// BASE_SIMPRO_LUMEN_FILE = `${BASE_OSPRO}/assets/file/project`;
// break;
// default:
// BASE_OSPRO = "https://ospro-api.ospro.id"
// }
export const USERROLE_ADD = `${BASE_SIMPRO}/user-role/add`;
export const USERROLE_SEARCH = `${BASE_SIMPRO}/user-role/search`;
@ -849,8 +818,6 @@ export const MENU_COMPANY_DELETE = (id) => {
return `${BASE_SIMPRO_LUMEN}/menu-company/delete/${id}`;
};
export const DEMO_MANAGEMENT_ADD = `${BASE_SIMPRO_LUMEN}/demo-management/add`;
export const DEMO_MANAGEMENT_SEARCH = `${BASE_SIMPRO_LUMEN}/demo-management/search`;
export const DEMO_MANAGEMENT_EDIT = (id) => {
@ -863,3 +830,27 @@ export const DEMO_MANAGEMENT_DELETE = (id) => {
return `${BASE_SIMPRO_LUMEN}/demo-management/delete/${id}`;
};
export const DEMO_MANAGEMENT_LIST = `${BASE_SIMPRO_LUMEN}/demo-management/list`;
export const REFERRAL_CODE_ADD = `${BASE_SIMPRO_LUMEN}/refferal-code/add`;
export const REFERRAL_CODE_SEARCH = `${BASE_SIMPRO_LUMEN}/refferal-code/search`;
export const REFERRAL_CODE_EDIT = (id) => {
return `${BASE_SIMPRO_LUMEN}/refferal-code/update/${id}`;
};
export const REFERRAL_CODE_GET_ID = (id) => {
return `${BASE_SIMPRO_LUMEN}/refferal-code/edit/${id}`;
};
export const REFERRAL_CODE_DELETE = (id) => {
return `${BASE_SIMPRO_LUMEN}/refferal-code/delete/${id}`;
};
export const SALES_CONTACT_ADD = `${BASE_SIMPRO_LUMEN}/sales-contact/add`;
export const SALES_CONTACT_SEARCH = `${BASE_SIMPRO_LUMEN}/sales-contact/search`;
export const SALES_CONTACT_EDIT = (id) => {
return `${BASE_SIMPRO_LUMEN}/sales-contact/update/${id}`;
};
export const SALES_CONTACT_GET_ID = (id) => {
return `${BASE_SIMPRO_LUMEN}/sales-contact/edit/${id}`;
};
export const SALES_CONTACT_DELETE = (id) => {
return `${BASE_SIMPRO_LUMEN}/sales-contact/delete/${id}`;
};

40
src/const/CustomFunc.js

@ -164,6 +164,46 @@ export const QUERY_BUILDER_FIELD_SALES =
},
}
export const checkActMenup = (menuPath, actProp) => {
let foundObj;
let entireObj = JSON.parse(localStorage.getItem("menu_login"));
JSON.stringify(entireObj, (_, nestedValue) => {
if (nestedValue && nestedValue.url === menuPath) {
foundObj = nestedValue;
}
return nestedValue;
});
let output = false;
let actValue = actProp.trim().toLowerCase()
switch (actValue) {
case "create":
if (foundObj[actValue] === true) {
output = true;
} else {
output = false;
}
case "read":
if (foundObj[actValue] === true) {
output = true;
}
case "update":
if (foundObj[actValue] === true) {
output = true;
}
case "delete":
if (foundObj[actValue] === true) {
output = true;
}
// default:
// output = false
}
return output;
// return foundObj[actProp];
};
export const QUERY_BUILDER_FIELD_CUSTOMER =
{
"properties->name": {

7
src/const/en.json

@ -1,6 +1,8 @@
{
"3days": "3 Days Ago",
"7days": "7 Days Ago",
"amount": "Amount",
"allocation": "Allocation",
"action": "Action",
"add": "Add",
"address": "Address",
@ -16,6 +18,8 @@
"cancel": "Cancel",
"close": "Close",
"color": "Color",
"code": "Code",
"discount": "Discount",
"date": "Date",
"dateSend": "Send Date",
"dateAbsent": "Absent Date",
@ -37,6 +41,7 @@
"demoAdd": "Add Demo",
"employeeType": "Employee Type",
"edit": "Edit",
"exp": "Expire",
"export": "Export",
"exportExcel": "Export Excel",
"exportPdf": "Export Pdf",
@ -54,6 +59,8 @@
"inputDescription": "Input Description",
"inputParentMenu": "Select Parent Menu",
"inputAliasMenu": "Input Menu Alias",
"inputAmount": "Input Amount",
"inputCode": "Input Code",
"inputOrder": "Input Order",
"inputUrl": "Input URL",
"inputMsg": "Input Message",

7
src/const/id.json

@ -1,6 +1,8 @@
{
"3days": "3 Hari Yang lalu",
"7days": "7 Hari Yang lalu",
"amount": "Jumlah",
"allocation": "Kuota",
"action": "Aksi",
"add": "Tambah",
"address": "Alamat",
@ -16,6 +18,8 @@
"cancel": "Batal",
"close": "Tutup",
"color": "Warna",
"code": "Kode",
"discount": "Diskon",
"date": "Tanggal",
"dateSend": "Tanggal Kirim",
"dateAbsent": "Tanggal Absen",
@ -36,6 +40,7 @@
"divisionAdd": "Tambah Divisi",
"demoAdd": "Tambah Demo",
"edit": "Ubah",
"exp": "Batas Waktu",
"export": "Ekspor",
"exportExcel": "Ekspor Excel",
"exportPdf": "Ekspor Pdf",
@ -69,6 +74,8 @@
"inputNik": "Masukan NIK (KTP)",
"inputMessage": "Masukan Pesan",
"inputRole": "Masukan Peran",
"inputAmount": "Masukan Jumlah",
"inputCode": "Masukan Kode",
"image": "Gambar",
"imageCheck": "Lihat Selfie Presensi",
"locIn": "Lokasi Masuk",

2
src/containers/DefaultLayout/DefaultLayout.js

@ -111,8 +111,6 @@ class DefaultLayout extends Component {
default:
faviconElement.type = 'image/png';
}
console.log('faviconElement.type', faviconElement.type);
console.log('faviconContent', faviconContent);
faviconElement.href = faviconContent || `${process.env.PUBLIC_URL}/OSPRO.ico`;

15
src/routes.js

@ -50,16 +50,22 @@ const UserShift = React.lazy(() => import('./views/SimproV2/UserShift'));
const Kanban = React.lazy(() => import('./views/SimproV2/Kanban'));
// const DashboardProject = React.lazy(() => import('./views/DashboardProject'));
const DashboardBOD = React.lazy(() => import('./views/Dashboard/DashboardBOD'));
const DashboardDyna = React.lazy(() => import('./views/Dashboard/DashboardDyna'));
const DashboardDND = React.lazy(() => import('./components/wj/App'))
const DashboardCustomer = React.lazy(() => import('./views/Dashboard/DashboardCustomer'));
const DashboardProject = React.lazy(() => import('./views/Dashboard/DashboardProject'));
const DashboardProjectCarousell = React.lazy(() => import('./views/Dashboard/DashboardProjectCarousell'));
const MapMonitoring = React.lazy(() => import('./views/MapMonitoring'));
const Settings = React.lazy(() => import('./views/SimproV2/Settings/Desktop'));
const CompanyManagement = React.lazy(() => import('./views/Master/MasterCompany'))
const DemoManagement = React.lazy(() => import('./views/SimproV2/Demo'))
const DemoRequest = React.lazy(() => import('./views/SimproV2/DemoRequest'))
const ReferralCode = React.lazy(() => import('./views/SimproV2/ReferralCode'))
const SalesContact = React.lazy(() => import('./views/SimproV2/SalesContact'))
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: '/dashboard', name: 'DashboardBOD', component: DashboardBOD },
{ path: '/dashboard-dyna', name: 'DashboardBOD', component: DashboardDyna },
{ path: '/dashboard-dnd', name: 'DashboardBOD', component: DashboardDND },
{ path: '/dashboard-customer/:PROJECT_ID/:GANTT_ID/:SCURVE', name: 'DashboardCustomer', component: DashboardCustomer },
{ path: '/dashboard-project/:PROJECT_ID/:GANTT_ID/:Header', exact: true, name: 'Dashboard Project', component: DashboardProject },
{ path: '/dashboard-perproject', exact: true, name: 'Dashboard Project Carousell', component: DashboardProjectCarousell },
@ -119,8 +125,11 @@ const routes = [
{ path: '/map-monitoring', exact: true, name: 'Map Monitoring', component: MapMonitoring },
// { path: '/dashboard-project/:ID/:GANTTID', exact: true, name: 'Dashboard Project', component: DashboardProject },
{ path: '/settings', exact: true, name: 'Settings', component: Settings },
{ path: '/company-management', exact: true, name: 'Company Management', component: CompanyManagement },
{ path: '/demo-management', exact: true, name: 'Demo Management', component: DemoManagement },
{ path: '/register-management', exact: true, name: 'Register Management', component: CompanyManagement },
{ path: '/demo-request', exact: true, name: 'Request Demo', component: DemoRequest },
{ path: '/referral-code-management', exact: true, name: 'Referral Code Management', component: ReferralCode },
{ path: '/sales-contact', exact: true, name: 'Sales Contact', component: SalesContact },
];
export default routes;

296
src/views/Dashboard/DashboardDyna.js

@ -0,0 +1,296 @@
import React, { useState } from 'react';
import { Row, Col, Button, Modal } from 'antd';
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
import { Widgets } from 'react-awesome-query-builder';
const DashboardDyna = () => {
const [templates, setTemplates] = useState([]);
const [widgets, setWidgets] = useState([]);
const [templateColSpan, setTemplateColSpan] = useState(5);
const [widgetColSpan, setWidgetColSpan] = useState(5);
const [dashboardColSpan, setDashboardColSpan] = useState(19);
const [isModalOpen, setIsModalOpen] = useState(false);
const [templateIndex, setTemplateIndex] = useState(0);
const [columnIndex, setColumnIndex] = useState(0);
const addWidget = (widget) => {
// Make a copy of the current widgets state
const newWidgets = [...widgets];
if (!newWidgets[templateIndex]) {
newWidgets[templateIndex] = []
}
// Make sure the templateIndex and columnIndex are within bounds
// if (templateIndex <= newWidgets.length && columnIndex <= newWidgets[templateIndex].length) {
// Make a copy of the nested array at templateIndex
const nestedArray = [...newWidgets[templateIndex]];
// Update the value at columnIndex
nestedArray[columnIndex] = widget;
// Update the nested array in the newWidgets array
newWidgets[templateIndex] = nestedArray;
// Update the state with the newWidgets array
setWidgets(newWidgets);
// }
const lastIndex = templates.length - 1;
if (lastIndex >= 0) {
let id = templates[templateIndex].id;
let updatedTemplate = checkTemplate(id, newWidgets, true);
let updatedTemplates = [...templates];
updatedTemplates[templateIndex] = updatedTemplate;
setTemplates(updatedTemplates);
}
};
const addTemplate = (template) => {
const lastIndex = templates.length - 1;
setTemplates([...templates, checkTemplate(template)]);
};
const clearTemplates = () => {
setTemplates([]);
clearWidgets()
};
const clearWidgets = () => {
setWidgets([]);
};
const showModal = (template = 0, column = 0) => {
setTemplateIndex(template);
setColumnIndex(column);
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const checkTemplate = (template, widgets = null, add_widget = false) => {
let component, widgetCount, id = template;
let templateIndex = templates.length;
if (add_widget) {
templateIndex--
}
switch (template) {
case 0:
component = (
<Row style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={24} onClick={() => showModal(templateIndex, 0)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][0] : null}
</div>
</Col>
</Row>
);
widgetCount = 1;
break;
case 1:
component = (
<Row style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10, height: '25vh' }}>
<Col span={5} onClick={() => showModal(templateIndex, 0)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][0] : null}
</div> </Col>
<Col span={5} onClick={() => showModal(templateIndex, 1)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][1] : null}
</div> </Col>
<Col span={5} onClick={() => showModal(templateIndex, 2)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][2] : null}
</div> </Col>
<Col span={6} onClick={() => showModal(templateIndex, 3)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][3] : null}
</div> </Col>
</Row>
);
widgetCount = 4;
break;
case 2:
component = (
<Row style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10, height: '25vh' }}>
<Col span={5} onClick={() => showModal(templateIndex, 0)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][0] : null}
</div> </Col>
<Col span={18} onClick={() => showModal(templateIndex, 1)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][1] : null}
</div> </Col>
</Row>
);
widgetCount = 2;
break;
case 3:
component = (
<Row style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10, height: '25vh' }}>
<Col span={17} onClick={() => showModal(templateIndex, 0)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][0] : null}
</div> </Col>
<Col span={6} onClick={() => showModal(templateIndex, 1)}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10, height: '25vh' }}>
{widgets ? widgets[templateIndex][1] : null}
</div> </Col>
</Row>
);
widgetCount = 2;
break;
default:
break;
}
return { component, widgetCount, id };
};
const toggleTemplateColumn = () => {
if (templateColSpan === 1) {
setTemplateColSpan(5);
setDashboardColSpan(dashboardColSpan - 4);
} else {
setTemplateColSpan(1);
setDashboardColSpan(dashboardColSpan + 4);
}
};
const toggleWidgetColumn = () => {
if (widgetColSpan === 1) {
setWidgetColSpan(5);
setDashboardColSpan(dashboardColSpan - 4);
} else {
setWidgetColSpan(1);
setDashboardColSpan(dashboardColSpan + 4);
}
};
return (
<div style={{ marginLeft: -25, marginRight: -25 }}>
<Row>
<Col span={templateColSpan}>
<div style={{ backgroundColor: '#F8F8F8', margin: 2, paddingLeft: 20, paddingRight: 20, paddingTop: 10, height: '90vh' }}>
<div style={{ display: 'flex', flexDirection: 'row', marginBottom: 10 }}>
<div style={{ flex: 20, display: 'flex', flexDirection: 'column' }}>
{templateColSpan === 1 ?
<Button icon={<RightOutlined />} onClick={toggleTemplateColumn}></Button>
:
<>
<div style={{ color: '#444444', fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>Template</div>
<Row onClick={() => addTemplate(0)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={24}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
</Row>
<Row onClick={() => addTemplate(1)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={6}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
</Row>
<Row onClick={() => addTemplate(2)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={18}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
</Row>
<Row onClick={() => addTemplate(3)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={17}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={6}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
</Row>
<Button icon={<LeftOutlined />} onClick={toggleTemplateColumn}></Button>
</>
}
</div>
</div>
</div>
</Col>
<Col span={dashboardColSpan}>
<div style={{ backgroundColor: '#F8F8F8', margin: 2, paddingLeft: 20, paddingRight: 20, paddingTop: 10, height: '90vh' }}>
<div style={{ display: 'flex', flexDirection: 'row', marginBottom: 10 }}>
<div style={{ flex: 20, display: 'flex', flexDirection: 'column' }}>
<div style={{ color: '#444444', fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>Dashboard</div>
{templates.length > 0 ? templates.map((template) => {
return template.component;
}) : null}
<Button onClick={() => clearTemplates()}>Clear</Button>
</div>
</div>
</div>
</Col>
</Row>
<Modal title="Widgets" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<div style={{ backgroundColor: '#F8F8F8', margin: 2, paddingLeft: 20, paddingRight: 20, paddingTop: 10 }}>
<div style={{ display: 'flex', flexDirection: 'row', marginBottom: 10 }}>
<div style={{ flex: 20, display: 'flex', flexDirection: 'column' }}>
{widgetColSpan === 1 ?
<Button icon={<RightOutlined />} onClick={toggleWidgetColumn}></Button>
:
<>
<div style={{ color: '#444444', fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>Widget</div>
<Row onClick={() => addWidget(0)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={18}>
<div style={{ padding: 0 }}>Pie Chart Project By Role</div>
</Col>
</Row>
<Row onClick={() => addWidget(1)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={18}>
<div style={{ padding: 0 }}>Pie Chart Project By Role</div>
</Col>
</Row>
<Row onClick={() => addWidget(2)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={18}>
<div style={{ padding: 0 }}>Pie Chart Project By Role</div>
</Col>
</Row>
<Row onClick={() => addWidget(3)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
<Col span={5}>
<div style={{ backgroundColor: '#CCCCCC', padding: 10 }} />
</Col>
<Col span={18}>
<div style={{ padding: 0 }}>Pie Chart Project By Role</div>
</Col>
</Row>
</>
}
</div>
</div>
</div>
</Modal>
</div>
);
};
export default DashboardDyna;

237
src/views/Master/MasterRoles/DialogMenuRoles.js

@ -3,11 +3,8 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, Row, Col, Table } from 'rea
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
import 'antd/dist/antd.css';
import axios from 'axios';
import { MENU_SEARCH } from '../../../const/ApiConst.js';
import { MENU_COMPANY_SEARCH } from '../../../const/ApiConst.js';
const token = window.localStorage.getItem('token');
const config = {
headers:
{
@ -15,7 +12,6 @@ const config = {
"Content-type": `application/json`
}
};
export default class DialogMenuRoles extends Component {
constructor(props) {
super(props)
@ -36,13 +32,13 @@ export default class DialogMenuRoles extends Component {
stateUpdateAll: false,
stateDeleteAll: false,
allChecked: true,
company_id: props.company_id || null,
}
}
async componentDidMount() {
this.props.showDialog(this.showDialog);
this.getAllMenu();
}
async componentDidUpdate() {
if (this.state.isParentClick === true) {
const { idRoles } = this.props
@ -54,43 +50,32 @@ export default class DialogMenuRoles extends Component {
this.setState({ isParentClick: false, id: idRoles });
}
}
showDialog = () => {
this.setState({ isParentClick: true });
}
getAllMenu = async () => {
const payload = {
"paging": { "start": 0, "length": -1 },
"columns": [
{ "name": "name", "logic_operator": "ilike", "value": "", "operator": "AND" }
{ "name": "company_id", "logic_operator": "=", "value": this.state.company_id, "operator": "AND" }
],
"joins": [
{ "name": "t_roles_menu", "column_join": "id", "column_results": ["create", "update", "delete", "read"] }
{ "name": "t_roles_menu", "column_join": "menu_id", "column_results": ["create", "update", "delete", "read"] },
{ "name": "m_menu", "column_join": "menu_id", "column_results": ["name"] }
],
"orders": { "columns": ["id"], "ascending": false }
}
const result = await axios
.post(MENU_SEARCH, payload, config)
.post(MENU_COMPANY_SEARCH, payload, config)
.then(res => res)
.catch((error) => error.response);
// console.log('78', result);
if (result && result.data && result.data.code == 200) {
this.setState({ menu: result.data.data }, () => {
this.setStateMenu(false);
});
} else {
}
}
setStateMenu = edit => {
const stateMenu = [];
this.state.menu.map((val) => {
@ -104,7 +89,6 @@ export default class DialogMenuRoles extends Component {
})
})
}
setStateRead = edit => {
const stateRead = [];
this.state.menu.map((val) => {
@ -118,7 +102,6 @@ export default class DialogMenuRoles extends Component {
})
})
}
setStateCreate = edit => {
const stateCreate = [];
this.state.menu.map((val) => {
@ -132,7 +115,6 @@ export default class DialogMenuRoles extends Component {
})
})
}
setStateUpdate = edit => {
const stateUpdate = [];
this.state.menu.map((val) => {
@ -146,7 +128,6 @@ export default class DialogMenuRoles extends Component {
})
})
}
setStateDelete = edit => {
const stateDelete = [];
this.state.menu.map((val) => {
@ -160,13 +141,10 @@ export default class DialogMenuRoles extends Component {
})
})
}
checkMenuRoles = () => {
let copyStateMenu = [...this.state.stateMenu];
this.props.menuRoles.map((val, indexMenu) => {
let index = this.getIndexDataMenu(val.menu_id);
if (index >= 0 || val.read === true || val.create === true || val.update === true || val.delete === true) {
copyStateMenu[index] = true;
}
@ -180,7 +158,6 @@ export default class DialogMenuRoles extends Component {
}
})
}
checkReadRoles = () => {
let copyStateRead = [...this.state.stateRead];
this.props.menuRoles.map((val) => {
@ -191,7 +168,6 @@ export default class DialogMenuRoles extends Component {
copyStateRead[index] = false;
}
})
this.setState({ stateRead: [] }, () => {
let check = copyStateRead.some(this.checkArray);
if (check === false) {
@ -201,10 +177,8 @@ export default class DialogMenuRoles extends Component {
}
})
}
checkCreateRoles = () => {
let copyStateCreate = [...this.state.stateCreate];
this.props.menuRoles.map((val) => {
let index = this.getIndexDataMenu(val.menu_id);
if (val.create === true) {
@ -213,7 +187,6 @@ export default class DialogMenuRoles extends Component {
copyStateCreate[index] = false;
}
})
this.setState({ stateCreate: [] }, () => {
let check = copyStateCreate.some(this.checkArray);
if (check === false) {
@ -223,10 +196,8 @@ export default class DialogMenuRoles extends Component {
}
})
}
checkUpdateRoles = () => {
let copyStateUpdate = [...this.state.stateUpdate];
this.props.menuRoles.map((val) => {
let index = this.getIndexDataMenu(val.menu_id);
if (val.update === true) {
@ -235,7 +206,6 @@ export default class DialogMenuRoles extends Component {
copyStateUpdate[index] = false;
}
})
this.setState({ stateUpdate: [] }, () => {
let check = copyStateUpdate.some(this.checkArray);
if (check === false) {
@ -245,10 +215,8 @@ export default class DialogMenuRoles extends Component {
}
})
}
checkDeleteRoles = () => {
let copyStateDelete = [...this.state.stateDelete];
this.props.menuRoles.map((val) => {
let index = this.getIndexDataMenu(val.menu_id);
if (val.delete === true) {
@ -257,7 +225,6 @@ export default class DialogMenuRoles extends Component {
copyStateDelete[index] = false;
}
})
this.setState({ stateDelete: [] }, () => {
let check = copyStateDelete.some(this.checkArray);
if (check === false) {
@ -267,32 +234,26 @@ export default class DialogMenuRoles extends Component {
}
})
}
getIndexDataMenu = (id) => {
let index = this.state.menu.findIndex(obj => obj.id === id);
return index
}
getIndexDataCreate = (id) => {
let index = this.state.stateCreate.findIndex(obj => obj.id === id);
return index
}
getIndexDataDelete = (id) => {
let index = this.state.stateDelete.findIndex(obj => obj.id === id);
return index
}
getIndexDataRead = (id) => {
let index = this.state.stateRead.findIndex(obj => obj.id === id);
return index
}
getIndexDataUpdate = (id) => {
let index = this.state.stateUpdate.findIndex(obj => obj.id === id);
return index
}
handleSave = () => {
const {
stateMenu,
@ -303,9 +264,7 @@ export default class DialogMenuRoles extends Component {
menu,
id
} = this.state
const arrayData = [];
menu.map((val, index) => {
let data = {
roles_id: id,
@ -315,56 +274,116 @@ export default class DialogMenuRoles extends Component {
update: stateUpdate[index],
delete: stateDelete[index],
checked: stateMenu[index],
}
arrayData.push(data);
})
console.log('arrayData', arrayData);
// this.props.closeDialog('save', arrayData);
this.props.closeDialog('save', arrayData);
this.setState({ id: 0 });
}
handleCancel = () => {
this.props.closeDialog('cancel', 'none')
}
handleChangeCheckbox = (checked, index) => {
let copyStateMenu = [...this.state.stateMenu];
copyStateMenu[index] = checked;
this.setState({ stateMenu: copyStateMenu })
}
handleChangeCheckboxRead = (checked, index) => {
handleChangeCheckboxRead = (checked, index, menuItem = null, menuIdxList = []) => {
let copyStateRead = [...this.state.stateRead];
copyStateRead[index] = checked;
let menu = this.state.menu;
let checkMenuParent = menu.map((state, index) => {
if (state.parent_menu_id === menuItem.menu_id) {
return state.menu_id
} else {
return null
}
}).filter(index => index !== null);
let stateReadIdx = [];
menuIdxList.forEach((menu, index) => {
checkMenuParent.forEach((check, idx) => {
if (check === menu) {
stateReadIdx.push(index);
}
});
});
stateReadIdx.map((stateRead) => {
copyStateRead[stateRead] = checked
})
this.setState({ stateRead: copyStateRead })
}
handleChangeCheckboxCreate = (checked, index) => {
handleChangeCheckboxCreate = (checked, index, menuItem = null, menuIdxList = []) => {
let copyStateCreate = [...this.state.stateCreate];
copyStateCreate[index] = checked;
let menu = this.state.menu;
let checkMenuParent = menu.map((state, index) => {
if (state.parent_menu_id === menuItem.menu_id) {
return state.menu_id
} else {
return null
}
}).filter(index => index !== null);
let stateCreateIdx = [];
menuIdxList.forEach((menu, index) => {
checkMenuParent.forEach((check, idx) => {
if (check === menu) {
stateCreateIdx.push(index);
}
});
});
stateCreateIdx.map((stateCreate) => {
copyStateCreate[stateCreate] = checked
})
this.setState({ stateCreate: copyStateCreate })
}
handleChangeCheckboxEdit = (checked, index) => {
handleChangeCheckboxEdit = (checked, index, menuItem = null, menuIdxList = []) => {
let copyStateEdit = [...this.state.stateUpdate];
copyStateEdit[index] = checked;
let menu = this.state.menu;
let checkMenuParent = menu.map((state, index) => {
if (state.parent_menu_id === menuItem.menu_id) {
return state.menu_id
} else {
return null
}
}).filter(index => index !== null);
let stateEditIdx = [];
menuIdxList.forEach((menu, index) => {
checkMenuParent.forEach((check, idx) => {
if (check === menu) {
stateEditIdx.push(index);
}
});
});
stateEditIdx.map((stateEdit) => {
copyStateEdit[stateEdit] = checked
})
this.setState({ stateUpdate: copyStateEdit })
}
handleChangeCheckboxDelete = (checked, index) => {
handleChangeCheckboxDelete = (checked, index, menuItem = null, menuIdxList = []) => {
let copyStateDelete = [...this.state.stateDelete];
copyStateDelete[index] = checked;
let menu = this.state.menu;
let checkMenuParent = menu.map((state, index) => {
if (state.parent_menu_id === menuItem.menu_id) {
return state.menu_id
} else {
return null
}
}).filter(index => index !== null);
let stateDeleteIdx = [];
menuIdxList.forEach((menu, index) => {
checkMenuParent.forEach((check, idx) => {
if (check === menu) {
stateDeleteIdx.push(index);
}
});
});
stateDeleteIdx.map((stateDelete) => {
copyStateDelete[stateDelete] = checked
})
this.setState({ stateDelete: copyStateDelete })
}
handleChangeCheckboxReadAll = (checked) => {
let copyStateRead = [...this.state.stateRead];
copyStateRead.map((val, index) => {
@ -372,7 +391,6 @@ export default class DialogMenuRoles extends Component {
})
this.setState({ stateRead: copyStateRead })
}
handleChangeCheckboxCreateAll = (checked, index) => {
let copyStateCreate = [...this.state.stateCreate];
copyStateCreate.map((val, index) => {
@ -380,7 +398,6 @@ export default class DialogMenuRoles extends Component {
})
this.setState({ stateCreate: copyStateCreate })
}
handleChangeCheckboxEditAll = (checked, index) => {
let copyStateEdit = [...this.state.stateUpdate];
copyStateEdit.map((val, index) => {
@ -388,7 +405,6 @@ export default class DialogMenuRoles extends Component {
})
this.setState({ stateUpdate: copyStateEdit })
}
handleChangeCheckboxDeleteAll = (checked, index) => {
let copyStateDelete = [...this.state.stateDelete];
copyStateDelete.map((val, index) => {
@ -396,69 +412,44 @@ export default class DialogMenuRoles extends Component {
})
this.setState({ stateDelete: copyStateDelete })
}
// renderForm = () => {
// const { menu, stateRead, stateCreate, stateUpdate, stateDelete } = this.state
// console.log('menu', menu);
// return (
// menu.map((val, index) => {
// return (
// <tr key={index}>
// <td>{val.name}</td>
// <td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxRead(e.target.checked, index)} defaultChecked={stateRead[index]} /></td>
// <td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxCreate(e.target.checked, index)} defaultChecked={stateCreate[index]} /></td>
// <td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxEdit(e.target.checked, index)} defaultChecked={stateUpdate[index]} /></td>
// <td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxDelete(e.target.checked, index)} defaultChecked={stateDelete[index]} /></td>
// </tr>
// )
// })
// )
// }
renderForm = () => {
const { menu, stateRead, stateCreate, stateUpdate, stateDelete } = this.state;
let menuIdxList = []
let menuIdx = 0
const getChildren = (parentId) => {
return menu.filter(item => item.parent_id === parentId);
return menu.filter(item => item.parent_menu_id === parentId);
};
// Function to render menu items
const renderMenu = (parentId, depth = 0) => {
const children = getChildren(parentId);
return children.map((menuItem, index) => {
const paddingLeft = depth * 20;
const fontWeight = menuItem.parent_id === null ? 'bold' : 'normal';
const currentIndex = menuIdx;
menuIdxList[currentIndex] = menuItem.menu_id;
menuIdx++
const paddingLeft = depth * 30;
return (
<React.Fragment key={index}>
<React.Fragment key={menuIdx - 1}>
<tr >
<td style={{ paddingLeft: paddingLeft }}>{menuItem.name}</td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxRead(e.target.checked, index)} defaultChecked={stateRead[index]} /></td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxCreate(e.target.checked, index)} defaultChecked={stateCreate[index]} /></td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxEdit(e.target.checked, index)} defaultChecked={stateUpdate[index]} /></td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxDelete(e.target.checked, index)} defaultChecked={stateDelete[index]} /></td>
<td style={{ paddingLeft: paddingLeft }}>{menuItem.join_second_name}</td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxRead(e.target.checked, currentIndex, menuItem, menuIdxList)} defaultChecked={stateRead[currentIndex]} /></td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxCreate(e.target.checked, currentIndex, menuItem, menuIdxList)} defaultChecked={stateCreate[currentIndex]} /></td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxEdit(e.target.checked, currentIndex, menuItem, menuIdxList)} defaultChecked={stateUpdate[currentIndex]} /></td>
<td><input type="checkbox" onClick={(e) => this.handleChangeCheckboxDelete(e.target.checked, currentIndex, menuItem, menuIdxList)} defaultChecked={stateDelete[currentIndex]} /></td>
</tr>
{renderMenu(menuItem.id, depth + 1)} {/* Recursively render children */}
{renderMenu(menuItem.menu_id, depth + 1)}
</React.Fragment>
);
});
};
// Render top-level menu items (parents)
menuIdxList = []
menuIdx = 0
return (
<React.Fragment>
{this.renderAll()} {/* Render the "All" row */}
{renderMenu(null)} {/* Render the menu items */}
{this.renderAll()}
{renderMenu(null)}
</React.Fragment>
);
};
renderAll = () => {
return (
<tr>
<td>All</td>
@ -469,45 +460,32 @@ export default class DialogMenuRoles extends Component {
</tr>
)
}
checkArray = (val) => {
return val === false;
}
handleAllChecked = (checked) => {
this.setState({ allChecked: !this.state.allChecked });
if (checked === true) {
let check = this.state.stateMenu.some(this.checkArray);
if (check) {
const stateMenu = [];
this.state.menu.map((val) => {
stateMenu.push(true);
})
this.setState({ stateMenu: [] }, () => {
this.setState({ stateMenu: stateMenu });
})
}
} else {
const stateMenu = [];
this.state.menu.map((val) => {
stateMenu.push(false);
})
this.setState({ stateMenu: [] }, () => {
this.setState({ stateMenu: stateMenu });
})
}
}
render() {
return (
<Modal size="xl" scrollable={true} isOpen={this.props.openDialog} toggle={this.props.toggleDialog}>
@ -525,12 +503,11 @@ export default class DialogMenuRoles extends Component {
Create
</th>
<th>
Edite
Edit
</th>
<th>
Delete
</th>
</tr>
</thead>
<tbody>
@ -546,4 +523,4 @@ export default class DialogMenuRoles extends Component {
</Modal>
)
}
}
}

42
src/views/Master/MasterRoles/index.js

@ -10,6 +10,7 @@ import { NotificationContainer, NotificationManager } from 'react-notifications'
import { Pagination, Tooltip, Table } from 'antd';
import { ROLE_ADD, ROLE_SEARCH, ROLE_EDIT, ROLE_DELETE, ROLEMENU_ADD, ROLEMENU_SEARCH, ROLEMENU_DELETE_ROLE } from '../../../const/ApiConst.js';
import { withTranslation } from 'react-i18next';
import { checkActMenup } from '../../../const/CustomFunc';
const LENGTH_DATA = 10
@ -66,15 +67,30 @@ class index extends Component {
className: 'nowrap',
render: (text, record) => <>
<Tooltip title={this.props.t('menuRoles')}>
<i className="cil-menu" style={{ color: 'green', marginRight: 10, cursor: "pointer" }} onClick={() => this.handleMenuRoles(text.id)}></i>
{
checkActMenup('/roles', 'update') ?
<i className="cil-menu fa-lg" style={{ color: 'green', marginRight: 10, cursor: "pointer" }} onClick={() => this.handleMenuRoles(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={this.props.t('delete')}>
<i className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => this.handleDelete(text.id)}></i>
{
checkActMenup('/roles', 'delete') ?
<i className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => this.handleDelete(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={this.props.t('edit')}>
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => this.handleEdit(text)}></i>
{
checkActMenup('/roles', 'update') ?
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => this.handleEdit(text)}></i>
:
null
}
</Tooltip>
</>,
},
@ -331,10 +347,15 @@ class index extends Component {
let promises = []
let result = []
dataArray.map((val, index) => {
if (val.checked === true) {
if (val.read === true) {
const formData = {
menu_id: val.menu_id,
role_id: val.roles_id
role_id: val.roles_id,
create: val.create,
read: val.read,
update: val.update,
delete: val.delete
}
promises.push(axios.post(ROLEMENU_ADD, formData, this.state.config)
.then(res => result.push(res)))
@ -498,10 +519,17 @@ class index extends Component {
</Col>
<Col>
<Tooltip title={this.props.t('rolesAdd')}>
<Button id="TooltipTambah" color="success" onClick={() => this.handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
{
checkActMenup('/roles', 'create') ?
<Button Button id="TooltipTambah" color="success" onClick={() => this.handleOpenDialog('Save')}><i className="fa fa-plus"></i>
</Button>
:
null
}
</Tooltip>
<Tooltip title={this.props.t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} id="TooltipExport" color="primary" onClick={() => this.handleExportExcel()}><i className="fa fa-print"></i></Button>
<Button style={{ marginLeft: "5px" }} id="TooltipExport" color="primary" onClick={() => this.handleExportExcel()}><i className="fa fa-print"></i></Button>
</Tooltip>
</Col>
</Row>

17
src/views/Master/MenuCompany/index.js

@ -10,6 +10,8 @@ import { MENU_ADD, MENU_SEARCH, MENU_EDIT, MENU_DELETE, MENU_LIST, MENU_COMPANY_
import { NotificationContainer, NotificationManager } from 'react-notifications';
import { Pagination, Tooltip, Table } from 'antd';
import { useTranslation } from 'react-i18next';
import { checkActMenup } from '../../../const/CustomFunc.js';
import { useLocation } from "react-router-dom";
const token = window.localStorage.getItem('token');
const column = [
{ name: "Nama" },
@ -34,6 +36,7 @@ const Index = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation()
const [alertDelete, setAlertDelete] = useState(false)
const [allDataMenu, setAllDataMenu] = useState([])
const [clickOpenModal, setClickOpenModal] = useState(false)
@ -340,10 +343,20 @@ const Index = ({ params, ...props }) => {
key: 'x',
render: (text, record) => <>
<Tooltip title={t('delete')}>
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
{
checkActMenup(location.pathname, 'delete') ?
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={t('edit')}>
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
{
checkActMenup(location.pathname, 'update') ?
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
:
null
}
</Tooltip>
</>,
},

28
src/views/Master/ProjectPhase/index.js

@ -9,6 +9,14 @@ import { NotificationContainer, NotificationManager } from 'react-notifications'
import { PROJECT_PHASE_ADD, PROJECT_PHASE_EDIT, PROJECT_PHASE_DELETE, PROJECT_PHASE_SEARCH, COMPANY_MANAGEMENT_LIST, BASE_OSPRO } from '../../../const/ApiConst';
import { Pagination, Button, Tooltip, Table } from 'antd';
import { useTranslation } from 'react-i18next';
import {
formatNumber,
formatRupiah,
formatThousand,
renderFormatRupiah,
checkActMenup,
} from "../../../const/CustomFunc";
import { useLocation } from "react-router-dom";
const ProjectPhase = ({ params, ...props }) => {
@ -26,6 +34,7 @@ const ProjectPhase = ({ params, ...props }) => {
user_name = props.user_name;
}
const location = useLocation();
const HEADER = {
headers: {
"Content-Type": "application/json",
@ -350,10 +359,20 @@ const ProjectPhase = ({ params, ...props }) => {
className: 'nowrap',
render: (text, record) => <>
<Tooltip title={t('delete')}>
<i className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
{
checkActMenup(location.pathname, 'delete') ?
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={t('Edit')}>
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
{
checkActMenup(location.pathname, 'update') ?
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
:
null
}
</Tooltip>{" "}
</>,
},
@ -429,7 +448,12 @@ const ProjectPhase = ({ params, ...props }) => {
</Col>
<Col>
<Tooltip title={t('projectPhase')}>
{
checkActMenup(location.pathname, 'create') ?
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
:
null
}
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>

23
src/views/Master/RoleProject/index.js

@ -9,6 +9,7 @@ import { NotificationContainer, NotificationManager } from 'react-notifications'
import { PROJECT_ROLE_ADD, PROJECT_ROLE_SEARCH, PROJECT_ROLE_EDIT, PROJECT_ROLE_DELETE } from '../../../const/ApiConst.js';
import { Pagination, Tooltip, Table } from 'antd';
import { withTranslation } from 'react-i18next';
import { checkActMenup } from '../../../const/CustomFunc';
const LENGTH_DATA = 10
class index extends Component {
constructor(props) {
@ -63,10 +64,20 @@ class index extends Component {
className: 'nowrap',
render: (text, record) => <>
<Tooltip title={this.props.t('delete')}>
<i className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => this.handleDelete(text.id)}></i>
{
checkActMenup('/project-role', 'delete') ?
<i className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => this.handleDelete(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={this.props.t('edit')}>
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => this.handleEdit(text)}></i>
{
checkActMenup('/project-role', 'update') ?
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => this.handleEdit(text)}></i>
:
null
}
</Tooltip>
</>,
},
@ -403,7 +414,13 @@ class index extends Component {
</Col>
<Col>
<Tooltip title={this.props.t('rolesAdd')}>
<Button id="TooltipTambah" color="success" onClick={() => this.handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
{
checkActMenup('/roles', 'create') ?
<Button Button id="TooltipTambah" color="success" onClick={() => this.handleOpenDialog('Save')}><i className="fa fa-plus"></i>
</Button>
:
null
}
</Tooltip>
<Tooltip title={this.props.t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} id="TooltipExport" color="primary" onClick={() => this.handleExportExcel()}><i className="fa fa-print"></i></Button>

30
src/views/SimproV2/ChecklistK3/index.js

@ -10,6 +10,14 @@ import {
CHECKLIST_K3_ADD, CHECKLIST_K3_EDIT, CHECKLIST_K3_DELETE, CHECKLIST_K3_SEARCH, COMPANY_MANAGEMENT_LIST
} from '../../../const/ApiConst';
import { useTranslation } from 'react-i18next';
import { useLocation } from "react-router-dom";
import {
formatNumber,
formatRupiah,
formatThousand,
renderFormatRupiah,
checkActMenup,
} from "../../../const/CustomFunc";
const token = window.localStorage.getItem('token');
const config = {
@ -34,6 +42,7 @@ const ChecklistK3 = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation();
const HEADER = {
headers: {
"Content-Type": "application/json",
@ -363,7 +372,12 @@ const ChecklistK3 = ({ params, ...props }) => {
</Col>
<Col>
<Tooltip title={t('ChecklistK3Add')}>
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
{
checkActMenup(location.pathname, 'create') ?
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
:
null
}
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} color="primary" onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>
@ -390,10 +404,20 @@ const ChecklistK3 = ({ params, ...props }) => {
<tr key={n.id}>
<td className='nowrap'>
<Tooltip title={t('delete')}>
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(n.id)}></i>
{
checkActMenup(location.pathname, 'delete') ?
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(n.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={t('edit')}>
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(n)}></i>
{
checkActMenup(location.pathname, 'edit') ?
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(n)}></i>
:
null
}
</Tooltip>
</td>
{role_name === 'Super Admin' &&

41
src/views/SimproV2/CreatedProyek/index.js

@ -61,9 +61,6 @@ import {
IMAGE_GET_BY_ID,
IMAGE_DELETE,
} from "../../../const/ApiConst";
import {
formatThousand
} from "../../../const/CustomFunc";
import moment from "moment";
// import DialogFormResource from './DialogFormResource';
import DialogFormMaterial from "./DataRequestMaterial";
@ -72,6 +69,13 @@ import DialogDocument from "./DialogDocument";
import DialogInitDocument from "./DialogInitDocument";
import DialogGantt from "./DialogGantt";
import DialogHierarchy from "./DialogHierarchy";
import {
formatNumber,
formatRupiah,
formatThousand,
renderFormatRupiah,
checkActMenup,
} from "../../../const/CustomFunc";
// import DialogAsignHr from './AsignHrProject';
import AssignHrProject from "./AsignHrProject";
import AssignCustProject from "./AsignCustProject";
@ -80,7 +84,7 @@ import ViewProject from "./ViewProject";
import ReportAnalysis from "./ReportAnalysis";
import { Icon } from "@iconify/react";
// import SubProyekComp from './SubProyekComp';
import { Link, useHistory, withRouter } from "react-router-dom";
import { Link, useHistory, withRouter, useLocation } from "react-router-dom";
import { t } from "i18next";
const url = "";
@ -100,6 +104,7 @@ const CreatedProyek = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation();
const history = useHistory();
const HEADER = {
headers: {
@ -1727,10 +1732,17 @@ const CreatedProyek = ({ params, ...props }) => {
content={popupMenu(text, record)}
trigger="click"
>
{
checkActMenup(location.pathname, 'read') ?
<Button size="small" type="link" style={{ color: "green" }}>
<i className="fa fa-ellipsis-v"></i>
</Button>
:
null
}
</Popover>
{
checkActMenup(location.pathname, 'update') ?
<Popover
placement="rightTop"
title={text.nama}
@ -1752,6 +1764,9 @@ const CreatedProyek = ({ params, ...props }) => {
</Button>
)}
</Popover>
:
null
}
</>
),
},
@ -2106,12 +2121,18 @@ const CreatedProyek = ({ params, ...props }) => {
<Col>
{parseInt(role_id) == 44 ? null : ( // role kustomer
<Tooltip title="Add Project">
<Button
style={{ background: "#4caf50", color: "#fff" }}
onClick={() => handleOpenDialogProyek(0)}
>
<i className="fa fa-plus"></i>
</Button>
{
checkActMenup(location.pathname, 'create') ?
<Button
style={{ background: "#4caf50", color: "#fff" }}
onClick={() => handleOpenDialogProyek(0)}
>
<i className="fa fa-plus"></i>
</Button>
:
null
}
</Tooltip>
)}
<Tooltip title="Export">

0
src/views/SimproV2/Demo/DialogForm.js → src/views/SimproV2/DemoRequest/DialogForm.js

4
src/views/SimproV2/Demo/index.js → src/views/SimproV2/DemoRequest/index.js

@ -202,7 +202,7 @@ const ProjectType = ({ params, ...props }) => {
if (type === "save") {
saveDemo(data);
} else if (type === "edit") {
editMaterialR(data);
editDemo(data);
}
setDataEdit([])
setOpenDialog(false)
@ -221,7 +221,7 @@ const ProjectType = ({ params, ...props }) => {
}
}
const editMaterialR = async (data) => {
const editDemo = async (data) => {
let urlEdit = DEMO_MANAGEMENT_EDIT(data.id)
const formData = data
const result = await axios.put(urlEdit, formData, HEADER)

37
src/views/SimproV2/Divisi/index.js

@ -8,6 +8,14 @@ import { DIVISI_LIST, DIVISI_ADD, DIVISI_EDIT, DIVISI_DELETE, DIVISI_SEARCH, COM
import { NotificationContainer, NotificationManager } from 'react-notifications';
import { Pagination, Button, Tooltip, Table, Spin } from 'antd';
import { useTranslation } from 'react-i18next';
import { useLocation } from "react-router-dom";
import {
formatNumber,
formatRupiah,
formatThousand,
renderFormatRupiah,
checkActMenup,
} from "../../../const/CustomFunc";
const ProjectType = ({ params, ...props }) => {
@ -24,7 +32,7 @@ const ProjectType = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation();
const HEADER = {
headers: {
"Content-Type": "application/json",
@ -400,13 +408,29 @@ const ProjectType = ({ params, ...props }) => {
render: (text, record) =>
<>
<Tooltip title="Hapus">
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
{
checkActMenup(location.pathname, 'delete') ?
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title="Add">
<i id="TooltipEdit" className="cil-level-down fa-sm" style={{ color: 'blue', marginRight: 10, cursor: "pointer" }} onClick={() => handleAddChild(text)}></i>
{
checkActMenup(location.pathname, 'create') ?
<i id="TooltipEdit" className="cil-level-down fa-sm" style={{ color: 'blue', marginRight: 10, cursor: "pointer" }} onClick={() => handleAddChild(text)}></i>
:
null
}
</Tooltip>
<Tooltip title="Edit">
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
{
checkActMenup(location.pathname, 'update') ?
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
:
null
}
</Tooltip>
</>
,
@ -511,7 +535,12 @@ const ProjectType = ({ params, ...props }) => {
</Col>
<Col>
<Tooltip title={t('divisionAdd')}>
{
checkActMenup(location.pathname, 'create') ?
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
:
null
}
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>

22
src/views/SimproV2/ProjectType/index.js

@ -11,6 +11,8 @@ import { NotificationContainer, NotificationManager } from 'react-notifications'
import { PROJECT_TYPE_ADD, PROJECT_TYPE_EDIT, PROJECT_TYPE_DELETE, PROJECT_TYPE_SEARCH, COMPANY_MANAGEMENT_LIST } from '../../../const/ApiConst';
import { Pagination, Button, Tooltip, Table } from 'antd';
import { useTranslation } from 'react-i18next';
import { checkActMenup } from '../../../const/CustomFunc.js';
import { useLocation } from "react-router-dom";
const ProjectType = ({ params, ...props }) => {
@ -27,6 +29,7 @@ const ProjectType = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation()
const HEADER = {
headers: {
"Content-Type": "application/json",
@ -353,10 +356,20 @@ const ProjectType = ({ params, ...props }) => {
className: 'nowrap',
render: (text, record) => <>
<Tooltip title={t('delete')}>
<i className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
{
checkActMenup(location.pathname, 'delete') ?
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={t('edit')}>
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
{
checkActMenup(location.pathname, 'update') ?
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i>
:
null
}
</Tooltip>{" "}
<Tooltip title="Template Gantt">
<i className="fa fa-gears" style={{ color: 'blue', cursor: "pointer" }} onClick={() => handleDialogIg(text.id)}></i>
@ -430,7 +443,12 @@ const ProjectType = ({ params, ...props }) => {
</Col>
<Col>
<Tooltip title={t('projectType')}>
{
checkActMenup(location.pathname, 'create') ?
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
:
null
}
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>

190
src/views/SimproV2/ReferralCode/DialogForm.js

@ -0,0 +1,190 @@
import React, { useEffect, useState } from 'react'
import {
Modal, ModalHeader, ModalBody, ModalFooter,
Button, Form, FormGroup, Label, Input, Col, Row
} from 'reactstrap';
import { Select, DatePicker } from 'antd';
import 'antd/dist/antd.css';
import { useTranslation } from 'react-i18next';
import "rc-color-picker/assets/index.css";
import moment from 'moment';
import { formatNumber } from "../../../const/CustomFunc";
const { Option } = Select
const DialogForm = ({ openDialog, closeDialog, toggleDialog, typeDialog, dataEdit }) => {
const [id, setId] = useState(0)
const [code, setCode] = useState('')
const [amount, setAmount] = useState(0)
const [description, setDescription] = useState('')
const [exp, setExp] = useState('')
const [type, setType] = useState('')
const [allocation, setAllocation] = useState(0)
const { t } = useTranslation()
useEffect(() => {
console.log('dataEdit', dataEdit);
if (typeDialog === "Edit") {
setId(dataEdit.id)
setCode(dataEdit.code)
setAmount(dataEdit.amount)
setDescription(dataEdit.description)
setExp(dataEdit.exp)
setType(dataEdit.type)
} else {
handleClear()
}
}, [dataEdit, openDialog])
const validation = () => {
if (!code || code === "") {
alert("Code cannot be empty!");
return true;
}
}
const handleSave = () => {
let data = '';
const err = validation();
if (!err) {
if (typeDialog === "Save") {
data = {
code,
amount,
description,
exp,
type,
allocation
}
console.log('data', data);
closeDialog('save', data);
} else {
data = {
id,
code,
amount,
description,
exp,
type,
allocation
}
console.log('data', data);
closeDialog('edit', data);
}
handleClear()
}
}
const handleCancel = () => {
closeDialog('cancel', 'none')
handleClear()
}
const handleClear = () => {
setId(0)
setCode('')
setAmount('')
setDescription('')
setExp('')
}
const renderForm = () => {
return (
<Form>
<Row>
<Col md={12}>
<span style={{ color: "red" }}>*</span> Wajib diisi.
</Col>
</Row>
<Row>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('code')} {t('discount')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder={t('inputCode')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('amount')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder={t('inputAmount')}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={6}>
<FormGroup>
<Label className="capitalize" style={{ fontWeight: "bold" }}>
{t('exp')}<span style={{ color: "red" }}>*</span>
</Label>
<DatePicker
format={"DD-MM-YYYY HH:mm"}
style={{ width: "100%" }}
value={exp ? moment(exp) : exp}
onChange={(date, dateString) => setExp(date)}
showTime={{ defaultValue: moment('00:00:00', 'HH:mm:ss') }}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('allocation')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="number"
value={allocation}
onChange={(e) => setAllocation(e.target.value)}
placeholder={t('input')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('type')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={type}
onChange={(e) => setType(e.target.value)}
placeholder={t('inputType')}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<FormGroup>
<Label className="capitalize">{t('description')}</Label>
<Input row="4" type="textarea" value={description} onChange={(e) => setDescription(e.target.value)} placeholder={t('inputDescription')} />
</FormGroup>
</Col>
</Row>
</Form>
)
}
return (
<>
<Modal size="lg" isOpen={openDialog} toggle={toggleDialog}>
<ModalHeader className="capitalize" toggle={closeDialog}>{typeDialog == "Save" ? `Add` : "Edit"} {t('Demo')}</ModalHeader>
<ModalBody>
{renderForm()}
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => handleSave()}>{typeDialog}</Button>{' '}
<Button className="capitalize" color="secondary" onClick={() => handleCancel()}>{t('cancel')}</Button>
</ModalFooter>
</Modal>
</>
)
}
export default DialogForm;

372
src/views/SimproV2/ReferralCode/index.js

@ -0,0 +1,372 @@
import * as XLSX from 'xlsx';
import DialogForm from './DialogForm';
import React, { useState, useEffect } from 'react';
import SweetAlert from 'react-bootstrap-sweetalert';
import axios from "../../../const/interceptorApi"
import { Card, CardBody, CardHeader, Col, Row, Input, Table } from 'reactstrap';
import { REFERRAL_CODE_EDIT, REFERRAL_CODE_SEARCH, REFERRAL_CODE_ADD, REFERRAL_CODE_DELETE } from '../../../const/ApiConst';
import { NotificationContainer, NotificationManager } from 'react-notifications';
import { Spin, Button, Tooltip } from 'antd';
import { useTranslation } from 'react-i18next';
import moment from 'moment';
import toRupiah from '@develoka/angka-rupiah-js';
const ProjectType = ({ params, ...props }) => {
let role_id = 0, user_id = 0, isLogin = false, token = '', company_id = 0, all_project = null, role_name = '', hierarchy = [], user_name = '';
if (props && props.role_id && props.user_id) {
role_id = props.role_id;
user_id = props.user_id;
token = props.token;
isLogin = props.isLogin;
company_id = props.company_id;
all_project = props.all_project;
role_name = props.role_name;
isLogin = props.isLogin;
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const HEADER = {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
}
}
const pageName = params.name;
const [alertDelete, setAlertDelete] = useState(false)
const [allDataMenu, setAllDataMenu] = useState([])
const [clickOpenModal, setClickOpenModal] = useState(false)
const [currentPage, setCurrentPage] = useState(1)
const [dataEdit, setDataEdit] = useState([])
const [dataExport, setDataExport] = useState([])
const [dataTable, setDatatable] = useState([])
const [idDelete, setIdDelete] = useState(0)
const [openDialog, setOpenDialog] = useState(false)
const [rowsPerPage, setRowsPerPage] = useState(10)
const [search, setSearch] = useState("")
const [totalPage, setTotalPage] = useState(0)
const [typeDialog, setTypeDialog] = useState('Save')
const [dataDemo, setDataDemo] = useState([])
const [listCompany, setListCompany] = useState([])
const [loading, setLoading] = useState(true);
const { t } = useTranslation()
const column = [
{ name: t('code') },
{ name: t('type') },
{ name: t('amount') },
{ name: t('exp') },
{ name: t('allocation') },
{ name: t('description') },
].filter(column => column && column.name);
useEffect(() => {
getReferralCode();
}, [currentPage, rowsPerPage, search])
useEffect(() => {
const cekData = dataExport || []
if (cekData.length > 0) {
exportExcel()
}
}, [dataExport])
const getReferralCode = async () => {
let start = 0;
if (currentPage !== 1 && currentPage > 1) {
start = currentPage * rowsPerPage - rowsPerPage;
}
const payload = {
group_column: {
"operator": "AND",
"group_operator": "OR",
"where": [
{
"name": "name",
"logic_operator": "~*",
"value": search,
}
]
},
columns: [],
"orders": {
"ascending": true,
"columns": [
'id'
]
},
"paging": {
"length": rowsPerPage,
"start": start
},
'joins': []
}
const result = await axios
.post(REFERRAL_CODE_SEARCH, payload, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code == 200) {
setDatatable(result.data.data);
setTotalPage(result.data.totalRecord);
setLoading(false)
} else {
NotificationManager.error('Gagal Mengambil Data!!', 'Failed');
setLoading(false)
}
}
const handleExportExcel = async () => {
let start = 0;
if (currentPage !== 1 && currentPage > 1) {
start = (currentPage * rowsPerPage) - rowsPerPage
}
const payload = {
group_column: {
"operator": "AND",
"group_operator": "OR",
"where": [
{
"name": "name",
"logic_operator": "~*",
"value": search,
}
]
},
"columns": [],
"orders": {
"ascending": true,
"columns": [
'id'
]
},
"paging": {
"length": rowsPerPage,
"start": start
},
'joins': []
}
const result = await axios
.post(REFERRAL_CODE_SEARCH, payload, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code == 200) {
let resData = result.data.data;
const excelData = [];
resData.map((val, index) => {
let dataRow = {};
dataRow["Kode"] = val.code;
dataRow["Jumlah"] = val.amount;
dataRow["Kuota"] = val.allocation;
dataRow["Kadaluarsa"] = val.exp;
dataRow["Deskripsi"] = val.deskription;
excelData.push(dataRow)
})
await setDataExport(excelData)
} else {
NotificationManager.error('Gagal Export Data!!', 'Failed');
}
}
const exportExcel = () => {
const dataExcel = dataExport || [];
const fileName = `Data ${pageName}.xlsx`;
const ws = XLSX.utils.json_to_sheet(dataExcel);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, `Data ${pageName}`);
XLSX.writeFile(wb, fileName);
setDataExport([])
}
const handleSearch = e => {
const value = e.target.value
setSearch(value);
setCurrentPage(1)
};
const handleOpenDialog = (type) => {
setOpenDialog(true)
setTypeDialog(type)
}
const handleEdit = (data) => {
setDataEdit(data)
handleOpenDialog('Edit');
}
const handleDelete = async (id) => {
await setAlertDelete(true)
await setIdDelete(id)
}
const handleCloseDialog = (type, data) => {
if (type === "save") {
saveReferralCode(data);
} else if (type === "edit") {
editReferralCode(data);
}
setDataEdit([])
setOpenDialog(false)
}
const saveReferralCode = async (data) => {
const formData = data
const result = await axios.post(REFERRAL_CODE_ADD, formData, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code === 200) {
getReferralCode()
NotificationManager.success(`Data berhasil ditambahkan`, 'Success!!');
} else {
NotificationManager.error(`Data gagal ditambahkan`, 'Failed!!');
}
}
const editReferralCode = async (data) => {
let urlEdit = REFERRAL_CODE_EDIT(data.id)
const formData = data
const result = await axios.put(urlEdit, formData, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code === 200) {
getReferralCode();
NotificationManager.success(`Data berhasil diubah`, 'Success!!');
} else {
NotificationManager.error(`Data gagal diubah`, `Failed!!`);
}
}
const toggleAddDialog = () => {
setOpenDialog(!openDialog)
}
const onConfirmDelete = async () => {
let url = REFERRAL_CODE_DELETE(idDelete);
const result = await axios.delete(url, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code === 200) {
getReferralCode()
setIdDelete(0)
setAlertDelete(false)
NotificationManager.success(`Data berhasil dihapus!`, 'Success!!');
} else {
setIdDelete(0)
setAlertDelete(false)
NotificationManager.error(`Data gagal dihapus!}`, 'Failed!!');
}
}
const cancelDelete = () => {
setAlertDelete(false)
setIdDelete(0)
}
const onShowSizeChange = (current, pageSize) => {
setRowsPerPage(pageSize)
}
const onPagination = (current, pageSize) => {
setCurrentPage(current)
}
const dataNotAvailable = () => {
if (dataTable.length === 0) {
return (
<tr>
<td align="center" colSpan="7">{t('noData')}</td>
</tr>
)
}
}
return (
<div>
<NotificationContainer />
<SweetAlert
show={alertDelete}
warning
showCancel
confirmBtnText="Delete"
confirmBtnBsStyle="danger"
title={t('deleteConfirm')}
onConfirm={onConfirmDelete}
onCancel={cancelDelete}
focusCancelBtn
>
{t('deleteMsg')}
</SweetAlert>
<DialogForm
openDialog={openDialog}
closeDialog={handleCloseDialog}
toggleDialog={() => toggleAddDialog}
typeDialog={typeDialog}
dataEdit={dataEdit}
clickOpenModal={clickOpenModal}
dataParent={allDataMenu}
/>
<Card>
<CardHeader style={{ display: "flex", justifyContent: "space-between" }}>
<h4 className="capitalize">{pageName}</h4>
<Row>
<Col>
<Input onChange={handleSearch} value={search} type="text" name="search" id="search" placeholder={t('search')} />
</Col>
<Col>
<Tooltip title={t('demoAdd')}>
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>
</Tooltip>
</Col>
</Row>
</CardHeader>
<CardBody>
<Spin tip="Loading..." spinning={loading}>
<Table responsive striped hover>
<thead>
<tr>
<th>Aksi</th>
{column.map((i, index) => {
return (
<th key={index} scope="row">{i.name}</th>
)
})}
</tr>
</thead>
<tbody>
{dataNotAvailable()}
{dataTable.map((n, index) => {
return (
<tr key={n.id}>
<td className='nowrap'>
<Tooltip title={t('delete')}>
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(n.id)}></i>
</Tooltip>
<Tooltip title={t('edit')}>
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(n)}></i>
</Tooltip>
</td>
<td>{n.code}</td>
<td>{n.type}</td>
<td>{toRupiah(n.amount)}</td>
<td>{n?.exp ? moment(n.exp).format('DD-MM-YYYY HH:mm') : "-"}</td>
<td>{n.allocation ? n.allocation : "-"}</td>
<td>{n.description ? n.description : "-"}</td>
</tr>
)
})}
</tbody>
</Table>
</Spin>
</CardBody>
</Card>
</div>
)
}
export default ProjectType;

36
src/views/SimproV2/ResourceWorker/index.js

@ -8,11 +8,19 @@ import moment from 'moment'
import { Card, CardBody, CardHeader, Col, Row, Input } from 'reactstrap';
import { DownloadOutlined } from '@ant-design/icons';
import { NotificationContainer, NotificationManager } from 'react-notifications';
import { useLocation } from "react-router-dom";
import { Pagination, Table, Button, Tooltip, Spin } from 'antd';
import {
USER_ADD, USER_SEARCH, USER_EDIT, USER_DELETE, ROLE_SEARCH, DIVISI_SEARCH, USER_SHIFT_ADD, COMPANY_MANAGEMENT_LIST
} from '../../../const/ApiConst';
import { useTranslation } from 'react-i18next';
import {
formatNumber,
formatRupiah,
formatThousand,
renderFormatRupiah,
checkActMenup,
} from "../../../const/CustomFunc";
const ResourceWorker = ({ params, ...props }) => {
let role_id = 0, user_id = 0, isLogin = false, token = '', company_id = 0, all_project = null, role_name = '', hierarchy = [], user_name = '';
@ -28,7 +36,7 @@ const ResourceWorker = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation();
const HEADER = {
headers: {
"Content-Type": "application/json",
@ -547,14 +555,29 @@ const ResourceWorker = ({ params, ...props }) => {
key: 'x',
render: (text, record) => <>
<Tooltip title={t('edit')}>
<Button size="small" type="link" style={{ color: 'orange' }} onClick={() => handleEdit(text)}><i className="fa fa-edit"></i></Button>
{
checkActMenup(location.pathname, 'update') ?
<i size="small" type="link" style={{ color: 'green', marginRight: '5px', cursor: "pointer" }} onClick={() => handleEdit(text)} className="fa fa-edit"></i>
:
null
}
</Tooltip>
<Tooltip title={t('delete')}>
<Button size="small" type="link" style={{ color: 'red' }} onClick={() => handleDelete(text.id)}><i className="fa fa-trash"></i></Button>
{
checkActMenup(location.pathname, 'delete') ?
<i size="small" type="link" style={{ color: 'red', marginRight: '5px', cursor: "pointer" }} onClick={() => handleDelete(text.id)} className="fa fa-trash"></i>
:
null
}
</Tooltip>
<Tooltip title="Set User">
<Button size="small" type="link" style={{ color: 'lightblue' }} onClick={() => handleSetWorker(text)}><i className="fa fa-key"></i></Button>
{
checkActMenup(location.pathname, 'update') ?
<i Button size="small" type="link" style={{ color: 'blue', marginRight: '4px', cursor: "pointer" }} onClick={() => handleSetWorker(text)} className="fa fa-key"></i>
:
null
}
</Tooltip>
</>,
},
@ -638,7 +661,12 @@ const ResourceWorker = ({ params, ...props }) => {
</Col>
<Col>
<Tooltip title={t('hradd')}>
{
checkActMenup(location.pathname, 'create') ?
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
:
null
}
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>

201
src/views/SimproV2/SalesContact/DialogForm.js

@ -0,0 +1,201 @@
import React, { useEffect, useState } from 'react'
import {
Modal, ModalHeader, ModalBody, ModalFooter,
Button, Form, FormGroup, Label, Input, Col, Row
} from 'reactstrap';
import { Select } from 'antd';
import 'antd/dist/antd.css';
import { useTranslation } from 'react-i18next';
import "rc-color-picker/assets/index.css";
const { Option } = Select
const DialogForm = ({ openDialog, closeDialog, toggleDialog, typeDialog, dataEdit }) => {
const [id, setId] = useState(0)
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [message, setMessage] = useState('')
const [phoneNumber, setPhoneNumber] = useState('')
const [role, setRole] = useState(null)
const [status, setStatus] = useState('')
const { t } = useTranslation()
useEffect(() => {
if (typeDialog === "Edit") {
setId(dataEdit.id)
setName(dataEdit.name)
setEmail(dataEdit.email)
setMessage(dataEdit.message)
setPhoneNumber(dataEdit.number_phone)
setStatus(dataEdit.status)
setRole(dataEdit.role)
} else {
setId(0)
setName('')
setEmail('')
setMessage('')
setStatus('')
setRole('')
setPhoneNumber('')
}
}, [dataEdit, openDialog])
const validation = () => {
if (!name || name === "") {
alert("Name cannot be empty!");
return true;
}
}
const handleSave = () => {
let data = '';
const err = validation();
if (!err) {
if (typeDialog === "Save") {
data = {
name,
email,
message,
number_phone: phoneNumber,
role,
status
}
closeDialog('save', data);
} else {
data = {
id,
name,
email,
message,
number_phone: phoneNumber,
role,
status
}
closeDialog('edit', data);
}
setId(0)
setName('')
}
}
const handleCancel = () => {
closeDialog('cancel', 'none')
setId(0)
setName('')
}
const onChangeStatus = (val) => {
setStatus(val)
}
const renderForm = () => {
return (
<Form>
<Row>
<Col md={12}>
<span style={{ color: "red" }}>*</span> Wajib diisi.
</Col>
</Row>
<Row>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('name')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t('inputName')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('email')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder={t('inputEmail')}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('phoneNumber')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={phoneNumber}
onChange={(e) => setName(e.target.value)}
placeholder={t('inputNoPhone')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup>
<Label className="capitalize">{t('role')}<span style={{ color: "red" }}>*</span></Label>
<Input
type="text"
value={role}
onChange={(e) => setRole(e.target.value)}
placeholder={t('inputRole')}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<FormGroup>
<Label className="capitalize">
{t('status')}<span style={{ color: "red" }}>*</span>
</Label>
<Select
showSearch
filterOption={(inputValue, option) =>
option.children.toLowerCase().includes(inputValue.toLowerCase())
}
value={status}
defaultValue={status}
onChange={onChangeStatus}
style={{ width: "100%" }}
>
<Option value="New Contact">New Contact</Option>
<Option value="Presentation">Presentation</Option>
<Option value="Live Demo">Live Demo</Option>
<Option value="Trial">Trial</Option>
<Option value="Go-Live">Go-Live</Option>
<Option value="Postpone">Postpone</Option>
</Select>
</FormGroup>
</Col>
</Row>
<Row>
<Col md={12}>
<FormGroup>
<Label className="capitalize">{t('message')}</Label>
<Input row="4" type="textarea" value={message} onChange={(e) => setMessage(e.target.value)} placeholder={t('inputMessage')} />
</FormGroup>
</Col>
</Row>
</Form>
)
}
return (
<>
<Modal size="lg" isOpen={openDialog} toggle={toggleDialog}>
<ModalHeader className="capitalize" toggle={closeDialog}>{typeDialog == "Save" ? `Add` : "Edit"} {t('Demo')}</ModalHeader>
<ModalBody>
{renderForm()}
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => handleSave()}>{typeDialog}</Button>{' '}
<Button className="capitalize" color="secondary" onClick={() => handleCancel()}>{t('cancel')}</Button>
</ModalFooter>
</Modal>
</>
)
}
export default DialogForm;

374
src/views/SimproV2/SalesContact/index.js

@ -0,0 +1,374 @@
import * as XLSX from 'xlsx';
import DialogForm from './DialogForm';
import React, { useState, useEffect } from 'react';
import SweetAlert from 'react-bootstrap-sweetalert';
import axios from "../../../const/interceptorApi"
import { Card, CardBody, CardHeader, Col, Row, Input, Table } from 'reactstrap';
import { SALES_CONTACT_EDIT, SALES_CONTACT_SEARCH, SALES_CONTACT_LIST, SALES_CONTACT_GET_ID, SALES_CONTACT_ADD, SALES_CONTACT_DELETE } from '../../../const/ApiConst';
import { NotificationContainer, NotificationManager } from 'react-notifications';
import { Spin, Button, Tooltip } from 'antd';
import { useTranslation } from 'react-i18next';
const ProjectType = ({ params, ...props }) => {
let role_id = 0, user_id = 0, isLogin = false, token = '', company_id = 0, all_project = null, role_name = '', hierarchy = [], user_name = '';
if (props && props.role_id && props.user_id) {
role_id = props.role_id;
user_id = props.user_id;
token = props.token;
isLogin = props.isLogin;
company_id = props.company_id;
all_project = props.all_project;
role_name = props.role_name;
isLogin = props.isLogin;
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const HEADER = {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
}
}
const pageName = params.name;
const [alertDelete, setAlertDelete] = useState(false)
const [allDataMenu, setAllDataMenu] = useState([])
const [clickOpenModal, setClickOpenModal] = useState(false)
const [currentPage, setCurrentPage] = useState(1)
const [dataEdit, setDataEdit] = useState([])
const [dataExport, setDataExport] = useState([])
const [dataTable, setDatatable] = useState([])
const [idDelete, setIdDelete] = useState(0)
const [openDialog, setOpenDialog] = useState(false)
const [rowsPerPage, setRowsPerPage] = useState(10)
const [search, setSearch] = useState("")
const [totalPage, setTotalPage] = useState(0)
const [typeDialog, setTypeDialog] = useState('Save')
const [dataDemo, setDataDemo] = useState([])
const [listCompany, setListCompany] = useState([])
const [loading, setLoading] = useState(true);
const { t } = useTranslation()
const column = [
{ name: t('name') },
{ name: t('company') },
{ name: t('email') },
{ name: t('roles') },
{ name: t('phoneNumber') },
{ name: t('status') },
{ name: t('description') },
].filter(column => column && column.name);
useEffect(() => {
getDataContactSales();
}, [currentPage, rowsPerPage, search])
useEffect(() => {
const cekData = dataExport || []
if (cekData.length > 0) {
exportExcel()
}
}, [dataExport])
const getDataContactSales = async () => {
let start = 0;
if (currentPage !== 1 && currentPage > 1) {
start = currentPage * rowsPerPage - rowsPerPage;
}
const payload = {
group_column: {
"operator": "AND",
"group_operator": "OR",
"where": [
{
"name": "name",
"logic_operator": "~*",
"value": search,
}
]
},
columns: [],
"orders": {
"ascending": true,
"columns": [
'id'
]
},
"paging": {
"length": rowsPerPage,
"start": start
},
'joins': []
}
const result = await axios
.post(SALES_CONTACT_SEARCH, payload, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code == 200) {
setDatatable(result.data.data);
setTotalPage(result.data.totalRecord);
setLoading(false)
} else {
NotificationManager.error('Gagal Mengambil Data!!', 'Failed');
setLoading(false)
}
}
const handleExportExcel = async () => {
let start = 0;
if (currentPage !== 1 && currentPage > 1) {
start = (currentPage * rowsPerPage) - rowsPerPage
}
const payload = {
group_column: {
"operator": "AND",
"group_operator": "OR",
"where": [
{
"name": "name",
"logic_operator": "~*",
"value": search,
}
]
},
"columns": [],
"orders": {
"ascending": true,
"columns": [
'id'
]
},
"paging": {
"length": rowsPerPage,
"start": start
},
'joins': []
}
const result = await axios
.post(SALES_CONTACT_SEARCH, payload, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code == 200) {
let resData = result.data.data;
const excelData = [];
resData.map((val, index) => {
let dataRow = {};
dataRow["Nama"] = val.name;
dataRow["Email"] = val.email;
dataRow["Role"] = val.role;
dataRow["No Telepon"] = val.number_phone;
dataRow["Status"] = val.message;
dataRow["Message"] = val.message;
excelData.push(dataRow)
})
await setDataExport(excelData)
} else {
NotificationManager.error('Gagal Export Data!!', 'Failed');
}
}
const exportExcel = () => {
const dataExcel = dataExport || [];
const fileName = `Data ${pageName}.xlsx`;
const ws = XLSX.utils.json_to_sheet(dataExcel);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, `Data ${pageName}`);
XLSX.writeFile(wb, fileName);
setDataExport([])
}
const handleSearch = e => {
const value = e.target.value
setSearch(value);
setCurrentPage(1)
};
const handleOpenDialog = (type) => {
setOpenDialog(true)
setTypeDialog(type)
}
const handleEdit = (data) => {
setDataEdit(data)
handleOpenDialog('Edit');
}
const handleDelete = async (id) => {
await setAlertDelete(true)
await setIdDelete(id)
}
const handleCloseDialog = (type, data) => {
if (type === "save") {
saveContactSales(data);
} else if (type === "edit") {
editContactSales(data);
}
setDataEdit([])
setOpenDialog(false)
}
const saveContactSales = async (data) => {
const formData = data
const result = await axios.post(SALES_CONTACT_ADD, formData, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code === 200) {
getDataContactSales()
NotificationManager.success(`Data berhasil ditambahkan`, 'Success!!');
} else {
NotificationManager.error(`Data gagal ditambahkan`, 'Failed!!');
}
}
const editContactSales = async (data) => {
let urlEdit = SALES_CONTACT_EDIT(data.id)
const formData = data
const result = await axios.put(urlEdit, formData, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code === 200) {
getDataContactSales();
NotificationManager.success(`Data berhasil diubah`, 'Success!!');
} else {
NotificationManager.error(`Data gagal diubah`, `Failed!!`);
}
}
const toggleAddDialog = () => {
setOpenDialog(!openDialog)
}
const onConfirmDelete = async () => {
let url = SALES_CONTACT_DELETE(idDelete);
const result = await axios.delete(url, HEADER)
.then(res => res)
.catch((error) => error.response);
if (result && result.data && result.data.code === 200) {
getDataContactSales()
setIdDelete(0)
setAlertDelete(false)
NotificationManager.success(`Data berhasil dihapus!`, 'Success!!');
} else {
setIdDelete(0)
setAlertDelete(false)
NotificationManager.error(`Data gagal dihapus!}`, 'Failed!!');
}
}
const cancelDelete = () => {
setAlertDelete(false)
setIdDelete(0)
}
const onShowSizeChange = (current, pageSize) => {
setRowsPerPage(pageSize)
}
const onPagination = (current, pageSize) => {
setCurrentPage(current)
}
const dataNotAvailable = () => {
if (dataTable.length === 0) {
return (
<tr>
<td align="center" colSpan="8">{t('noData')}</td>
</tr>
)
}
}
return (
<div>
<NotificationContainer />
<SweetAlert
show={alertDelete}
warning
showCancel
confirmBtnText="Delete"
confirmBtnBsStyle="danger"
title={t('deleteConfirm')}
onConfirm={onConfirmDelete}
onCancel={cancelDelete}
focusCancelBtn
>
{t('deleteMsg')}
</SweetAlert>
<DialogForm
openDialog={openDialog}
closeDialog={handleCloseDialog}
toggleDialog={() => toggleAddDialog}
typeDialog={typeDialog}
dataEdit={dataEdit}
clickOpenModal={clickOpenModal}
dataParent={allDataMenu}
/>
<Card>
<CardHeader style={{ display: "flex", justifyContent: "space-between" }}>
<h4 className="capitalize">{pageName}</h4>
<Row>
<Col>
<Input onChange={handleSearch} value={search} type="text" name="search" id="search" placeholder={t('searchDemo')} />
</Col>
<Col>
<Tooltip title={t('demoAdd')}>
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>
</Tooltip>
</Col>
</Row>
</CardHeader>
<CardBody>
<Spin tip="Loading..." spinning={loading}>
<Table responsive striped hover>
<thead>
<tr>
<th>Aksi</th>
{column.map((i, index) => {
return (
<th key={index} scope="row">{i.name}</th>
)
})}
</tr>
</thead>
<tbody>
{dataNotAvailable()}
{dataTable.map((n, index) => {
return (
<tr key={n.id}>
<td className='nowrap'>
<Tooltip title={t('delete')}>
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(n.id)}></i>
</Tooltip>
<Tooltip title={t('edit')}>
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(n)}></i>
</Tooltip>
</td>
<td>{n.name}</td>
<td>{n.company_name}</td>
<td>{n.email}</td>
<td>{n.role}</td>
<td>{n.number_phone}</td>
<td>{n.status}</td>
<td>{n.message}</td>
</tr>
)
})}
</tbody>
</Table>
</Spin>
</CardBody>
</Card>
</div>
)
}
export default ProjectType;

29
src/views/SimproV2/Satuan/index.js

@ -8,6 +8,14 @@ import { NotificationContainer, NotificationManager } from 'react-notifications'
import { Pagination, Button, Tooltip } from 'antd';
import { SATUAN_ADD, SATUAN_EDIT, SATUAN_DELETE, SATUAN_SEARCH, COMPANY_MANAGEMENT_LIST } from '../../../const/ApiConst';
import { useTranslation } from 'react-i18next';
import { useLocation } from "react-router-dom";
import {
formatNumber,
formatRupiah,
formatThousand,
renderFormatRupiah,
checkActMenup,
} from "../../../const/CustomFunc";
const Satuan = ({ params, ...props }) => {
@ -24,7 +32,7 @@ const Satuan = ({ params, ...props }) => {
hierarchy = props.hierarchy;
user_name = props.user_name;
}
const location = useLocation();
const HEADER = {
headers: {
"Content-Type": "application/json",
@ -352,7 +360,12 @@ const Satuan = ({ params, ...props }) => {
</Col>
<Col>
<Tooltip title={t('uomAdd')}>
{
checkActMenup(location.pathname, 'create') ?
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button>
:
null
}
</Tooltip>
<Tooltip title={t('exportExcel')}>
<Button style={{ marginLeft: "5px" }} color="primary" onClick={() => handleExportExcel()}><i className="fa fa-print"></i></Button>
@ -380,11 +393,21 @@ const Satuan = ({ params, ...props }) => {
<td className='nowrap'>
<Tooltip title={t('delete')}>
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(n.id)}></i>
{
checkActMenup(location.pathname, 'delete') ?
<i id="TooltipDelete" className="fa fa-trash" style={{ color: 'red', marginRight: 10, cursor: "pointer" }} onClick={() => handleDelete(n.id)}></i>
:
null
}
</Tooltip>
<Tooltip title={t('ubah')}>
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(n)}></i>
{
checkActMenup(location.pathname, 'update') ?
<i id="TooltipEdit" className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(n)}></i>
:
null
}
</Tooltip>
</td>
{role_name === 'Super Admin' &&

Loading…
Cancel
Save