devextreme-planit-treegrid-react 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DxPlanitTreeGrid.js +610 -0
- package/dist/index.js +10 -0
- package/dist/type.js +5 -0
- package/package.json +16 -9
- package/dist/DxPlanitTreeGrid.tsx +0 -580
- package/dist/index.tsx +0 -3
- package/dist/type.ts +0 -32
@@ -0,0 +1,610 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.default = void 0;
|
8
|
+
var _react = require("react");
|
9
|
+
var _loadPanel = require("devextreme-react/load-panel");
|
10
|
+
var _pivotGrid = _interopRequireWildcard(require("devextreme-react/pivot-grid"));
|
11
|
+
var _dataGrid = require("devextreme-react/data-grid");
|
12
|
+
var _excel_exporter = require("devextreme/excel_exporter");
|
13
|
+
var _exceljs = require("exceljs");
|
14
|
+
var _fileSaver = _interopRequireDefault(require("file-saver"));
|
15
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
16
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
17
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
18
|
+
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
19
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
20
|
+
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
21
|
+
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
22
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
23
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
24
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
25
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
26
|
+
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0) { ; } } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
|
27
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
28
|
+
/**
|
29
|
+
* devextreme pivotgrid Configrations 중 사용 불가 항목 : id, width, height, showColumnGrandTotals, showColumnTotals, showRowGrandTotals, FieldChooser
|
30
|
+
* devextreme pivotgrid Configrations 중 사용 방법 변경 항목 : stateStoring, Export
|
31
|
+
* onExported, onFileSaving 이벤트 사용하지 않음.
|
32
|
+
*/
|
33
|
+
/**
|
34
|
+
* todoList:
|
35
|
+
* 2) columIndex 초기화 기능이 있어야 함(column 개수 변할 때)
|
36
|
+
* 3) 헤더에 테이블 삽입되면서 그리드 크기가 늘어남. height에 그리드 크기 늘어난 만큼 반영되어야 함.
|
37
|
+
*/
|
38
|
+
|
39
|
+
var grandTotalCssNm = 'data-grand-total';
|
40
|
+
var DxPlanitTreeGrid = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref) {
|
41
|
+
var _props$id = props.id,
|
42
|
+
id = _props$id === void 0 ? 'dx-planit-vera-pivotgrid-id' : _props$id,
|
43
|
+
groupField = props.groupField,
|
44
|
+
dataColor = props.dataColor,
|
45
|
+
_props$convertNullToH = props.convertNullToHipen,
|
46
|
+
convertNullToHipen = _props$convertNullToH === void 0 ? true : _props$convertNullToH,
|
47
|
+
_props$convertZeroToH = props.convertZeroToHipen,
|
48
|
+
convertZeroToHipen = _props$convertZeroToH === void 0 ? true : _props$convertZeroToH,
|
49
|
+
_props$stateStoringKe = props.stateStoringKey,
|
50
|
+
stateStoringKey = _props$stateStoringKe === void 0 ? '' : _props$stateStoringKe,
|
51
|
+
_props$allowExpandAll = props.allowExpandAll,
|
52
|
+
allowExpandAll = _props$allowExpandAll === void 0 ? false : _props$allowExpandAll,
|
53
|
+
_props$allowFiltering = props.allowFiltering,
|
54
|
+
allowFiltering = _props$allowFiltering === void 0 ? false : _props$allowFiltering,
|
55
|
+
_props$allowSorting = props.allowSorting,
|
56
|
+
allowSorting = _props$allowSorting === void 0 ? false : _props$allowSorting,
|
57
|
+
_props$allowSortingBy = props.allowSortingBySummary,
|
58
|
+
allowSortingBySummary = _props$allowSortingBy === void 0 ? false : _props$allowSortingBy,
|
59
|
+
_props$dataFieldArea = props.dataFieldArea,
|
60
|
+
dataFieldArea = _props$dataFieldArea === void 0 ? 'column' : _props$dataFieldArea,
|
61
|
+
dataSource = props.dataSource,
|
62
|
+
_props$disabled = props.disabled,
|
63
|
+
disabled = _props$disabled === void 0 ? false : _props$disabled,
|
64
|
+
elementAttr = props.elementAttr,
|
65
|
+
encodeHtml = props.encodeHtml,
|
66
|
+
_props$hideEmptySumma = props.hideEmptySummaryCells,
|
67
|
+
hideEmptySummaryCells = _props$hideEmptySumma === void 0 ? false : _props$hideEmptySumma,
|
68
|
+
hint = props.hint,
|
69
|
+
_props$rowHeaderLayou = props.rowHeaderLayout,
|
70
|
+
rowHeaderLayout = _props$rowHeaderLayou === void 0 ? 'standard' : _props$rowHeaderLayou,
|
71
|
+
_props$rtlEnabled = props.rtlEnabled,
|
72
|
+
rtlEnabled = _props$rtlEnabled === void 0 ? false : _props$rtlEnabled,
|
73
|
+
_props$showBorders = props.showBorders,
|
74
|
+
showBorders = _props$showBorders === void 0 ? true : _props$showBorders,
|
75
|
+
_props$showRowTotals = props.showRowTotals,
|
76
|
+
showRowTotals = _props$showRowTotals === void 0 ? true : _props$showRowTotals,
|
77
|
+
_props$showTotalsPrio = props.showTotalsPrior,
|
78
|
+
showTotalsPrior = _props$showTotalsPrio === void 0 ? 'none' : _props$showTotalsPrio,
|
79
|
+
_props$tabIndex = props.tabIndex,
|
80
|
+
tabIndex = _props$tabIndex === void 0 ? 0 : _props$tabIndex,
|
81
|
+
_props$visible = props.visible,
|
82
|
+
visible = _props$visible === void 0 ? true : _props$visible,
|
83
|
+
_props$wordWrapEnable = props.wordWrapEnabled,
|
84
|
+
wordWrapEnabled = _props$wordWrapEnable === void 0 ? false : _props$wordWrapEnable,
|
85
|
+
_props$customExcelBut = props.customExcelButton,
|
86
|
+
customExcelButton = _props$customExcelBut === void 0 ? false : _props$customExcelBut,
|
87
|
+
onCellClick = props.onCellClick,
|
88
|
+
onCellPrepared = props.onCellPrepared,
|
89
|
+
onContentReady = props.onContentReady,
|
90
|
+
onContextMenuPreparing = props.onContextMenuPreparing,
|
91
|
+
onDisposing = props.onDisposing,
|
92
|
+
onExporting = props.onExporting,
|
93
|
+
onInitialized = props.onInitialized,
|
94
|
+
onOptionChanged = props.onOptionChanged;
|
95
|
+
var _useState = (0, _react.useState)(0),
|
96
|
+
_useState2 = _slicedToArray(_useState, 2),
|
97
|
+
width = _useState2[0],
|
98
|
+
setWidth = _useState2[1];
|
99
|
+
var _useState3 = (0, _react.useState)(0),
|
100
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
101
|
+
height = _useState4[0],
|
102
|
+
setHeight = _useState4[1];
|
103
|
+
var _useState5 = (0, _react.useState)(0),
|
104
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
105
|
+
columnIndex = _useState6[0],
|
106
|
+
setColumnIndex = _useState6[1];
|
107
|
+
var _useState7 = (0, _react.useState)(dataSource),
|
108
|
+
_useState8 = _slicedToArray(_useState7, 2),
|
109
|
+
gridDataSource = _useState8[0],
|
110
|
+
setGridDataSource = _useState8[1];
|
111
|
+
var $tableRef = (0, _react.useRef)(null);
|
112
|
+
var excelBorder = {
|
113
|
+
style: 'thin',
|
114
|
+
color: {
|
115
|
+
argb: 'FF7E7E7E'
|
116
|
+
}
|
117
|
+
};
|
118
|
+
(0, _react.useImperativeHandle)(ref, function () {
|
119
|
+
return {
|
120
|
+
exportToExcel: exportToExcel
|
121
|
+
};
|
122
|
+
});
|
123
|
+
|
124
|
+
/**
|
125
|
+
* 그리드 사이즈 재조정
|
126
|
+
* @returns 그리드 사이즈
|
127
|
+
*/
|
128
|
+
var getGridSize = function getGridSize() {
|
129
|
+
var _wrapper$clientWidth;
|
130
|
+
var wrapper = document.querySelector('.diag-table-wrapper');
|
131
|
+
var gap = 10;
|
132
|
+
setWidth((_wrapper$clientWidth = wrapper === null || wrapper === void 0 ? void 0 : wrapper.clientWidth) !== null && _wrapper$clientWidth !== void 0 ? _wrapper$clientWidth : 0);
|
133
|
+
setHeight(wrapper ? wrapper.clientHeight - gap : 0);
|
134
|
+
window.addEventListener('resize', function () {
|
135
|
+
var _wrapper$clientWidth2;
|
136
|
+
setWidth((_wrapper$clientWidth2 = wrapper === null || wrapper === void 0 ? void 0 : wrapper.clientWidth) !== null && _wrapper$clientWidth2 !== void 0 ? _wrapper$clientWidth2 : 0);
|
137
|
+
setHeight(wrapper ? wrapper.clientHeight - gap : 0);
|
138
|
+
});
|
139
|
+
return {
|
140
|
+
width: width,
|
141
|
+
height: height
|
142
|
+
};
|
143
|
+
};
|
144
|
+
|
145
|
+
/**
|
146
|
+
* 'Total' 을 한글로 변경
|
147
|
+
* @param e devextreme CellPreparedEvent
|
148
|
+
*/
|
149
|
+
var changeTotalText = function changeTotalText(e) {
|
150
|
+
var _e$cell;
|
151
|
+
if (!e.cellElement) {
|
152
|
+
return;
|
153
|
+
}
|
154
|
+
if (((_e$cell = e.cell) === null || _e$cell === void 0 ? void 0 : _e$cell.type) === 'T') {
|
155
|
+
var _e$cell$text;
|
156
|
+
var text = (_e$cell$text = e.cell.text) === null || _e$cell$text === void 0 ? void 0 : _e$cell$text.replace('Total', '합계');
|
157
|
+
e.cellElement.innerHTML = "<span>".concat(text, "</span>");
|
158
|
+
}
|
159
|
+
};
|
160
|
+
|
161
|
+
/**
|
162
|
+
* null값을 하이픈으로 모두 변경
|
163
|
+
* @param e devextreme CellPreparedEvent
|
164
|
+
*/
|
165
|
+
var changeNullToHipen = function changeNullToHipen(e) {
|
166
|
+
var _e$cell2;
|
167
|
+
if (!convertNullToHipen) {
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
if (e.area === 'data' && ((_e$cell2 = e.cell) === null || _e$cell2 === void 0 ? void 0 : _e$cell2.text) === null && e.cellElement) {
|
171
|
+
e.cellElement.innerHTML = '<span class="text-color">-</span>';
|
172
|
+
}
|
173
|
+
};
|
174
|
+
|
175
|
+
/**
|
176
|
+
* '0', '0.0%' 를 하이픈으로 모두 변경
|
177
|
+
* @param e devextreme CellPreparedEvent
|
178
|
+
*/
|
179
|
+
var changeZeroToHipen = function changeZeroToHipen(e) {
|
180
|
+
var _e$cell3, _e$cell4, _e$cell5;
|
181
|
+
if (!convertZeroToHipen) {
|
182
|
+
return;
|
183
|
+
}
|
184
|
+
if (e.area === 'data' && (((_e$cell3 = e.cell) === null || _e$cell3 === void 0 ? void 0 : _e$cell3.text) === '0' || ((_e$cell4 = e.cell) === null || _e$cell4 === void 0 ? void 0 : _e$cell4.text) === '0.0%' || ((_e$cell5 = e.cell) === null || _e$cell5 === void 0 ? void 0 : _e$cell5.text) === '') && e.cellElement) {
|
185
|
+
e.cellElement.innerHTML = '<span class="text-color">-</span>';
|
186
|
+
}
|
187
|
+
};
|
188
|
+
|
189
|
+
/**
|
190
|
+
* 테이블 헤더에 colspan, rowspan 한 HTMLElement 정보 반환
|
191
|
+
* @param groupField 사용자가 작성한 그룹 정보
|
192
|
+
* @return
|
193
|
+
*/
|
194
|
+
var makeColspan = function makeColspan(group, index, isLast) {
|
195
|
+
var td = document.createElement('td');
|
196
|
+
var text = group.groupCaption;
|
197
|
+
if (group.depth === 1) {
|
198
|
+
text = "".concat(group.groupCaption);
|
199
|
+
}
|
200
|
+
td.setAttribute('colspan', group.colspan.toString());
|
201
|
+
td.setAttribute('class', 'dx-row-total dx-grand-total dx-planit-colspan');
|
202
|
+
if (isLast && index === 0) {
|
203
|
+
td.setAttribute('style', 'border-bottom: 0; border-right: 0');
|
204
|
+
} else if (isLast && index !== 0) {
|
205
|
+
td.setAttribute('style', 'border-right: 0');
|
206
|
+
} else if (!isLast && index === 0) {
|
207
|
+
td.setAttribute('style', 'border-bottom: 0');
|
208
|
+
}
|
209
|
+
td.innerHTML = "<div>".concat(text, "</div>");
|
210
|
+
return td;
|
211
|
+
};
|
212
|
+
|
213
|
+
/**
|
214
|
+
* 그룹 필드 데이터 유효성 검증용 데이터 생성
|
215
|
+
* @param groupField
|
216
|
+
* @returns
|
217
|
+
*/
|
218
|
+
var makeCheckGroupData = function makeCheckGroupData(groupField) {
|
219
|
+
var data = {};
|
220
|
+
groupField === null || groupField === void 0 ? void 0 : groupField.forEach(function (group) {
|
221
|
+
if (data[group.depth]) {
|
222
|
+
data[group.depth] += group.colspan;
|
223
|
+
} else {
|
224
|
+
data[group.depth] = group.colspan;
|
225
|
+
}
|
226
|
+
});
|
227
|
+
return data;
|
228
|
+
};
|
229
|
+
|
230
|
+
/**
|
231
|
+
* GroupField 데이터 검증
|
232
|
+
* @param 사용자가 설정한 그룹 필드 정보
|
233
|
+
* @returns 데이터 검증 결과
|
234
|
+
*/
|
235
|
+
var isCheckGroupField = function isCheckGroupField(groupField) {
|
236
|
+
var map = makeCheckGroupData(groupField);
|
237
|
+
for (var _i2 = 0, _Object$keys = Object.keys(map); _i2 < _Object$keys.length; _i2++) {
|
238
|
+
var depth = _Object$keys[_i2];
|
239
|
+
if (map[depth] !== columnIndex + 1) {
|
240
|
+
console.error('그룹 데이터의 children 숫자가 columnIndex와 맞지 않습니다. 다시 한 번 확인 바랍니다.');
|
241
|
+
}
|
242
|
+
}
|
243
|
+
return true;
|
244
|
+
};
|
245
|
+
|
246
|
+
/**
|
247
|
+
* Grand Total 셀 정보 저장
|
248
|
+
* @param e
|
249
|
+
*/
|
250
|
+
var setTotalElementInfo = function setTotalElementInfo(e) {
|
251
|
+
var _e$cell6, _e$cell7, _e$cellElement;
|
252
|
+
if (!(groupField !== null && groupField !== void 0 && groupField.length) || ((_e$cell6 = e.cell) === null || _e$cell6 === void 0 ? void 0 : _e$cell6.type) !== 'GT' || ((_e$cell7 = e.cell) === null || _e$cell7 === void 0 ? void 0 : _e$cell7.text) !== 'Grand Total') {
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
(_e$cellElement = e.cellElement) === null || _e$cellElement === void 0 ? void 0 : _e$cellElement.classList.add(grandTotalCssNm);
|
256
|
+
};
|
257
|
+
|
258
|
+
/**
|
259
|
+
* cell의 columnIndex 최대값 저장
|
260
|
+
* @param e
|
261
|
+
*/
|
262
|
+
var setMaxColumIndex = function setMaxColumIndex(e) {
|
263
|
+
if (!e.columnIndex) {
|
264
|
+
return;
|
265
|
+
}
|
266
|
+
if (e.columnIndex > columnIndex) {
|
267
|
+
setColumnIndex(e.columnIndex);
|
268
|
+
}
|
269
|
+
};
|
270
|
+
|
271
|
+
/**
|
272
|
+
* groupField depth의 유니크한 배열 구하기
|
273
|
+
* @param group
|
274
|
+
* @param arr
|
275
|
+
* @returns
|
276
|
+
*/
|
277
|
+
var getGroupDepth = function getGroupDepth(group, arr) {
|
278
|
+
var groupData = group.slice();
|
279
|
+
var set = new Set(groupData.map(function (group) {
|
280
|
+
return group.depth;
|
281
|
+
}));
|
282
|
+
return Array.from(set).sort(function compare(a, b) {
|
283
|
+
if (a > b) {
|
284
|
+
return arr === 'asc' ? -1 : 1;
|
285
|
+
}
|
286
|
+
if (a < b) {
|
287
|
+
return arr === 'asc' ? 1 : -1;
|
288
|
+
}
|
289
|
+
return 0;
|
290
|
+
});
|
291
|
+
};
|
292
|
+
|
293
|
+
/**
|
294
|
+
* 현재 depth에 맞는 그룹 필드 정보 반환
|
295
|
+
* @param group
|
296
|
+
* @param depth
|
297
|
+
* @returns
|
298
|
+
*/
|
299
|
+
var getCurrentGroup = function getCurrentGroup(group, depth) {
|
300
|
+
return group.filter(function (gr) {
|
301
|
+
return gr.depth === depth;
|
302
|
+
});
|
303
|
+
};
|
304
|
+
|
305
|
+
/**
|
306
|
+
* 테이블 헤더(DOM)에 colspan 적용된 테이블 삽입
|
307
|
+
*/
|
308
|
+
var insertRowHeaderGroup = function insertRowHeaderGroup() {
|
309
|
+
var _thead$previousSiblin;
|
310
|
+
if (!(groupField !== null && groupField !== void 0 && groupField.length)) {
|
311
|
+
return;
|
312
|
+
}
|
313
|
+
isCheckGroupField(groupField);
|
314
|
+
var totalElement = document.querySelector('.' + grandTotalCssNm);
|
315
|
+
var targetElement = totalElement === null || totalElement === void 0 ? void 0 : totalElement.parentNode;
|
316
|
+
var thead = targetElement === null || targetElement === void 0 ? void 0 : targetElement.parentNode;
|
317
|
+
if (!targetElement || !thead) {
|
318
|
+
return;
|
319
|
+
}
|
320
|
+
var firstChild = thead === null || thead === void 0 ? void 0 : thead.firstChild;
|
321
|
+
if (!firstChild) {
|
322
|
+
return;
|
323
|
+
}
|
324
|
+
totalElement.innerHTML = '';
|
325
|
+
totalElement.setAttribute('style', 'padding: 0; border: 0');
|
326
|
+
var colgroup = (_thead$previousSiblin = thead.previousSibling) === null || _thead$previousSiblin === void 0 ? void 0 : _thead$previousSiblin.cloneNode(true);
|
327
|
+
var groupData = groupField.slice();
|
328
|
+
var depth = getGroupDepth(groupData, 'asc');
|
329
|
+
var table = document.createElement('table');
|
330
|
+
depth.forEach(function (dep, index) {
|
331
|
+
var groupInfo = getCurrentGroup(groupData, dep);
|
332
|
+
var tr = document.createElement('tr');
|
333
|
+
groupInfo.forEach(function (group, cellIndex) {
|
334
|
+
var isLast = cellIndex === groupInfo.length - 1 ? true : false;
|
335
|
+
tr.appendChild(makeColspan(group, index, isLast));
|
336
|
+
});
|
337
|
+
table.prepend(tr);
|
338
|
+
});
|
339
|
+
table.prepend(colgroup);
|
340
|
+
totalElement.appendChild(table);
|
341
|
+
};
|
342
|
+
|
343
|
+
/**
|
344
|
+
* Devextreme의 dateController columnInfo에 그룹 정보 삽입
|
345
|
+
* @param group
|
346
|
+
* @returns
|
347
|
+
*/
|
348
|
+
var makeDataControllerColumnGroup = function makeDataControllerColumnGroup(group) {
|
349
|
+
var groupData = group.slice();
|
350
|
+
var depth = getGroupDepth(groupData, 'desc');
|
351
|
+
return depth.map(function (dep) {
|
352
|
+
var groupInfo = getCurrentGroup(groupData, dep);
|
353
|
+
return groupInfo.map(function (group) {
|
354
|
+
return {
|
355
|
+
colspan: group.colspan,
|
356
|
+
text: group.groupCaption,
|
357
|
+
type: 'GT'
|
358
|
+
};
|
359
|
+
});
|
360
|
+
});
|
361
|
+
};
|
362
|
+
|
363
|
+
/**
|
364
|
+
* 사용자가 입력한 컬러 조건을 { standard: string; condition: string } 형식으로 변경 반환
|
365
|
+
* @param condition 사용자 입력 컬러 조건식 ex) '>= 100'
|
366
|
+
* @returns
|
367
|
+
*/
|
368
|
+
var makeSplitCondtion = function makeSplitCondtion(condition) {
|
369
|
+
var newCondition = {
|
370
|
+
standard: '',
|
371
|
+
condition: ''
|
372
|
+
};
|
373
|
+
_toConsumableArray(condition).forEach(function (cond) {
|
374
|
+
if (Number.isNaN(parseFloat(cond))) {
|
375
|
+
newCondition.condition += cond;
|
376
|
+
} else {
|
377
|
+
newCondition.standard += cond;
|
378
|
+
}
|
379
|
+
});
|
380
|
+
return newCondition;
|
381
|
+
};
|
382
|
+
|
383
|
+
/**
|
384
|
+
* 데이터에 색상 적용
|
385
|
+
* @param e onCellPrepared 이벤트
|
386
|
+
* @returns
|
387
|
+
*/
|
388
|
+
var makeColorAtPercent = function makeColorAtPercent(e) {
|
389
|
+
if (!dataColor || !e.cellElement) {
|
390
|
+
return;
|
391
|
+
}
|
392
|
+
dataColor.forEach(function (color) {
|
393
|
+
var _e$cell8, _e$cell8$format;
|
394
|
+
if (e.cell.value === null) {
|
395
|
+
return;
|
396
|
+
}
|
397
|
+
if (((_e$cell8 = e.cell) === null || _e$cell8 === void 0 ? void 0 : (_e$cell8$format = _e$cell8.format) === null || _e$cell8$format === void 0 ? void 0 : _e$cell8$format.type) === color.format && !Number.isNaN(e.cell.value)) {
|
398
|
+
var standardData = makeSplitCondtion(color.condition.replace(/(\s*)/g, ''));
|
399
|
+
var rate = color.format === 'percent' ? 0.01 : 1;
|
400
|
+
var condition = false;
|
401
|
+
switch (standardData.condition) {
|
402
|
+
case '>':
|
403
|
+
condition = e.cell.value > parseFloat(standardData.standard) * rate;
|
404
|
+
break;
|
405
|
+
case '>=':
|
406
|
+
condition = e.cell.value >= parseFloat(standardData.standard) * rate;
|
407
|
+
break;
|
408
|
+
case '<':
|
409
|
+
condition = e.cell.value < parseFloat(standardData.standard) * rate;
|
410
|
+
break;
|
411
|
+
case '<=':
|
412
|
+
condition = e.cell.value <= parseFloat(standardData.standard) * rate;
|
413
|
+
break;
|
414
|
+
}
|
415
|
+
if (condition && !(e.cell.value === 0 && convertZeroToHipen)) {
|
416
|
+
e.cellElement.style.color = color.color;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
});
|
420
|
+
};
|
421
|
+
|
422
|
+
/**
|
423
|
+
* 그리드 데이터 정합성 체크. 데이터 잘못되어 있으면 에러 발생
|
424
|
+
* @param dataSource
|
425
|
+
*/
|
426
|
+
var checkDataSource = function checkDataSource(dataSource) {
|
427
|
+
var isColumns = dataSource._fields.findIndex(function (field) {
|
428
|
+
return field.area === 'column';
|
429
|
+
});
|
430
|
+
var isRows = dataSource._fields.findIndex(function (field) {
|
431
|
+
return field.area === 'row';
|
432
|
+
});
|
433
|
+
var isDatas = dataSource._fields.findIndex(function (field) {
|
434
|
+
return field.area === 'data';
|
435
|
+
});
|
436
|
+
if (isColumns > -1) {
|
437
|
+
throw Error('DxPlanitTreeGrid는 column이 존재하는 형식의 pivot grid에는 사용할 수 없습니다.');
|
438
|
+
}
|
439
|
+
if (isRows === -1 || isDatas === -1) {
|
440
|
+
throw Error('DxPlanitTreeGrid 데이터는 row와 data가 반드시 존재해야 합니다.');
|
441
|
+
}
|
442
|
+
};
|
443
|
+
|
444
|
+
/**
|
445
|
+
* 그리드 펼침 정보 세션스토리지 리셋
|
446
|
+
*/
|
447
|
+
var resetSession = function resetSession() {
|
448
|
+
sessionStorage.removeItem('dx-vera-pivotgrid-storing');
|
449
|
+
};
|
450
|
+
|
451
|
+
/**
|
452
|
+
* 엑셀 export 명령
|
453
|
+
* @param fileName 저장하고자 하는 엑셀파일명
|
454
|
+
*/
|
455
|
+
var exportToExcel = function exportToExcel(fileName) {
|
456
|
+
setTimeout(function () {
|
457
|
+
var _$tableRef$current;
|
458
|
+
return exportToExcelAction((_$tableRef$current = $tableRef.current) === null || _$tableRef$current === void 0 ? void 0 : _$tableRef$current.instance, fileName);
|
459
|
+
});
|
460
|
+
};
|
461
|
+
|
462
|
+
/**
|
463
|
+
* devextreme component 정보의 dataController의 columnInfo에 사용자가 설정한 groupFIled 정보 병합
|
464
|
+
* @param component devextreme component
|
465
|
+
* @returns devextreme component
|
466
|
+
*/
|
467
|
+
var convertDataControllerColumnsInfo = function convertDataControllerColumnsInfo(component) {
|
468
|
+
var arr = [];
|
469
|
+
var columnInfo = component._dataController._columnsInfo.forEach(function (column) {
|
470
|
+
var newColumn = column.slice();
|
471
|
+
if (groupField && newColumn.length === 1 && newColumn[0].type === 'GT' && newColumn[0].text === 'Grand Total') {
|
472
|
+
arr.push.apply(arr, _toConsumableArray(makeDataControllerColumnGroup(groupField)));
|
473
|
+
} else {
|
474
|
+
arr.push(newColumn);
|
475
|
+
}
|
476
|
+
});
|
477
|
+
component._dataController._columnsInfo = arr;
|
478
|
+
return component;
|
479
|
+
};
|
480
|
+
|
481
|
+
/**
|
482
|
+
* 엑셀 export
|
483
|
+
* @param e
|
484
|
+
*/
|
485
|
+
var exportToExcelAction = function exportToExcelAction(e, fileName) {
|
486
|
+
var newComponent = convertDataControllerColumnsInfo(e);
|
487
|
+
var workbook = new _exceljs.Workbook();
|
488
|
+
var worksheet = workbook.addWorksheet(fileName);
|
489
|
+
(0, _excel_exporter.exportPivotGrid)({
|
490
|
+
component: newComponent,
|
491
|
+
worksheet: worksheet,
|
492
|
+
customizeCell: function customizeCell(_ref) {
|
493
|
+
var excelCell = _ref.excelCell;
|
494
|
+
var borderStyle = excelBorder;
|
495
|
+
excelCell.border = {
|
496
|
+
bottom: borderStyle,
|
497
|
+
left: borderStyle,
|
498
|
+
right: borderStyle,
|
499
|
+
top: borderStyle
|
500
|
+
};
|
501
|
+
}
|
502
|
+
}).then(function () {
|
503
|
+
workbook.xlsx.writeBuffer().then(function (buffer) {
|
504
|
+
(0, _fileSaver.default)(new Blob([buffer], {
|
505
|
+
type: 'application/octet-stream'
|
506
|
+
}), fileName + '.xlsx');
|
507
|
+
});
|
508
|
+
});
|
509
|
+
e.cancel = true;
|
510
|
+
};
|
511
|
+
|
512
|
+
/**
|
513
|
+
* devextreme CellPreparedEvent 이벤트 실행
|
514
|
+
* @param e
|
515
|
+
*/
|
516
|
+
var onCellPreparedChild = function onCellPreparedChild(e) {
|
517
|
+
makeColorAtPercent(e);
|
518
|
+
setTotalElementInfo(e);
|
519
|
+
setMaxColumIndex(e);
|
520
|
+
changeTotalText(e);
|
521
|
+
changeNullToHipen(e);
|
522
|
+
changeZeroToHipen(e);
|
523
|
+
return onCellPrepared ? onCellPrepared(e) : undefined;
|
524
|
+
};
|
525
|
+
|
526
|
+
/**
|
527
|
+
* devextreme Raise Event
|
528
|
+
*/
|
529
|
+
var onContentReadyChild = function onContentReadyChild(e) {
|
530
|
+
setTimeout(function () {
|
531
|
+
return insertRowHeaderGroup();
|
532
|
+
}, 0);
|
533
|
+
getGridSize();
|
534
|
+
return onContentReady ? onContentReady(e) : undefined;
|
535
|
+
};
|
536
|
+
var onCellClickChild = function onCellClickChild(e) {
|
537
|
+
return onCellClick ? onCellClick(e) : undefined;
|
538
|
+
};
|
539
|
+
var onContextMenuPreparingChild = function onContextMenuPreparingChild(e) {
|
540
|
+
return onContextMenuPreparing ? onContextMenuPreparing(e) : undefined;
|
541
|
+
};
|
542
|
+
var onDisposingChild = function onDisposingChild(e) {
|
543
|
+
return onDisposing ? onDisposing(e) : undefined;
|
544
|
+
};
|
545
|
+
var onExportingChild = function onExportingChild(e) {
|
546
|
+
return onExporting ? onExporting(e) : undefined;
|
547
|
+
};
|
548
|
+
var onInitializedChild = function onInitializedChild(e) {
|
549
|
+
return onInitialized ? onInitialized(e) : undefined;
|
550
|
+
};
|
551
|
+
var onOptionChangedChild = function onOptionChangedChild(e) {
|
552
|
+
return onOptionChanged ? onOptionChanged(e) : undefined;
|
553
|
+
};
|
554
|
+
(0, _react.useEffect)(function () {
|
555
|
+
if (customExcelButton) {}
|
556
|
+
}, [customExcelButton]);
|
557
|
+
(0, _react.useEffect)(function () {
|
558
|
+
setGridDataSource(dataSource);
|
559
|
+
checkDataSource(dataSource);
|
560
|
+
resetSession();
|
561
|
+
}, [dataSource]);
|
562
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(_loadPanel.LoadPanel, {
|
563
|
+
position: {
|
564
|
+
of: id
|
565
|
+
}
|
566
|
+
}), /*#__PURE__*/React.createElement(_pivotGrid.default, {
|
567
|
+
id: id,
|
568
|
+
ref: $tableRef,
|
569
|
+
dataSource: gridDataSource,
|
570
|
+
showColumnTotals: false,
|
571
|
+
showColumnGrandTotals: true,
|
572
|
+
showRowGrandTotals: false,
|
573
|
+
width: width,
|
574
|
+
height: height,
|
575
|
+
allowExpandAll: allowExpandAll,
|
576
|
+
allowFiltering: allowFiltering,
|
577
|
+
allowSorting: allowSorting,
|
578
|
+
allowSortingBySummary: allowSortingBySummary,
|
579
|
+
dataFieldArea: dataFieldArea,
|
580
|
+
disabled: disabled,
|
581
|
+
elementAttr: elementAttr,
|
582
|
+
encodeHtml: encodeHtml,
|
583
|
+
hideEmptySummaryCells: hideEmptySummaryCells,
|
584
|
+
hint: hint,
|
585
|
+
rowHeaderLayout: rowHeaderLayout,
|
586
|
+
rtlEnabled: rtlEnabled,
|
587
|
+
showBorders: showBorders,
|
588
|
+
showRowTotals: showRowTotals,
|
589
|
+
showTotalsPrior: showTotalsPrior,
|
590
|
+
tabIndex: tabIndex,
|
591
|
+
visible: visible,
|
592
|
+
wordWrapEnabled: wordWrapEnabled,
|
593
|
+
onCellClick: onCellClickChild,
|
594
|
+
onContentReady: onContentReadyChild,
|
595
|
+
onCellPrepared: onCellPreparedChild,
|
596
|
+
onContextMenuPreparing: onContextMenuPreparingChild,
|
597
|
+
onDisposing: onDisposingChild,
|
598
|
+
onExporting: onExportingChild,
|
599
|
+
onInitialized: onInitializedChild,
|
600
|
+
onOptionChanged: onOptionChangedChild
|
601
|
+
}, /*#__PURE__*/React.createElement(_dataGrid.StateStoring, {
|
602
|
+
enabled: stateStoringKey === null || stateStoringKey === void 0 ? void 0 : stateStoringKey.length,
|
603
|
+
type: "sessionStorage",
|
604
|
+
storageKey: stateStoringKey
|
605
|
+
}), /*#__PURE__*/React.createElement(_pivotGrid.FieldChooser, {
|
606
|
+
enabled: false
|
607
|
+
})));
|
608
|
+
});
|
609
|
+
var _default = DxPlanitTreeGrid;
|
610
|
+
exports.default = _default;
|
package/dist/index.js
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.default = void 0;
|
7
|
+
var _DxPlanitTreeGrid = _interopRequireDefault(require("./DxPlanitTreeGrid"));
|
8
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
9
|
+
var _default = _DxPlanitTreeGrid.default;
|
10
|
+
exports.default = _default;
|
package/dist/type.js
ADDED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "devextreme-planit-treegrid-react",
|
3
|
-
"version": "0.2.
|
3
|
+
"version": "0.2.3",
|
4
4
|
"description": "Devextreme의 DxPivotGrid를 Tree Grid처럼 보여주는 Wrapper입니다.",
|
5
5
|
"main": "dist/index.tsx",
|
6
6
|
"module": "dist/index.tsx",
|
@@ -20,7 +20,7 @@
|
|
20
20
|
"test": "react-scripts test",
|
21
21
|
"eject": "react-scripts eject",
|
22
22
|
"clean": "rimraf dist",
|
23
|
-
"compile": "
|
23
|
+
"compile": "npm run clean && cross-env NODE_ENV=production babel src/lib --out-dir dist --copy-files --extensions .ts,.tsx"
|
24
24
|
},
|
25
25
|
"babel": {
|
26
26
|
"presets": [
|
@@ -39,25 +39,30 @@
|
|
39
39
|
"not op_mini all"
|
40
40
|
],
|
41
41
|
"dependencies": {
|
42
|
-
"@babel/polyfill": "^7.12.1",
|
43
42
|
"devextreme": "^22.1.6",
|
44
43
|
"devextreme-planit-treegrid-react": "^0.2.1",
|
45
44
|
"devextreme-react": "^22.1.6",
|
46
45
|
"exceljs": "^4.3.0",
|
47
46
|
"file-saver": "^2.0.5",
|
48
|
-
"react": "^18.2.0",
|
49
|
-
"react-dom": "^18.2.0",
|
50
|
-
"react-router-dom": "^6.4.3",
|
51
|
-
"react-scripts": "^5.0.1",
|
52
47
|
"typescript": "^4.9.3",
|
53
48
|
"uuid": "^9.0.0",
|
54
49
|
"web-vitals": "^2.1.4"
|
55
50
|
},
|
51
|
+
"peerDependencies": {
|
52
|
+
"react": "^18.2.0",
|
53
|
+
"react-dom": "^18.2.0",
|
54
|
+
"react-router-dom": "^6.4.3",
|
55
|
+
"react-scripts": "^5.0.1"
|
56
|
+
},
|
56
57
|
"devDependencies": {
|
57
|
-
"@babel/cli": "^7.
|
58
|
+
"@babel/cli": "^7.0.0",
|
58
59
|
"@babel/core": "^7.20.5",
|
60
|
+
"@babel/node": "^7.20.5",
|
61
|
+
"@babel/polyfill": "^7.12.1",
|
59
62
|
"@babel/preset-env": "^7.20.2",
|
60
63
|
"@babel/preset-react": "^7.18.6",
|
64
|
+
"@babel/preset-typescript": "^7.18.6",
|
65
|
+
"@babel/register": "^7.18.9",
|
61
66
|
"@testing-library/jest-dom": "^5.16.5",
|
62
67
|
"@testing-library/react": "^13.4.0",
|
63
68
|
"@testing-library/user-event": "^13.5.0",
|
@@ -65,7 +70,9 @@
|
|
65
70
|
"@types/react": "^18.0.25",
|
66
71
|
"@types/react-dom": "^18.0.9",
|
67
72
|
"@types/uuid": "^8.3.4",
|
68
|
-
"babel-
|
73
|
+
"babel-core": "^7.0.0-bridge.0",
|
74
|
+
"babel-loader": "^8.0.0-beta.6",
|
75
|
+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
69
76
|
"cross-env": "^7.0.3",
|
70
77
|
"css-loader": "^6.7.2",
|
71
78
|
"eslint": "^8.27.0",
|
@@ -1,580 +0,0 @@
|
|
1
|
-
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
2
|
-
|
3
|
-
import { LoadPanel } from 'devextreme-react/load-panel';
|
4
|
-
|
5
|
-
import PivotGrid, { FieldChooser } from 'devextreme-react/pivot-grid';
|
6
|
-
import { StateStoring } from 'devextreme-react/data-grid';
|
7
|
-
import DevExpress from 'devextreme';
|
8
|
-
import { ColumnField, IColorInfo, IGroupField, Props } from './type';
|
9
|
-
import { exportPivotGrid } from 'devextreme/excel_exporter';
|
10
|
-
import { Workbook } from 'exceljs';
|
11
|
-
import saveAs from 'file-saver';
|
12
|
-
import PivotGridDataSource from 'devextreme/ui/pivot_grid/data_source';
|
13
|
-
|
14
|
-
/**
|
15
|
-
* devextreme pivotgrid Configrations 중 사용 불가 항목 : id, width, height, showColumnGrandTotals, showColumnTotals, showRowGrandTotals, FieldChooser
|
16
|
-
* devextreme pivotgrid Configrations 중 사용 방법 변경 항목 : stateStoring, Export
|
17
|
-
* onExported, onFileSaving 이벤트 사용하지 않음.
|
18
|
-
*/
|
19
|
-
/**
|
20
|
-
* todoList:
|
21
|
-
* 2) columIndex 초기화 기능이 있어야 함(column 개수 변할 때)
|
22
|
-
* 3) 헤더에 테이블 삽입되면서 그리드 크기가 늘어남. height에 그리드 크기 늘어난 만큼 반영되어야 함.
|
23
|
-
*/
|
24
|
-
|
25
|
-
const grandTotalCssNm = 'data-grand-total';
|
26
|
-
|
27
|
-
const DxPlanitTreeGrid = forwardRef(
|
28
|
-
(props: Props, ref: any): JSX.Element => {
|
29
|
-
const {
|
30
|
-
id = 'dx-planit-vera-pivotgrid-id',
|
31
|
-
groupField,
|
32
|
-
dataColor,
|
33
|
-
convertNullToHipen = true,
|
34
|
-
convertZeroToHipen = true,
|
35
|
-
stateStoringKey = '',
|
36
|
-
allowExpandAll = false,
|
37
|
-
allowFiltering = false,
|
38
|
-
allowSorting = false,
|
39
|
-
allowSortingBySummary = false,
|
40
|
-
dataFieldArea = 'column',
|
41
|
-
dataSource,
|
42
|
-
disabled = false,
|
43
|
-
elementAttr,
|
44
|
-
encodeHtml,
|
45
|
-
hideEmptySummaryCells = false,
|
46
|
-
hint,
|
47
|
-
rowHeaderLayout = 'standard',
|
48
|
-
rtlEnabled = false,
|
49
|
-
showBorders = true,
|
50
|
-
showRowTotals = true,
|
51
|
-
showTotalsPrior = 'none',
|
52
|
-
tabIndex = 0,
|
53
|
-
visible = true,
|
54
|
-
wordWrapEnabled = false,
|
55
|
-
customExcelButton = false,
|
56
|
-
onCellClick,
|
57
|
-
onCellPrepared,
|
58
|
-
onContentReady,
|
59
|
-
onContextMenuPreparing,
|
60
|
-
onDisposing,
|
61
|
-
onExporting,
|
62
|
-
onInitialized,
|
63
|
-
onOptionChanged,
|
64
|
-
} = props;
|
65
|
-
|
66
|
-
const [width, setWidth] = useState(0);
|
67
|
-
const [height, setHeight] = useState(0);
|
68
|
-
const [columnIndex, setColumnIndex] = useState(0);
|
69
|
-
const [gridDataSource, setGridDataSource] = useState<PivotGridDataSource>(dataSource);
|
70
|
-
|
71
|
-
const $tableRef = useRef<PivotGrid>(null);
|
72
|
-
const excelBorder = { style: 'thin', color: { argb: 'FF7E7E7E' } };
|
73
|
-
|
74
|
-
useImperativeHandle(ref, () => ({
|
75
|
-
exportToExcel,
|
76
|
-
}));
|
77
|
-
|
78
|
-
/**
|
79
|
-
* 그리드 사이즈 재조정
|
80
|
-
* @returns 그리드 사이즈
|
81
|
-
*/
|
82
|
-
const getGridSize = (): { width: number; height: number } => {
|
83
|
-
const wrapper = document.querySelector('.diag-table-wrapper');
|
84
|
-
const gap = 10;
|
85
|
-
setWidth(wrapper?.clientWidth ?? 0);
|
86
|
-
setHeight(wrapper ? wrapper.clientHeight - gap : 0);
|
87
|
-
|
88
|
-
window.addEventListener('resize', () => {
|
89
|
-
setWidth(wrapper?.clientWidth ?? 0);
|
90
|
-
setHeight(wrapper ? wrapper.clientHeight - gap : 0);
|
91
|
-
});
|
92
|
-
return { width, height };
|
93
|
-
};
|
94
|
-
|
95
|
-
/**
|
96
|
-
* 'Total' 을 한글로 변경
|
97
|
-
* @param e devextreme CellPreparedEvent
|
98
|
-
*/
|
99
|
-
const changeTotalText = (e: DevExpress.ui.dxPivotGrid.CellPreparedEvent): void => {
|
100
|
-
if (!e.cellElement) {
|
101
|
-
return;
|
102
|
-
}
|
103
|
-
if (e.cell?.type === 'T') {
|
104
|
-
const text = e.cell.text?.replace('Total', '합계');
|
105
|
-
e.cellElement.innerHTML = `<span>${text}</span>`;
|
106
|
-
}
|
107
|
-
};
|
108
|
-
|
109
|
-
/**
|
110
|
-
* null값을 하이픈으로 모두 변경
|
111
|
-
* @param e devextreme CellPreparedEvent
|
112
|
-
*/
|
113
|
-
const changeNullToHipen = (e: DevExpress.ui.dxPivotGrid.CellPreparedEvent): void => {
|
114
|
-
if (!convertNullToHipen) {
|
115
|
-
return;
|
116
|
-
}
|
117
|
-
if (e.area === 'data' && e.cell?.text === null && e.cellElement) {
|
118
|
-
e.cellElement.innerHTML = '<span class="text-color">-</span>';
|
119
|
-
}
|
120
|
-
};
|
121
|
-
|
122
|
-
/**
|
123
|
-
* '0', '0.0%' 를 하이픈으로 모두 변경
|
124
|
-
* @param e devextreme CellPreparedEvent
|
125
|
-
*/
|
126
|
-
const changeZeroToHipen = (e: DevExpress.ui.dxPivotGrid.CellPreparedEvent): void => {
|
127
|
-
if (!convertZeroToHipen) {
|
128
|
-
return;
|
129
|
-
}
|
130
|
-
if (e.area === 'data' && (e.cell?.text === '0' || e.cell?.text === '0.0%' || e.cell?.text === '') && e.cellElement) {
|
131
|
-
e.cellElement.innerHTML = '<span class="text-color">-</span>';
|
132
|
-
}
|
133
|
-
};
|
134
|
-
|
135
|
-
/**
|
136
|
-
* 테이블 헤더에 colspan, rowspan 한 HTMLElement 정보 반환
|
137
|
-
* @param groupField 사용자가 작성한 그룹 정보
|
138
|
-
* @return
|
139
|
-
*/
|
140
|
-
const makeColspan = (group: IGroupField, index: number, isLast: boolean): HTMLElement => {
|
141
|
-
const td = document.createElement('td');
|
142
|
-
let text = group.groupCaption;
|
143
|
-
|
144
|
-
if (group.depth === 1) {
|
145
|
-
text = `${group.groupCaption}`;
|
146
|
-
}
|
147
|
-
|
148
|
-
td.setAttribute('colspan', group.colspan.toString());
|
149
|
-
td.setAttribute('class', 'dx-row-total dx-grand-total dx-planit-colspan');
|
150
|
-
|
151
|
-
if (isLast && index === 0) {
|
152
|
-
td.setAttribute('style', 'border-bottom: 0; border-right: 0');
|
153
|
-
} else if (isLast && index !== 0) {
|
154
|
-
td.setAttribute('style', 'border-right: 0');
|
155
|
-
} else if (!isLast && index === 0) {
|
156
|
-
td.setAttribute('style', 'border-bottom: 0');
|
157
|
-
}
|
158
|
-
td.innerHTML = `<div>${text}</div>`;
|
159
|
-
|
160
|
-
return td;
|
161
|
-
};
|
162
|
-
|
163
|
-
/**
|
164
|
-
* 그룹 필드 데이터 유효성 검증용 데이터 생성
|
165
|
-
* @param groupField
|
166
|
-
* @returns
|
167
|
-
*/
|
168
|
-
const makeCheckGroupData = (groupField: IGroupField[]): any => {
|
169
|
-
const data: any = {};
|
170
|
-
|
171
|
-
groupField?.forEach((group: IGroupField) => {
|
172
|
-
if (data[group.depth]) {
|
173
|
-
data[group.depth] += group.colspan;
|
174
|
-
} else {
|
175
|
-
data[group.depth] = group.colspan;
|
176
|
-
}
|
177
|
-
});
|
178
|
-
|
179
|
-
return data;
|
180
|
-
};
|
181
|
-
|
182
|
-
/**
|
183
|
-
* GroupField 데이터 검증
|
184
|
-
* @param 사용자가 설정한 그룹 필드 정보
|
185
|
-
* @returns 데이터 검증 결과
|
186
|
-
*/
|
187
|
-
const isCheckGroupField = (groupField: IGroupField[]): boolean => {
|
188
|
-
const map = makeCheckGroupData(groupField);
|
189
|
-
|
190
|
-
for (const depth of Object.keys(map)) {
|
191
|
-
if (map[depth] !== columnIndex + 1) {
|
192
|
-
console.error('그룹 데이터의 children 숫자가 columnIndex와 맞지 않습니다. 다시 한 번 확인 바랍니다.');
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
|
-
return true;
|
197
|
-
};
|
198
|
-
|
199
|
-
/**
|
200
|
-
* Grand Total 셀 정보 저장
|
201
|
-
* @param e
|
202
|
-
*/
|
203
|
-
const setTotalElementInfo = (e: DevExpress.ui.dxPivotGrid.CellPreparedEvent): void => {
|
204
|
-
if (!groupField?.length || e.cell?.type !== 'GT' || e.cell?.text !== 'Grand Total') {
|
205
|
-
return;
|
206
|
-
}
|
207
|
-
|
208
|
-
e.cellElement?.classList.add(grandTotalCssNm);
|
209
|
-
};
|
210
|
-
|
211
|
-
/**
|
212
|
-
* cell의 columnIndex 최대값 저장
|
213
|
-
* @param e
|
214
|
-
*/
|
215
|
-
const setMaxColumIndex = (e: DevExpress.ui.dxPivotGrid.CellPreparedEvent): void => {
|
216
|
-
if (!e.columnIndex) {
|
217
|
-
return;
|
218
|
-
}
|
219
|
-
if (e.columnIndex > columnIndex) {
|
220
|
-
setColumnIndex(e.columnIndex);
|
221
|
-
}
|
222
|
-
};
|
223
|
-
|
224
|
-
/**
|
225
|
-
* groupField depth의 유니크한 배열 구하기
|
226
|
-
* @param group
|
227
|
-
* @param arr
|
228
|
-
* @returns
|
229
|
-
*/
|
230
|
-
const getGroupDepth = (group: IGroupField[], arr: 'asc' | 'desc'): number[] => {
|
231
|
-
const groupData = group.slice();
|
232
|
-
const set = new Set(groupData.map((group: IGroupField) => group.depth));
|
233
|
-
return Array.from(set).sort(function compare(a: number, b: number) {
|
234
|
-
if (a > b) {
|
235
|
-
return arr === 'asc' ? -1 : 1;
|
236
|
-
}
|
237
|
-
if (a < b) {
|
238
|
-
return arr === 'asc' ? 1 : -1;
|
239
|
-
}
|
240
|
-
return 0;
|
241
|
-
});
|
242
|
-
};
|
243
|
-
|
244
|
-
/**
|
245
|
-
* 현재 depth에 맞는 그룹 필드 정보 반환
|
246
|
-
* @param group
|
247
|
-
* @param depth
|
248
|
-
* @returns
|
249
|
-
*/
|
250
|
-
const getCurrentGroup = (group: IGroupField[], depth: number): IGroupField[] => {
|
251
|
-
return group.filter((gr: IGroupField) => gr.depth === depth);
|
252
|
-
};
|
253
|
-
|
254
|
-
/**
|
255
|
-
* 테이블 헤더(DOM)에 colspan 적용된 테이블 삽입
|
256
|
-
*/
|
257
|
-
const insertRowHeaderGroup = (): void => {
|
258
|
-
if (!groupField?.length) {
|
259
|
-
return;
|
260
|
-
}
|
261
|
-
|
262
|
-
isCheckGroupField(groupField);
|
263
|
-
|
264
|
-
const totalElement = document.querySelector('.' + grandTotalCssNm);
|
265
|
-
const targetElement = totalElement?.parentNode;
|
266
|
-
const thead = targetElement?.parentNode;
|
267
|
-
|
268
|
-
if (!targetElement || !thead) {
|
269
|
-
return;
|
270
|
-
}
|
271
|
-
|
272
|
-
const firstChild = thead?.firstChild;
|
273
|
-
if (!firstChild) {
|
274
|
-
return;
|
275
|
-
}
|
276
|
-
totalElement.innerHTML = '';
|
277
|
-
totalElement.setAttribute('style', 'padding: 0; border: 0');
|
278
|
-
const colgroup = thead.previousSibling?.cloneNode(true);
|
279
|
-
|
280
|
-
const groupData = groupField.slice();
|
281
|
-
const depth = getGroupDepth(groupData, 'asc');
|
282
|
-
|
283
|
-
const table = document.createElement('table');
|
284
|
-
|
285
|
-
depth.forEach((dep: number, index: number) => {
|
286
|
-
const groupInfo = getCurrentGroup(groupData, dep);
|
287
|
-
|
288
|
-
const tr = document.createElement('tr');
|
289
|
-
|
290
|
-
groupInfo.forEach((group: IGroupField, cellIndex: number) => {
|
291
|
-
const isLast = cellIndex === groupInfo.length - 1 ? true : false;
|
292
|
-
tr.appendChild(makeColspan(group, index, isLast));
|
293
|
-
});
|
294
|
-
(table as HTMLElement).prepend(tr);
|
295
|
-
});
|
296
|
-
|
297
|
-
table.prepend(colgroup as Node);
|
298
|
-
totalElement.appendChild(table);
|
299
|
-
};
|
300
|
-
|
301
|
-
/**
|
302
|
-
* Devextreme의 dateController columnInfo에 그룹 정보 삽입
|
303
|
-
* @param group
|
304
|
-
* @returns
|
305
|
-
*/
|
306
|
-
const makeDataControllerColumnGroup = (group: IGroupField[]): ColumnField[][] => {
|
307
|
-
const groupData = group.slice();
|
308
|
-
const depth = getGroupDepth(groupData, 'desc');
|
309
|
-
|
310
|
-
return depth.map((dep: number) => {
|
311
|
-
const groupInfo = getCurrentGroup(groupData, dep);
|
312
|
-
return groupInfo.map((group: IGroupField) => ({ colspan: group.colspan, text: group.groupCaption, type: 'GT' }));
|
313
|
-
});
|
314
|
-
};
|
315
|
-
|
316
|
-
/**
|
317
|
-
* 사용자가 입력한 컬러 조건을 { standard: string; condition: string } 형식으로 변경 반환
|
318
|
-
* @param condition 사용자 입력 컬러 조건식 ex) '>= 100'
|
319
|
-
* @returns
|
320
|
-
*/
|
321
|
-
const makeSplitCondtion = (condition: string): { standard: string; condition: string } => {
|
322
|
-
const newCondition = { standard: '', condition: '' };
|
323
|
-
[...condition].forEach((cond: string) => {
|
324
|
-
if (Number.isNaN(parseFloat(cond))) {
|
325
|
-
newCondition.condition += cond;
|
326
|
-
} else {
|
327
|
-
newCondition.standard += cond;
|
328
|
-
}
|
329
|
-
});
|
330
|
-
|
331
|
-
return newCondition;
|
332
|
-
};
|
333
|
-
|
334
|
-
/**
|
335
|
-
* 데이터에 색상 적용
|
336
|
-
* @param e onCellPrepared 이벤트
|
337
|
-
* @returns
|
338
|
-
*/
|
339
|
-
const makeColorAtPercent = (e: any): void => {
|
340
|
-
if (!dataColor || !e.cellElement) {
|
341
|
-
return;
|
342
|
-
}
|
343
|
-
|
344
|
-
dataColor.forEach((color: IColorInfo) => {
|
345
|
-
if (e.cell.value === null) {
|
346
|
-
return;
|
347
|
-
}
|
348
|
-
if (e.cell?.format?.type === color.format && !Number.isNaN(e.cell.value)) {
|
349
|
-
const standardData = makeSplitCondtion(color.condition.replace(/(\s*)/g, ''));
|
350
|
-
const rate = color.format === 'percent' ? 0.01 : 1;
|
351
|
-
let condition = false;
|
352
|
-
|
353
|
-
switch (standardData.condition) {
|
354
|
-
case '>':
|
355
|
-
condition = e.cell.value > parseFloat(standardData.standard) * rate;
|
356
|
-
break;
|
357
|
-
case '>=':
|
358
|
-
condition = e.cell.value >= parseFloat(standardData.standard) * rate;
|
359
|
-
break;
|
360
|
-
case '<':
|
361
|
-
condition = e.cell.value < parseFloat(standardData.standard) * rate;
|
362
|
-
break;
|
363
|
-
case '<=':
|
364
|
-
condition = e.cell.value <= parseFloat(standardData.standard) * rate;
|
365
|
-
break;
|
366
|
-
}
|
367
|
-
|
368
|
-
if (condition && !(e.cell.value === 0 && convertZeroToHipen)) {
|
369
|
-
e.cellElement.style.color = color.color;
|
370
|
-
}
|
371
|
-
}
|
372
|
-
});
|
373
|
-
};
|
374
|
-
|
375
|
-
/**
|
376
|
-
* 그리드 데이터 정합성 체크. 데이터 잘못되어 있으면 에러 발생
|
377
|
-
* @param dataSource
|
378
|
-
*/
|
379
|
-
const checkDataSource = (dataSource: any): void => {
|
380
|
-
const isColumns = dataSource._fields.findIndex((field: any) => field.area === 'column');
|
381
|
-
const isRows = dataSource._fields.findIndex((field: any) => field.area === 'row');
|
382
|
-
const isDatas = dataSource._fields.findIndex((field: any) => field.area === 'data');
|
383
|
-
|
384
|
-
if (isColumns > -1) {
|
385
|
-
throw Error('DxPlanitTreeGrid는 column이 존재하는 형식의 pivot grid에는 사용할 수 없습니다.');
|
386
|
-
}
|
387
|
-
|
388
|
-
if (isRows === -1 || isDatas === -1) {
|
389
|
-
throw Error('DxPlanitTreeGrid 데이터는 row와 data가 반드시 존재해야 합니다.');
|
390
|
-
}
|
391
|
-
};
|
392
|
-
|
393
|
-
/**
|
394
|
-
* 그리드 펼침 정보 세션스토리지 리셋
|
395
|
-
*/
|
396
|
-
const resetSession = (): void => {
|
397
|
-
sessionStorage.removeItem('dx-vera-pivotgrid-storing');
|
398
|
-
};
|
399
|
-
|
400
|
-
/**
|
401
|
-
* 엑셀 export 명령
|
402
|
-
* @param fileName 저장하고자 하는 엑셀파일명
|
403
|
-
*/
|
404
|
-
const exportToExcel = (fileName: string): void => {
|
405
|
-
setTimeout(() => exportToExcelAction($tableRef.current?.instance, fileName));
|
406
|
-
};
|
407
|
-
|
408
|
-
/**
|
409
|
-
* devextreme component 정보의 dataController의 columnInfo에 사용자가 설정한 groupFIled 정보 병합
|
410
|
-
* @param component devextreme component
|
411
|
-
* @returns devextreme component
|
412
|
-
*/
|
413
|
-
const convertDataControllerColumnsInfo = (component: any): any => {
|
414
|
-
let arr: ColumnField[][] = [];
|
415
|
-
const columnInfo = component._dataController._columnsInfo.forEach((column: ColumnField[]) => {
|
416
|
-
let newColumn = column.slice();
|
417
|
-
if (groupField && newColumn.length === 1 && newColumn[0].type === 'GT' && newColumn[0].text === 'Grand Total') {
|
418
|
-
arr.push(...makeDataControllerColumnGroup(groupField));
|
419
|
-
} else {
|
420
|
-
arr.push(newColumn);
|
421
|
-
}
|
422
|
-
});
|
423
|
-
component._dataController._columnsInfo = arr;
|
424
|
-
return component;
|
425
|
-
};
|
426
|
-
|
427
|
-
/**
|
428
|
-
* 엑셀 export
|
429
|
-
* @param e
|
430
|
-
*/
|
431
|
-
const exportToExcelAction = (e: any, fileName: string): void => {
|
432
|
-
const newComponent = convertDataControllerColumnsInfo(e);
|
433
|
-
|
434
|
-
const workbook = new Workbook();
|
435
|
-
const worksheet = workbook.addWorksheet(fileName);
|
436
|
-
|
437
|
-
exportPivotGrid({
|
438
|
-
component: newComponent,
|
439
|
-
worksheet,
|
440
|
-
customizeCell: ({ excelCell }) => {
|
441
|
-
const borderStyle = excelBorder;
|
442
|
-
excelCell.border = {
|
443
|
-
bottom: borderStyle,
|
444
|
-
left: borderStyle,
|
445
|
-
right: borderStyle,
|
446
|
-
top: borderStyle,
|
447
|
-
};
|
448
|
-
},
|
449
|
-
}).then(() => {
|
450
|
-
workbook.xlsx.writeBuffer().then(buffer => {
|
451
|
-
saveAs(new Blob([buffer], { type: 'application/octet-stream' }), fileName + '.xlsx');
|
452
|
-
});
|
453
|
-
});
|
454
|
-
e.cancel = true;
|
455
|
-
};
|
456
|
-
|
457
|
-
/**
|
458
|
-
* devextreme CellPreparedEvent 이벤트 실행
|
459
|
-
* @param e
|
460
|
-
*/
|
461
|
-
const onCellPreparedChild = (
|
462
|
-
e: DevExpress.ui.dxPivotGrid.CellPreparedEvent
|
463
|
-
): ((e: DevExpress.ui.dxPivotGrid.CellPreparedEvent) => void) | void => {
|
464
|
-
makeColorAtPercent(e);
|
465
|
-
setTotalElementInfo(e);
|
466
|
-
setMaxColumIndex(e);
|
467
|
-
changeTotalText(e);
|
468
|
-
changeNullToHipen(e);
|
469
|
-
changeZeroToHipen(e);
|
470
|
-
|
471
|
-
return onCellPrepared ? onCellPrepared(e) : undefined;
|
472
|
-
};
|
473
|
-
|
474
|
-
/**
|
475
|
-
* devextreme Raise Event
|
476
|
-
*/
|
477
|
-
const onContentReadyChild = (
|
478
|
-
e: DevExpress.ui.dxPivotGrid.ContentReadyEvent
|
479
|
-
): ((e: DevExpress.ui.dxPivotGrid.ContentReadyEvent) => void) | void => {
|
480
|
-
setTimeout(() => insertRowHeaderGroup(), 0);
|
481
|
-
getGridSize();
|
482
|
-
|
483
|
-
return onContentReady ? onContentReady(e) : undefined;
|
484
|
-
};
|
485
|
-
|
486
|
-
const onCellClickChild = (
|
487
|
-
e: DevExpress.ui.dxPivotGrid.CellClickEvent
|
488
|
-
): ((e: DevExpress.ui.dxPivotGrid.CellClickEvent) => void) | void => {
|
489
|
-
return onCellClick ? onCellClick(e) : undefined;
|
490
|
-
};
|
491
|
-
|
492
|
-
const onContextMenuPreparingChild = (
|
493
|
-
e: DevExpress.ui.dxPivotGrid.ContextMenuPreparingEvent
|
494
|
-
): ((e: DevExpress.ui.dxPivotGrid.ContextMenuPreparingEvent) => void) | void => {
|
495
|
-
return onContextMenuPreparing ? onContextMenuPreparing(e) : undefined;
|
496
|
-
};
|
497
|
-
|
498
|
-
const onDisposingChild = (
|
499
|
-
e: DevExpress.ui.dxPivotGrid.DisposingEvent
|
500
|
-
): ((e: DevExpress.ui.dxPivotGrid.DisposingEvent) => void) | void => {
|
501
|
-
return onDisposing ? onDisposing(e) : undefined;
|
502
|
-
};
|
503
|
-
|
504
|
-
const onExportingChild = (
|
505
|
-
e: DevExpress.ui.dxPivotGrid.ExportingEvent
|
506
|
-
): ((e: DevExpress.ui.dxPivotGrid.ExportingEvent) => void) | void => {
|
507
|
-
return onExporting ? onExporting(e) : undefined;
|
508
|
-
};
|
509
|
-
|
510
|
-
const onInitializedChild = (
|
511
|
-
e: DevExpress.ui.dxPivotGrid.InitializedEvent
|
512
|
-
): ((e: DevExpress.ui.dxPivotGrid.InitializedEvent) => void) | void => {
|
513
|
-
return onInitialized ? onInitialized(e) : undefined;
|
514
|
-
};
|
515
|
-
|
516
|
-
const onOptionChangedChild = (
|
517
|
-
e: DevExpress.ui.dxPivotGrid.OptionChangedEvent
|
518
|
-
): ((e: DevExpress.ui.dxPivotGrid.OptionChangedEvent) => void) | void => {
|
519
|
-
return onOptionChanged ? onOptionChanged(e) : undefined;
|
520
|
-
};
|
521
|
-
|
522
|
-
useEffect(() => {
|
523
|
-
if (customExcelButton) {
|
524
|
-
}
|
525
|
-
}, [customExcelButton]);
|
526
|
-
|
527
|
-
useEffect(() => {
|
528
|
-
setGridDataSource(dataSource);
|
529
|
-
checkDataSource(dataSource);
|
530
|
-
resetSession();
|
531
|
-
}, [dataSource]);
|
532
|
-
|
533
|
-
return (
|
534
|
-
<div>
|
535
|
-
<LoadPanel position={{ of: id }} />
|
536
|
-
<PivotGrid
|
537
|
-
id={id}
|
538
|
-
ref={$tableRef}
|
539
|
-
dataSource={gridDataSource}
|
540
|
-
showColumnTotals={false}
|
541
|
-
showColumnGrandTotals={true}
|
542
|
-
showRowGrandTotals={false}
|
543
|
-
width={width}
|
544
|
-
height={height}
|
545
|
-
allowExpandAll={allowExpandAll}
|
546
|
-
allowFiltering={allowFiltering}
|
547
|
-
allowSorting={allowSorting}
|
548
|
-
allowSortingBySummary={allowSortingBySummary}
|
549
|
-
dataFieldArea={dataFieldArea}
|
550
|
-
disabled={disabled}
|
551
|
-
elementAttr={elementAttr}
|
552
|
-
encodeHtml={encodeHtml}
|
553
|
-
hideEmptySummaryCells={hideEmptySummaryCells}
|
554
|
-
hint={hint}
|
555
|
-
rowHeaderLayout={rowHeaderLayout}
|
556
|
-
rtlEnabled={rtlEnabled}
|
557
|
-
showBorders={showBorders}
|
558
|
-
showRowTotals={showRowTotals}
|
559
|
-
showTotalsPrior={showTotalsPrior}
|
560
|
-
tabIndex={tabIndex}
|
561
|
-
visible={visible}
|
562
|
-
wordWrapEnabled={wordWrapEnabled}
|
563
|
-
onCellClick={onCellClickChild}
|
564
|
-
onContentReady={onContentReadyChild}
|
565
|
-
onCellPrepared={onCellPreparedChild}
|
566
|
-
onContextMenuPreparing={onContextMenuPreparingChild}
|
567
|
-
onDisposing={onDisposingChild}
|
568
|
-
onExporting={onExportingChild}
|
569
|
-
onInitialized={onInitializedChild}
|
570
|
-
onOptionChanged={onOptionChangedChild}
|
571
|
-
>
|
572
|
-
<StateStoring enabled={stateStoringKey?.length} type="sessionStorage" storageKey={stateStoringKey} />
|
573
|
-
<FieldChooser enabled={false} />
|
574
|
-
</PivotGrid>
|
575
|
-
</div>
|
576
|
-
);
|
577
|
-
}
|
578
|
-
);
|
579
|
-
|
580
|
-
export default DxPlanitTreeGrid;
|
package/dist/index.tsx
DELETED
package/dist/type.ts
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
import DevExpress from 'devextreme';
|
2
|
-
import { Format } from 'devextreme/localization';
|
3
|
-
|
4
|
-
export interface IGroupField {
|
5
|
-
groupCaption: string;
|
6
|
-
groupName?: string;
|
7
|
-
depth: number;
|
8
|
-
colspan: number;
|
9
|
-
}
|
10
|
-
|
11
|
-
export interface IColorInfo {
|
12
|
-
format: Format;
|
13
|
-
color: string;
|
14
|
-
condition: string;
|
15
|
-
}
|
16
|
-
|
17
|
-
export interface ColumnField {
|
18
|
-
colspan: number;
|
19
|
-
text: string;
|
20
|
-
type: string;
|
21
|
-
}
|
22
|
-
|
23
|
-
export interface Props extends DevExpress.ui.dxPivotGrid.Properties {
|
24
|
-
id?: string;
|
25
|
-
dataSource?: any;
|
26
|
-
groupField?: IGroupField[];
|
27
|
-
dataColor?: IColorInfo[];
|
28
|
-
convertNullToHipen?: boolean;
|
29
|
-
convertZeroToHipen?: boolean;
|
30
|
-
stateStoringKey?: string;
|
31
|
-
customExcelButton?: boolean;
|
32
|
-
}
|