diff --git a/package.json b/package.json index 964c241..81dbc9c 100644 --- a/package.json +++ b/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", diff --git a/src/components/wj/App.jsx b/src/components/wj/App.jsx new file mode 100644 index 0000000..09b5099 --- /dev/null +++ b/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: [ + , + , + ], + barChart: [ + , + , + , + , + ], + columnChart: [ + , + , + , + , + ], + bubbleChart: [ + , + , + , + ], + lineChart: [ + , + , + , + , + ], + radialGauge: [ + , + , + , + ], + linearGauge: [ + , + , + , + ], + bulletGraph: [ + , + , + , + , + , + , + , + , + , + ], + blank: [ + , + ], + }; + // 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 = (
this.setState({ isWideMenu: !isWideMenu })}> + + + + + +
); + // menu items + const renderMenuItems = ( + {this.tileCatalog.map((item) => (
this.addTile(item.name)}> + + {item.icon.map((entity, key) => ({entity}))} + +
{item.name}
+
))} +
); + // displayed when the dashboard is empty + const renderBlankTile = (
+ + + +
Click on an item on the menu bar to add the new tile to the dashboard.
+
); + // list of tiles + const renderTiles = ( + {tiles.map((item, index) => ())} + ); + const renderDashboard = tiles.length ? renderTiles : renderBlankTile; + return (
+
+ {renderMenuToggle} + {renderMenuItems} +
+
+
+
{renderDashboard}
+
+
); + } +} diff --git a/src/components/wj/components/BarChart.jsx b/src/components/wj/components/BarChart.jsx new file mode 100644 index 0000000..a9dc664 --- /dev/null +++ b/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 }) => ( + + + + + ); diff --git a/src/components/wj/components/Blank.jsx b/src/components/wj/components/Blank.jsx new file mode 100644 index 0000000..28f67ca --- /dev/null +++ b/src/components/wj/components/Blank.jsx @@ -0,0 +1,3 @@ +// React +import * as React from 'react'; +export const Blank = () =>
This is an empty tile.
; diff --git a/src/components/wj/components/BubbleChart.jsx b/src/components/wj/components/BubbleChart.jsx new file mode 100644 index 0000000..ad8f98d --- /dev/null +++ b/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 }) => ( + + + ); diff --git a/src/components/wj/components/BulletGraph.jsx b/src/components/wj/components/BulletGraph.jsx new file mode 100644 index 0000000..ed7af7a --- /dev/null +++ b/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 }) => (
+ + + {data.items.map((item, index) => ( + + + + ))} + +
{wjcCore.Globalize.format(item.date, 'MMM yyyy')} + + + + + +
+
); diff --git a/src/components/wj/components/ColumnChart.jsx b/src/components/wj/components/ColumnChart.jsx new file mode 100644 index 0000000..5d8e495 --- /dev/null +++ b/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 }) => ( + + + + + ); diff --git a/src/components/wj/components/Grid.jsx b/src/components/wj/components/Grid.jsx new file mode 100644 index 0000000..1d0caef --- /dev/null +++ b/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 }) => ( + + + + + + ); diff --git a/src/components/wj/components/LineChart.jsx b/src/components/wj/components/LineChart.jsx new file mode 100644 index 0000000..6f93ab2 --- /dev/null +++ b/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 }) => ( + + + + ); diff --git a/src/components/wj/components/LinearGauge.jsx b/src/components/wj/components/LinearGauge.jsx new file mode 100644 index 0000000..3a14ec3 --- /dev/null +++ b/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 (
+
+

Sales: {wjcCore.Globalize.format(lastItem.sales, 'c')}

+ +
+ +
+

Expenses: {wjcCore.Globalize.format(lastItem.expenses, 'c')}

+ +
+ +
+

Profit: {wjcCore.Globalize.format(lastItem.profit, 'c')}

+ +
+ +

KPIs for {wjcCore.Globalize.format(lastItem.date, 'MMMM yyyy')}

+
); +}; diff --git a/src/components/wj/components/RadialGauge.jsx b/src/components/wj/components/RadialGauge.jsx new file mode 100644 index 0000000..00faeb9 --- /dev/null +++ b/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 ( + +

Profit for {wjcCore.Globalize.format(lastItem.date, 'MMMM yyyy')}

+
); +}; diff --git a/src/components/wj/components/Tile.jsx b/src/components/wj/components/Tile.jsx new file mode 100644 index 0000000..62cdcf5 --- /dev/null +++ b/src/components/wj/components/Tile.jsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +export const Tile = ({ header, content, index, onRemove }) => (
+
+
{header}
+
+
onRemove(index)}> + + + +
+
+
+
{content}
+
); diff --git a/src/components/wj/index.css b/src/components/wj/index.css new file mode 100644 index 0000000..87262fc --- /dev/null +++ b/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; +} diff --git a/src/components/wj/index.jsx b/src/components/wj/index.jsx new file mode 100644 index 0000000..4f7df64 --- /dev/null +++ b/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')); diff --git a/src/components/wj/license.js b/src/components/wj/license.js new file mode 100644 index 0000000..5c66de4 --- /dev/null +++ b/src/components/wj/license.js @@ -0,0 +1,2 @@ +import { setLicenseKey } from '@grapecity/wijmo'; +setLicenseKey('GrapeCity,427351286422477#B0JoIIklkIs4nIzYXMyAjMiojIyVmdiwSZzxWYmpjIyNHZisnOiwmbBJye0ICRiwiI34TQvAVW7ZWbqNjeWZzRh9Ucl5UTuZVaCR7ZpB5UH9EVC3kaqJWZ0pnasJ7Q9I4bB3GR0F6aIt6NZ96MFN4aotEZrUXe4kHUlllerlGb9dDSPhEcFFmclJXd8syNEtENzMTOBNVYpFje6lDUlBlbkdmcNNGOrAHR9pXNSl6NpVkbCNWUxlkZ7o7QT3icHNGavFXdapWQDZ5KsN5N7dDcyRUW85ESFBlb4JUZq3GWlBldXBzU0hjW9wkb8cncopnSOBjNkJFdyNmSqB7YVlnNzpnb9BDSMllTSFDaNFjca54dtBXatlEdS5mVNV6dxIEN7RUNxYGSvU7KIdndPpENvlXW6hzbCh7RsZ7YLJzboNnU7ZERTRkVBNlN7RWSGhDVrdmZqZ4cq94aFpkbWZDMGBHdVZ5YwUTTIdlN0RnVvMDdFJzbQt6bolDM5NkdrsUO536LrNWSotmI0IyUiwiIDRTR4MjM9QjI0ICSiwSMyIjM9UTMwEjM0IicfJye35XX3JSSwIjUiojIDJCLi86bpNnblRHeFBCI4VWZoNFelxmRg2Wbql6ViojIOJyes4nI5kkTRJiOiMkIsIibvl6cuVGd8VEIgIXZ7VWaWRncvBXZSBybtpWaXJiOi8kI1xSfis4N8gkI0IyQiwiIu3Waz9WZ4hXRgAydvJVa4xWdNBybtpWaXJiOi8kI1xSfiQjR6QkI0IyQiwiIu3Waz9WZ4hXRgACUBx4TgAybtpWaXJiOi8kI1xSfiMzQwIkI0IyQiwiIlJ7bDBybtpWaXJiOi8kI1xSfiUFO7EkI0IyQiwiIu3Waz9WZ4hXRgACdyFGaDxWYpNmbh9WaGBybtpWaXJiOi8kI1tlOiQmcQJCLiETMwAzNwACOwcDMwIDMyIiOiQncDJCLi46bj9idlRWe4l6YlBXYydmLqwibj9SbvNmL9RXajVGchJ7ZuoCLt36YukHdpNWZwFmcn9iKsI7au26YukHdpNWZwFmcn9iKsAnau26YukHdpNWZwFmcn9iKiojIz5GRiwiI9RXaDVGchJ7RiojIh94QiwiI7cDNyIDN6gjMxUzMdI6N'); diff --git a/src/components/wj/utils/DragDropTouch.js b/src/components/wj/utils/DragDropTouch.js new file mode 100644 index 0000000..dfcdbe7 --- /dev/null +++ b/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 + * HTML Drag and Drop API. + * + * 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(','); diff --git a/src/const/CustomFunc.js b/src/const/CustomFunc.js index 808b6d4..bf29083 100644 --- a/src/const/CustomFunc.js +++ b/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": { diff --git a/src/routes.js b/src/routes.js index ebe60fe..bc7f34c 100644 --- a/src/routes.js +++ b/src/routes.js @@ -50,6 +50,8 @@ 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')); @@ -62,6 +64,8 @@ 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 }, diff --git a/src/views/Dashboard/DashboardDyna.js b/src/views/Dashboard/DashboardDyna.js new file mode 100644 index 0000000..2443392 --- /dev/null +++ b/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 = ( + + showModal(templateIndex, 0)}> +
+ {widgets ? widgets[templateIndex][0] : null} +
+ +
+ ); + widgetCount = 1; + break; + case 1: + component = ( + + showModal(templateIndex, 0)}> +
+ {widgets ? widgets[templateIndex][0] : null} +
+ showModal(templateIndex, 1)}> +
+ {widgets ? widgets[templateIndex][1] : null} +
+ showModal(templateIndex, 2)}> +
+ {widgets ? widgets[templateIndex][2] : null} +
+ showModal(templateIndex, 3)}> +
+ {widgets ? widgets[templateIndex][3] : null} +
+
+ ); + widgetCount = 4; + break; + case 2: + component = ( + + showModal(templateIndex, 0)}> +
+ {widgets ? widgets[templateIndex][0] : null} +
+ showModal(templateIndex, 1)}> +
+ {widgets ? widgets[templateIndex][1] : null} +
+
+ ); + widgetCount = 2; + break; + case 3: + component = ( + + showModal(templateIndex, 0)}> +
+ {widgets ? widgets[templateIndex][0] : null} +
+ showModal(templateIndex, 1)}> +
+ {widgets ? widgets[templateIndex][1] : null} +
+
+ ); + 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 ( +
+ + +
+
+
+ {templateColSpan === 1 ? + + : + <> +
Template
+ + addTemplate(0)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + + addTemplate(1)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
+ + +
+ + +
+ + + addTemplate(2)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
+ + + addTemplate(3)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
+ + + + + } +
+
+
+ + +
+
+
+
Dashboard
+ {templates.length > 0 ? templates.map((template) => { + return template.component; + }) : null} + +
+
+
+ + + +
+
+
+ {widgetColSpan === 1 ? + + : + <> +
Widget
+ + addWidget(0)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
Pie Chart Project By Role
+ + + addWidget(1)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
Pie Chart Project By Role
+ + + addWidget(2)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
Pie Chart Project By Role
+ + + addWidget(3)} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}> + +
+ + +
Pie Chart Project By Role
+ + + + } +
+
+
+ +
+ + ); +}; + +export default DashboardDyna; diff --git a/src/views/Master/MasterRoles/index.js b/src/views/Master/MasterRoles/index.js index 3ff0663..3d1137d 100644 --- a/src/views/Master/MasterRoles/index.js +++ b/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) => <> - this.handleMenuRoles(text.id)}> + { + checkActMenup('/roles', 'update') ? + this.handleMenuRoles(text.id)}> + : + null + } - this.handleDelete(text.id)}> + { + checkActMenup('/roles', 'delete') ? + this.handleDelete(text.id)}> + : + null + } - this.handleEdit(text)}> + { + checkActMenup('/roles', 'update') ? + this.handleEdit(text)}> + : + null + } , }, @@ -503,10 +519,17 @@ class index extends Component { - + { + checkActMenup('/roles', 'create') ? + + + : + null + } - +
diff --git a/src/views/Master/MenuCompany/index.js b/src/views/Master/MenuCompany/index.js index e3eb03b..e3e71a7 100644 --- a/src/views/Master/MenuCompany/index.js +++ b/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) => <> - handleDelete(text.id)}> + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(text.id)}> + : + null + } - handleEdit(text)}> + { + checkActMenup(location.pathname, 'update') ? + handleEdit(text)}> + : + null + } , }, diff --git a/src/views/Master/ProjectPhase/index.js b/src/views/Master/ProjectPhase/index.js index 9effc9b..708e91a 100644 --- a/src/views/Master/ProjectPhase/index.js +++ b/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) => <> - handleDelete(text.id)}> + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(text.id)}> + : + null + } - handleEdit(text)}> + { + checkActMenup(location.pathname, 'update') ? + handleEdit(text)}> + : + null + } {" "} , }, @@ -429,7 +448,12 @@ const ProjectPhase = ({ params, ...props }) => { + { + checkActMenup(location.pathname, 'create') ? + : + null + } diff --git a/src/views/Master/RoleProject/index.js b/src/views/Master/RoleProject/index.js index d5b095b..4d7bbc5 100644 --- a/src/views/Master/RoleProject/index.js +++ b/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) => <> - this.handleDelete(text.id)}> + { + checkActMenup('/project-role', 'delete') ? + this.handleDelete(text.id)}> + : + null + } - this.handleEdit(text)}> + { + checkActMenup('/project-role', 'update') ? + this.handleEdit(text)}> + : + null + } , }, @@ -124,7 +135,7 @@ class index extends Component { } if (this.state.role_name !== "Super Admin") { formData.columns.push( - { "name": "company_id", "logic_operator": "=", "value": this.state.company_id, "operator": "AND" }, + { "name": "company_id", "logic_operator": "=", "value": parseInt(this.state.company_id), "operator": "AND" }, ) } else { formData.columns.push( @@ -403,7 +414,13 @@ class index extends Component { - + { + checkActMenup('/roles', 'create') ? + + : + null + } diff --git a/src/views/SimproV2/ChecklistK3/index.js b/src/views/SimproV2/ChecklistK3/index.js index e0632b6..758f833 100644 --- a/src/views/SimproV2/ChecklistK3/index.js +++ b/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 }) => { - + { + checkActMenup(location.pathname, 'create') ? + + : + null + } @@ -390,10 +404,20 @@ const ChecklistK3 = ({ params, ...props }) => { - handleDelete(n.id)}> + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(n.id)}> + : + null + } - handleEdit(n)}> + { + checkActMenup(location.pathname, 'edit') ? + handleEdit(n)}> + : + null + } {role_name === 'Super Admin' && diff --git a/src/views/SimproV2/CreatedProyek/index.js b/src/views/SimproV2/CreatedProyek/index.js index 8a25b0e..d9c86b4 100644 --- a/src/views/SimproV2/CreatedProyek/index.js +++ b/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') ? + : + null + } + { + checkActMenup(location.pathname, 'update') ? { )} + : + null + } ), }, @@ -2106,12 +2121,18 @@ const CreatedProyek = ({ params, ...props }) => { {parseInt(role_id) == 44 ? null : ( // role kustomer - + { + checkActMenup(location.pathname, 'create') ? + + : + null + } )} diff --git a/src/views/SimproV2/Divisi/index.js b/src/views/SimproV2/Divisi/index.js index 68b1825..d423c8f 100644 --- a/src/views/SimproV2/Divisi/index.js +++ b/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) => <> - handleDelete(text.id)}> + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(text.id)}> + : + null + } + - handleAddChild(text)}> + { + checkActMenup(location.pathname, 'create') ? + handleAddChild(text)}> + : + null + } - handleEdit(text)}> + { + checkActMenup(location.pathname, 'update') ? + handleEdit(text)}> + : + null + } , @@ -511,7 +535,12 @@ const ProjectType = ({ params, ...props }) => { + { + checkActMenup(location.pathname, 'create') ? + : + null + } diff --git a/src/views/SimproV2/ProjectType/index.js b/src/views/SimproV2/ProjectType/index.js index 5d5b37c..0e23258 100644 --- a/src/views/SimproV2/ProjectType/index.js +++ b/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) => <> - handleDelete(text.id)}> + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(text.id)}> + : + null + } - handleEdit(text)}> + { + checkActMenup(location.pathname, 'update') ? + handleEdit(text)}> + : + null + } {" "} handleDialogIg(text.id)}> @@ -430,7 +443,12 @@ const ProjectType = ({ params, ...props }) => { + { + checkActMenup(location.pathname, 'create') ? + : + null + } diff --git a/src/views/SimproV2/ResourceWorker/index.js b/src/views/SimproV2/ResourceWorker/index.js index e042b07..9219541 100644 --- a/src/views/SimproV2/ResourceWorker/index.js +++ b/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) => <> - + { + checkActMenup(location.pathname, 'update') ? + handleEdit(text)} className="fa fa-edit"> + : + null + } - + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(text.id)} className="fa fa-trash"> + : + null + } - + { + checkActMenup(location.pathname, 'update') ? + handleSetWorker(text)} className="fa fa-key"> + : + null + } , }, @@ -638,7 +661,12 @@ const ResourceWorker = ({ params, ...props }) => { + { + checkActMenup(location.pathname, 'create') ? + : + null + } diff --git a/src/views/SimproV2/Satuan/index.js b/src/views/SimproV2/Satuan/index.js index ab81794..89cfad8 100644 --- a/src/views/SimproV2/Satuan/index.js +++ b/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 }) => { + { + checkActMenup(location.pathname, 'create') ? + : + null + } @@ -380,11 +393,21 @@ const Satuan = ({ params, ...props }) => { - handleDelete(n.id)}> + { + checkActMenup(location.pathname, 'delete') ? + handleDelete(n.id)}> + : + null + } - handleEdit(n)}> + { + checkActMenup(location.pathname, 'update') ? + handleEdit(n)}> + : + null + } {role_name === 'Super Admin' && diff --git a/src/views/SimproV2/Settings/DialogForm.js b/src/views/SimproV2/Settings/DialogForm.js index d968fc7..14ca80f 100644 --- a/src/views/SimproV2/Settings/DialogForm.js +++ b/src/views/SimproV2/Settings/DialogForm.js @@ -185,9 +185,9 @@ const DialogForm = ({ return (
- + - + + + setUserName(e.target.value)} /> + + setEmail(e.target.value)} /> - + setOldPassword(e.target.value)} /> + + setNewPassword(e.target.value)} /> + + setNewPasswordConfirm(e.target.value)} />