@things-factory/meta-ui 8.0.0-beta.9 → 8.0.0

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.
Files changed (115) hide show
  1. package/client/bootstrap.ts +170 -0
  2. package/client/component/filter/filter-form-meta-code-select.ts +102 -0
  3. package/client/component/filter/filter-form-meta-object-select.ts +107 -0
  4. package/client/component/filter/filter-grist-meta-code-select.ts +97 -0
  5. package/client/component/filter/filter-grist-meta-object-select.ts +102 -0
  6. package/client/component/grist/editor/grist-editor-code-input.js +96 -0
  7. package/client/component/grist/editor/grist-editor-meta-code-selector.js +157 -0
  8. package/client/component/grist/editor/grist-editor-meta-object-selector.js +122 -0
  9. package/client/component/grist/renderer/grist-renderer-code-input.js +20 -0
  10. package/client/component/grist/renderer/grist-renderer-meta-code-selector.js +28 -0
  11. package/client/component/grist/renderer/grist-renderer-meta-object-selector.js +25 -0
  12. package/client/component/popup/code-input-editor-popup.js +111 -0
  13. package/client/component/popup/file-upload-popup.js +129 -0
  14. package/client/component/popup/meta-object-selector-popup.ts +356 -0
  15. package/client/component/popup/record-based-code-editor-popup.ts +141 -0
  16. package/client/dynamic-menus.ts +38 -0
  17. package/client/index.ts +18 -0
  18. package/client/load-components.ts +17 -0
  19. package/client/mixin/meta-base-mixin.js +323 -0
  20. package/client/mixin/meta-basic-grist-mixin.js +283 -0
  21. package/client/mixin/meta-button-mixin.js +116 -0
  22. package/client/mixin/meta-form-mixin.js +435 -0
  23. package/client/mixin/meta-grist-tab-mixin.js +335 -0
  24. package/client/mixin/meta-main-tab-mixin.js +267 -0
  25. package/client/mixin/meta-master-detail-mixin.js +395 -0
  26. package/client/mixin/meta-service-mixin.js +306 -0
  27. package/client/mixin/meta-tab-detail-mixin.js +283 -0
  28. package/client/mixin/meta-tab-mixin.js +190 -0
  29. package/client/pages/activity/meta-activity-define-page.js +422 -0
  30. package/client/pages/activity/meta-activity-list-page.js +262 -0
  31. package/client/pages/activity/meta-activity-viewer-element.js +35 -0
  32. package/client/pages/activity/meta-activity-writer-element.js +48 -0
  33. package/client/pages/activity/meta-activiy-mixin.js +79 -0
  34. package/client/pages/button-role/button-role-detail.js +50 -0
  35. package/client/pages/button-role/button-role-page.js +25 -0
  36. package/client/pages/doc-number/doc-number-page.js +24 -0
  37. package/client/pages/doc-number/next-doc-number-popup.js +25 -0
  38. package/client/pages/entity/config-entity.js +955 -0
  39. package/client/pages/entity/main-menu-selector.js +245 -0
  40. package/client/pages/history/history-copy-list-popup.js +145 -0
  41. package/client/pages/history/history-json-list-popup.js +159 -0
  42. package/client/pages/menu/dynamic-menu-template.js +92 -0
  43. package/client/pages/menu/dynamic-menu.ts +744 -0
  44. package/client/pages/menu/export-menu-popup.js +468 -0
  45. package/client/pages/meta-form-element.js +9 -0
  46. package/client/pages/meta-grist-element.js +12 -0
  47. package/client/pages/meta-grist-page.js +16 -0
  48. package/client/pages/meta-grist-tab-element.js +16 -0
  49. package/client/pages/meta-grist-tab-page.js +16 -0
  50. package/client/pages/meta-main-tab-element.js +12 -0
  51. package/client/pages/meta-main-tab-page.js +16 -0
  52. package/client/pages/meta-master-detail-element.js +12 -0
  53. package/client/pages/meta-master-detail-page.js +16 -0
  54. package/client/pages/meta-tab-detail-element.js +12 -0
  55. package/client/pages/meta-tab-detail-page.js +16 -0
  56. package/client/pages/meta-tab-element.js +15 -0
  57. package/client/pages/printer-device/printer-device-page.js +24 -0
  58. package/client/pages/template/doc-template-page.js +24 -0
  59. package/client/pages/template/template-file-page.js +24 -0
  60. package/client/pages/terms/config-terminology.js +214 -0
  61. package/client/pages/work-code/work-code-detail-popup.js +16 -0
  62. package/client/pages/work-code/work-code-page.js +23 -0
  63. package/client/route.ts +36 -0
  64. package/client/tsconfig.json +13 -0
  65. package/client/utils/grist-default-value.js +36 -0
  66. package/client/utils/meta-api.js +811 -0
  67. package/client/utils/meta-crypto.js +52 -0
  68. package/client/utils/meta-ui-util.js +3304 -0
  69. package/client/utils/rest-service-util.js +328 -0
  70. package/client/utils/service-util.js +1327 -0
  71. package/client/utils/terms-util.ts +119 -0
  72. package/client/utils/ui-util.js +338 -0
  73. package/client/utils/value-util.js +234 -0
  74. package/dist-client/tsconfig.tsbuildinfo +1 -1
  75. package/dist-client/utils/service-util.d.ts +2 -2
  76. package/dist-client/utils/service-util.js +4 -4
  77. package/dist-client/utils/service-util.js.map +1 -1
  78. package/dist-server/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +24 -24
  80. package/server/activity/CommonActivity.ts +68 -0
  81. package/server/index.ts +3 -0
  82. package/server/routes.ts +61 -0
  83. package/server/service/button-role/button-role-mutation.ts +105 -0
  84. package/server/service/button-role/button-role-query.ts +53 -0
  85. package/server/service/button-role/button-role-type.ts +39 -0
  86. package/server/service/button-role/button-role.ts +61 -0
  87. package/server/service/button-role/index.ts +7 -0
  88. package/server/service/dynamic-menu/dynamic-menu-query.ts +270 -0
  89. package/server/service/dynamic-menu/dynamic-menu-type.ts +74 -0
  90. package/server/service/dynamic-menu/index.ts +3 -0
  91. package/server/service/entity-event-subscriber/entity-event-subscriber.ts +80 -0
  92. package/server/service/entity-event-subscriber/index.ts +3 -0
  93. package/server/service/index.ts +41 -0
  94. package/server/service/menu-button-auth/index.ts +7 -0
  95. package/server/service/menu-button-auth/menu-button-auth-mutation.ts +133 -0
  96. package/server/service/menu-button-auth/menu-button-auth-query.ts +138 -0
  97. package/server/service/menu-button-auth/menu-button-auth-type.ts +63 -0
  98. package/server/service/menu-button-auth/menu-button-auth.ts +92 -0
  99. package/server/service/meta-activity/index.ts +5 -0
  100. package/server/service/meta-activity/meta-activity-mutation.ts +191 -0
  101. package/server/service/meta-activity/meta-activity-query.ts +43 -0
  102. package/server/service/meta-activity/meta-activity-type.ts +56 -0
  103. package/server/service/set-translations/index.ts +3 -0
  104. package/server/service/set-translations/set-translation-resolver.ts +63 -0
  105. package/server/service/work-code/index.ts +6 -0
  106. package/server/service/work-code/work-code-mutation.ts +147 -0
  107. package/server/service/work-code/work-code-query.ts +67 -0
  108. package/server/service/work-code/work-code-type.ts +60 -0
  109. package/server/service/work-code/work-code.ts +83 -0
  110. package/server/service/work-code-detail/index.ts +6 -0
  111. package/server/service/work-code-detail/work-code-detail-mutation.ts +149 -0
  112. package/server/service/work-code-detail/work-code-detail-query.ts +59 -0
  113. package/server/service/work-code-detail/work-code-detail-type.ts +50 -0
  114. package/server/service/work-code-detail/work-code-detail.ts +82 -0
  115. package/server/tsconfig.json +9 -0
@@ -0,0 +1,3304 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/data-grist/ox-record-creator.js'
3
+ import 'ses'
4
+
5
+ import { css, html, render, nothing } from 'lit'
6
+
7
+ import { getEditor, getRenderer } from '@operato/data-grist'
8
+ import { getValueGenerators } from '@operato/time-calculator'
9
+ import moment from '@operato/moment-timezone-es'
10
+ import {
11
+ ScrollbarStyles,
12
+ CommonGristStyles,
13
+ CommonButtonStyles,
14
+ ButtonContainerStyles,
15
+ CommonHeaderStyles
16
+ } from '@operato/styles'
17
+
18
+ import { MetaCrypto } from './meta-crypto'
19
+ import { TermsUtil } from './terms-util'
20
+ import { ValueUtil } from './value-util'
21
+ import { ServiceUtil } from './service-util'
22
+ import { UiUtil } from './ui-util'
23
+ import { MetaApi } from './meta-api'
24
+
25
+ /**
26
+ * @license
27
+ * Copyright © HatioLab Inc. All rights reserved.
28
+ * @author Shortstop shortstop@hatiolab.com
29
+ * @description 메타 UI에서 필요한 유틸리티 함수 정의
30
+ */
31
+ export class MetaUiUtil {
32
+ /**
33
+ * @description 메뉴 ID 정보로 메뉴 메타 정보 조회
34
+ ********************************************
35
+ * @param {String} menuId 메뉴 ID 값
36
+ * @returns {Object} 메뉴 메타 정보
37
+ */
38
+ static async findMenuMeta(menuId) {
39
+ let menu = await ServiceUtil.findOne('menu', menuId, 'template')
40
+
41
+ if (!menu) {
42
+ throw new Error(TermsUtil.tText('value_is_not_exist', { value: `${TermsUtil.tMenu('menu')} ${menuId}` }))
43
+ }
44
+
45
+ if (!menu.template) {
46
+ throw new Error(
47
+ TermsUtil.tText('value_is_not_exist', { value: `${TermsUtil.tMenu('menu')} ${TermsUtil.tLabel('model')}` })
48
+ )
49
+ }
50
+
51
+ this.menuId = menuId
52
+ return JSON.parse(menu.template)
53
+ }
54
+
55
+ /**
56
+ * @description 메뉴 라우팅 정보로 메뉴 메타 정보 조회
57
+ **********************************************
58
+ * @param {String} menuRouting 메뉴 route 값
59
+ * @returns {Object} 메뉴 메타 정보 (암호)
60
+ */
61
+ static async getMenuMetaData(menuRouting) {
62
+ let filters = [{ name: 'routing', operator: 'eq', value: menuRouting }]
63
+ let data = await ServiceUtil.searchByPagination('menus', filters, null, 0, 0, 'id,template')
64
+ return data
65
+ }
66
+
67
+ /**
68
+ * @description 메뉴 라우팅 정보로 메뉴 메타 정보 조회
69
+ **********************************************
70
+ * @param {Object} pageView
71
+ * @param {String} menuRouting 메뉴 route 값
72
+ * @returns {Object} 메뉴 메타 정보
73
+ */
74
+ static async findMenuMetaByRouting(pageView, menuRouting) {
75
+ let data = await MetaUiUtil.getMenuMetaData(menuRouting)
76
+ let menu = undefined
77
+
78
+ if (!data || !data.records || data.records.length == 0) {
79
+ if (pageView.defaultPageConfig) {
80
+ menu = { id: menuRouting, template: pageView.defaultPageConfig }
81
+ }
82
+ } else {
83
+ menu = data.records[0]
84
+ if (!menu.template && pageView.defaultPageConfig) {
85
+ menu.template = pageView.defaultPageConfig
86
+ }
87
+ }
88
+
89
+ if (!menu) {
90
+ throw new Error(TermsUtil.tText('value_is_not_exist', { value: `${TermsUtil.tMenu('menu')} ${menuRouting}` }))
91
+ } else if (!menu.template) {
92
+ throw new Error(
93
+ TermsUtil.tText('value_is_not_exist', { value: `${TermsUtil.tMenu('menu')} ${TermsUtil.tLabel('model')}` })
94
+ )
95
+ }
96
+
97
+ pageView.menuId = menu.id
98
+ return JSON.parse(MetaCrypto.dec(menu.template))
99
+ }
100
+
101
+ /**
102
+ * @description 기본 그리스트 페이지 프로퍼티 리턴
103
+ *******************************************
104
+ * @returns {Object} 기본 그리스트 페이지 프로퍼티
105
+ */
106
+ static getBasicGristPageProperties() {
107
+ return {
108
+ /* 메뉴 메타 정보 */
109
+ menuInfo: Object, // 메뉴 기본 정보
110
+ gridConfig: Object, // 그리드 구성 정보
111
+ gridColumnConfig: Array, // 그리드 컬럼 정보
112
+ formColumnConfig: Array, // form 컬럼 정보
113
+ buttonConfig: Array, // 버튼 구성 정보
114
+ searchConfig: Array, // 서치폼 구성 정보
115
+ gqlInfo: String, // GraphQL 정보
116
+ gqlFetchField: String, // GraphQL 조회 필드
117
+ searchFormElement: String, // 화면에서 사용 되는 서치폼 (grist, filter, not)
118
+ useFilterForm: Boolean, // 그리드내 filter 폼 사용 여부
119
+
120
+ /* 화면 구성 */
121
+ gristConfigSet: Object, // 메타정보에서 변환된 그리드 구성 정보
122
+ formConfigSet: Array, // 메타정보에서 변환된 폼 구성 정보
123
+ useButtonExport: Boolean, // 내보내기 버튼 사용 여부
124
+ useButtonImport: Boolean, // 가져오기 버튼 사용 여부
125
+ useButtonAdd: Boolean, // 추가 버튼 사용 여부
126
+ gridMobileMode: String, // 모바일 기기에서 그리드 뷰
127
+ gridDeskMode: String, // 데스크탑에서 그리드 뷰
128
+ gridViewOptions: Array, // 그리드 뷰 옵션
129
+ gridMode: String, // 현재 그리드 뷰 모드
130
+ infinityPage: true, // 페이지 사용시 false
131
+
132
+ route_name: String, // Element 로드시 화면을 그릴 메뉴 정보,
133
+ parent_id: String, // 상세 폼 뷰 상위 데이터 ID
134
+
135
+ /* 속성 ? */
136
+ is_detail: {
137
+ // 페이지 컴포넌트 여부
138
+ type: Boolean,
139
+ converter(value) {
140
+ let retVal = Boolean(value)
141
+ return retVal
142
+ }
143
+ },
144
+
145
+ is_popup: {
146
+ // 팝업 여부
147
+ type: Boolean,
148
+ converter(value) {
149
+ let retVal = Boolean(value)
150
+ return retVal
151
+ }
152
+ },
153
+
154
+ is_activity: {
155
+ // 작업 결재 관련 화면에서 호출 여부
156
+ type: Boolean,
157
+ converter(value) {
158
+ let retVal = Boolean(value)
159
+ return retVal
160
+ }
161
+ },
162
+
163
+ is_readonly: {
164
+ // 수정 불가 페이지
165
+ type: Boolean,
166
+ converter(value) {
167
+ let retVal = Boolean(value)
168
+ return retVal
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * @description 메뉴 메타 정보를 조회한 후 파싱...
176
+ ********************************************
177
+ * @param {HTMLElement} pageView
178
+ * @returns {Object} 메뉴 메타 정보
179
+ */
180
+ static async getMenuMeta(pageView) {
181
+ // 1. 라우팅 추출 및 메뉴 메타 정보 조회
182
+ let currentRouting = pageView.isPage ? UiUtil.currentRouting() : pageView.route_name
183
+ pageView.currentRouting = currentRouting
184
+ let menuMetaData = await MetaUiUtil.findMenuMetaByRouting(pageView, currentRouting)
185
+
186
+ // 2. Activity 여부에 따라 버튼 / 검색 삭제
187
+ if (pageView.is_activity === true) {
188
+ menuMetaData.grid.option.grid_refresh_when_page_activated = false
189
+ menuMetaData.grid.option.use_row_checker = false
190
+ menuMetaData.grid.option.pages = 'unlimited'
191
+ menuMetaData.grid.use_filter_form = false
192
+ menuMetaData.grid.button = []
193
+ menuMetaData.button = menuMetaData.button.filter(x => x.name == 'add')
194
+ }
195
+
196
+ // 3. 화면이 읽기 전용인 경우 그리드 Configuration 처리
197
+ if (pageView.is_readonly === true) {
198
+ menuMetaData.grid_column.forEach(x => {
199
+ x.editable = false
200
+ })
201
+ menuMetaData.button = menuMetaData.button.filter(x => x.name != 'add')
202
+ }
203
+
204
+ // 4. 필터 폼 사용 여부 강제 설정
205
+ if (pageView.use_filter_form === false) {
206
+ menuMetaData.grid.use_filter_form = false
207
+ }
208
+
209
+ // 5. 필드 변경 이벤트 처리자 설정
210
+ menuMetaData.grid_column
211
+ .filter(x => x.change_event_handler)
212
+ .forEach(x => {
213
+ if (!pageView.fieldChangeHandlers) pageView.fieldChangeHandlers = {}
214
+ pageView.fieldChangeHandlers[x.name] = x.change_event_handler
215
+ })
216
+
217
+ // 6. 그리드 타입 통합 처리 : object, resource-object -> meta-object-selector, resource-code-selector -> meta-code-selector
218
+ menuMetaData.grid_column.forEach(x => {
219
+ if (['resource-object', 'object'].includes(x.type)) {
220
+ x.type = 'meta-object-selector'
221
+ } else if (['resource-code-selector'].includes(x.type)) {
222
+ x.type = 'meta-code-selector'
223
+ }
224
+ })
225
+
226
+ // 7. 검색 필드의 검색 Operator가 in인 경우 디폴트 값을 배열로 변경
227
+ if (menuMetaData.search && menuMetaData.search.length > 0) {
228
+ menuMetaData.search.forEach(c => {
229
+ let value = c.value
230
+ if (value && c.operator == 'in' && value.indexOf(',') > 0) {
231
+ c.value = value.split(',')
232
+ }
233
+ })
234
+ }
235
+
236
+ // 8. 메뉴 메타 정보 리턴
237
+ return menuMetaData
238
+ }
239
+
240
+ /**
241
+ * @description 파라미터로 받은 pageLimits로 페이지네이션 페이지 수를 표시
242
+ ***************************************************************
243
+ * @param {Array} 페이지네이션 페이지 수 배열
244
+ * @returns {Object} 페이지네이션 페이지 수
245
+ */
246
+ static getGristPaginationCustomConfig(...pageLimits) {
247
+ return { pages: pageLimits }
248
+ }
249
+
250
+ /**
251
+ * @description 그리스트 기본 gutter 설정 리턴
252
+ ********************************************
253
+ * @param {Boolean} useRowSelector 행 선택 기능 사용 여부
254
+ * @param {Boolean} multiple 여러 행 선택 가능 여부
255
+ * @returns {Object} 그리스트 기본 gutter 설정
256
+ */
257
+ static getGristGuttersConfig(useRowSelector, multiple) {
258
+ let configList = [
259
+ { type: 'gutter', gutterName: 'dirty' },
260
+ { type: 'gutter', gutterName: 'sequence' }
261
+ ]
262
+
263
+ if (useRowSelector) {
264
+ configList.push({ type: 'gutter', gutterName: 'row-selector', multiple: multiple })
265
+ }
266
+
267
+ return configList
268
+ }
269
+
270
+ /**
271
+ * @description 그리스트 선택 모드 설정
272
+ ***************************************
273
+ * @returns {Object} 그리스트 선택 모드 설정
274
+ */
275
+ static getGristSelectableConfig(multiple) {
276
+ return { selectable: { multiple: multiple } }
277
+ }
278
+
279
+ /**
280
+ * @description 메뉴 메타 정보를 기반으로 그리드 설정 셋을 파싱하여 리턴
281
+ ************************************************************
282
+ * @param {Object} pageView 페이지
283
+ * @returns {Object} 그리드 설정 셋
284
+ */
285
+ static async parseGridConfigSet(pageView) {
286
+ // 0. GraphQL 예외 처리를 위한 오브젝트 초기 화
287
+ if (!pageView.gqlInfo) pageView.gqlInfo = {}
288
+ if (!pageView.gqlInfo.query) pageView.gqlInfo.query = {}
289
+ if (!pageView.gqlInfo.query.after_set_fields) pageView.gqlInfo.query.after_set_fields = {}
290
+
291
+ if (!pageView.gqlInfo.mutation) pageView.gqlInfo.mutation = {}
292
+ if (!pageView.gqlInfo.mutation.multiple) pageView.gqlInfo.mutation.multiple = {}
293
+ if (!pageView.gqlInfo.mutation.multiple.skip_fields) pageView.gqlInfo.mutation.multiple.skip_fields = []
294
+
295
+ // 1. 그리드 옵션
296
+ let gridOption = MetaUiUtil.getGridOptionSet(pageView)
297
+
298
+ // 2. 그리드 리스트 설정
299
+ let gridList = MetaUiUtil.getGridListSet(pageView)
300
+
301
+ // 3. 그리드 row 설정
302
+ let gridRow = MetaUiUtil.getGridRowSet(pageView)
303
+
304
+ // 4. 그리드 버튼 설정
305
+ let gridButton = MetaUiUtil.getGridButtonSet(pageView)
306
+
307
+ // 5. 그리드 컬럼 설정
308
+ let gridColumns = await MetaUiUtil.getGridColumnSet(pageView)
309
+
310
+ // 6. 그리드 meta-object-selector 타입이면서 검색 조건에 해당하는 컬럼을 찾아 숨김 컬럼으로 등록
311
+ gridColumns
312
+ .filter(x => ['meta-object-selector'].includes(x.type))
313
+ .forEach(col => {
314
+ pageView.searchConfig
315
+ .filter(x => x.name == col.name)
316
+ .forEach(x => {
317
+ const options = col.record.options
318
+ // 6.1. 검색 필드 명 설정
319
+ const filterName = options.referenceField ? options.referenceField : `${col.name}Id`
320
+
321
+ // 6.2. 검색 필드 설정 찾기
322
+ const filterIdx = pageView.searchConfig.findIndex(item => {
323
+ return item.name == col.name
324
+ })
325
+
326
+ // 6.3. 검색 필드를 그리드 컬럼 리스트에 추가
327
+ gridColumns.push({
328
+ type: 'meta-object-selector',
329
+ header: col.header,
330
+ name: filterName,
331
+ hidden: true,
332
+ record: {
333
+ editable: false,
334
+ mandatory: false,
335
+ options: options
336
+ },
337
+ unusable: true
338
+ })
339
+
340
+ // 6.4. 조회시 skip 필드 추가
341
+ pageView.gqlInfo.query.after_set_fields[filterName] = 'x'
342
+
343
+ // 6.5. 저장시 skip 필드 추가
344
+ pageView.gqlInfo.mutation.multiple.skip_fields.push(filterName)
345
+
346
+ // 6.6. 검색 설정 이름 변경
347
+ pageView.searchConfig[filterIdx].name = filterName
348
+ pageView.searchConfig[filterIdx].type = 'meta-object-selector'
349
+ })
350
+ })
351
+
352
+ // 7. 누적 계산 설정 컬럼 추출
353
+ let accumulatorCols = gridColumns.filter(col => (col.accumulator ? true : false))
354
+ if (accumulatorCols && accumulatorCols.length > 0) {
355
+ gridRow.accumulator = true
356
+ }
357
+
358
+ // 8. default 그리드 gutter
359
+ let gutterConfigs = MetaUiUtil.getGristGuttersConfig(gridOption.use_row_checker, gridRow.selectable.multiple)
360
+
361
+ // 9. 필터 폼에 검색 조건 필드 정의
362
+ if (pageView.useFilterForm == true) {
363
+ gridColumns = MetaUiUtil.setGridColumnSearchOption(pageView, gridColumns)
364
+ }
365
+
366
+ // 10. 그리드 강조 설정
367
+ if (pageView.gridEmphasized) {
368
+ // 10.1. 그리드 row
369
+ if (pageView.gridEmphasized && pageView.gridEmphasized.row && pageView.gridEmphasized.row.length > 0) {
370
+ gridRow.classifier = function (recordData, rowIdx) {
371
+ let settings = pageView.gridEmphasized.row
372
+
373
+ for (let idx = 0; idx < settings.length; idx++) {
374
+ const logic = settings[idx].logic
375
+ const c = new Compartment({ record: recordData, rowIdx })
376
+ const evalFunc = c.evaluate(`(record, rowIdx) => {
377
+ ${logic}
378
+ }`)
379
+ if (evalFunc(recordData, rowIdx)) {
380
+ return {
381
+ emphasized: [settings[idx].backgroundColor, settings[idx].fontColor]
382
+ }
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ // 10.2. 그리드 column
389
+ if (pageView.gridEmphasized && pageView.gridEmphasized.column) {
390
+ let columSetting = pageView.gridEmphasized.column
391
+
392
+ Object.keys(columSetting).forEach(key => {
393
+ let settings = columSetting[key]
394
+ let colIdx = gridColumns.findIndex(x => x.name == key)
395
+ if (!gridColumns[colIdx].record) gridColumns[colIdx].record = {}
396
+
397
+ gridColumns[colIdx].record.classifier = function (recordData, rowIdx) {
398
+ for (let idx = 0; idx < settings.length; idx++) {
399
+ const logic = settings[idx].logic
400
+ const c = new Compartment({ record: recordData, rowIdx })
401
+ const evalFunc = c.evaluate(`(record, rowIdx) => {
402
+ ${logic}
403
+ }`)
404
+ if (evalFunc(recordData, rowIdx)) {
405
+ return {
406
+ emphasized: [settings[idx].backgroundColor, settings[idx].fontColor]
407
+ }
408
+ }
409
+ }
410
+ }
411
+ })
412
+ }
413
+ }
414
+
415
+ // 11. 그리드 최종 Configuration
416
+ return {
417
+ list: gridList,
418
+ pagination: gridOption.pages,
419
+ sorters: gridOption.sorters,
420
+ rows: gridRow,
421
+ columns: [...gutterConfigs, ...gridButton, ...gridColumns]
422
+ }
423
+ }
424
+
425
+ /**
426
+ * @description 메뉴 메타 정보를 기반으로 폼 설정 셋을 파싱하여 리턴
427
+ ********************************************************
428
+ * @param {Object} pageView 페이지
429
+ * @returns {Object} 폼 설정 셋
430
+ */
431
+ static async parseFormConfigSet(pageView) {
432
+ let formColumnConfig = pageView.formColumnConfig
433
+ let columns = []
434
+ let defaultValueGenerators = Object.keys(getValueGenerators())
435
+
436
+ for (let idx = 0; idx < formColumnConfig.length; idx++) {
437
+ let {
438
+ type = 'string',
439
+ name = undefined,
440
+ header = undefined,
441
+ hidden = false,
442
+ editable = true,
443
+ mandatory = false,
444
+ align = 'left',
445
+ select_opt = undefined,
446
+ object_opt = undefined,
447
+ default_value = undefined
448
+ } = formColumnConfig[idx]
449
+
450
+ // 컬럼 config 으로 변경 및 문자열 처리
451
+ let column = {
452
+ type: type,
453
+ name: name,
454
+ header_txt: ValueUtil.isEmpty(header) ? TermsUtil.tLabel(name) : TermsUtil.tLabel(header),
455
+ header: {
456
+ renderer: function (column) {
457
+ return column.header_txt
458
+ }
459
+ },
460
+ hidden: hidden,
461
+ record: {
462
+ editable: editable,
463
+ mandatory: mandatory,
464
+ align: align,
465
+ classifier: function () {},
466
+ renderer: getRenderer(type)
467
+ }
468
+ }
469
+
470
+ if (editable) {
471
+ column.record.editor = getEditor(type)
472
+ }
473
+
474
+ if (default_value) {
475
+ let defaultSet = default_value.split(',')
476
+ let defaultName = defaultSet[0]
477
+
478
+ if (defaultValueGenerators.includes(defaultName)) {
479
+ column.record.defaultValue =
480
+ defaultSet.length == 1 ? { name: defaultName } : { name: defaultName, params: { calc: defaultSet[1] } }
481
+ } else {
482
+ column.record.defaultValue = type === 'number' ? Number(default_value) : default_value
483
+ }
484
+ }
485
+
486
+ if (type == 'boolean-all') {
487
+ column.type = 'boolean'
488
+ }
489
+
490
+ // meta- 로 시작 되는 타입은 공통으로 메뉴를 조회 template 을 가져간다.
491
+ if (type.startsWith('meta-') && object_opt && object_opt.menu) {
492
+ object_opt = await MetaUiUtil.getGristMetaObjectOptions(type, object_opt)
493
+ }
494
+
495
+ // select-option
496
+ if (type === 'select' && ValueUtil.isNotEmpty(select_opt)) {
497
+ if (Array.isArray(select_opt)) {
498
+ column.record.options = select_opt
499
+ } else {
500
+ if (select_opt.type === 'code') {
501
+ // 공통 코드
502
+ column.record.options = await ServiceUtil.getCodeSelectorData(
503
+ select_opt.values ? select_opt.values : select_opt.name
504
+ )
505
+ } else if (select_opt.type === 'scenario') {
506
+ // 시나리오
507
+ column.record.options = await ServiceUtil.getCodeByScenario(select_opt.name, select_opt.args)
508
+ } else if (select_opt.type === 'entity') {
509
+ // 엔티티
510
+ column.record.options = await ServiceUtil.getCodeByEntity(select_opt.args)
511
+ }
512
+ }
513
+ }
514
+
515
+ if (type === 'meta-object-selector' && ValueUtil.isNotEmpty(object_opt)) {
516
+ column.record.options = { ...object_opt }
517
+ } else if (type === 'meta-code-selector' && ValueUtil.isNotEmpty(object_opt)) {
518
+ if (object_opt.dispField) {
519
+ object_opt.codes = await ServiceUtil.getCodeByEntity(object_opt)
520
+ }
521
+ column.record.options = { ...object_opt }
522
+ }
523
+ columns.push(column)
524
+ }
525
+
526
+ return columns
527
+ }
528
+
529
+ /**
530
+ * @description 메타 설정에서 그리드 옵션을 추출
531
+ *****************************************
532
+ * @param {Object} pageView
533
+ * @returns {Object}
534
+ */
535
+ static getGridOptionSet(pageView) {
536
+ let gridConfig = pageView.gridConfig
537
+ pageView.useFilterForm = gridConfig && gridConfig.use_filter_form != undefined ? gridConfig.use_filter_form : true
538
+
539
+ let {
540
+ mobile_mode = 'LIST',
541
+ desk_mode = 'GRID',
542
+ view_mode = [],
543
+ use_row_checker = true,
544
+ sorters = [],
545
+ pages = [50, 100, 500, 1000]
546
+ } = ValueUtil.isEmpty(gridConfig.option) ? {} : gridConfig.option
547
+
548
+ // 페이지 설정 변환
549
+ if (pages === 'unlimited' || pages === '-1') {
550
+ pageView.infinityPage = true
551
+ pages = { infinite: true }
552
+ } else {
553
+ pageView.infinityPage = false
554
+ pages.sort(function (a, b) {
555
+ return a - b
556
+ })
557
+ pages = { pages: pages }
558
+ }
559
+
560
+ // 모바일 데스크 뷰 모드
561
+ if (ValueUtil.isNotEmpty(view_mode)) {
562
+ if (view_mode.length == 1) {
563
+ desk_mode = view_mode[0]
564
+ mobile_mode = view_mode[0]
565
+ } else if (view_mode.length >= 2) {
566
+ if (!view_mode.includes(mobile_mode)) {
567
+ mobile_mode = view_mode[1]
568
+ }
569
+
570
+ if (!view_mode.includes(desk_mode)) {
571
+ desk_mode = view_mode[0]
572
+ }
573
+ }
574
+ }
575
+
576
+ pageView.gridMobileMode = mobile_mode
577
+ pageView.gridDeskMode = desk_mode
578
+ pageView.gridViewOptions = view_mode
579
+ pageView.gridMode = UiUtil.isMobileEnv() ? pageView.gridMobileMode : pageView.gridDeskMode
580
+
581
+ return {
582
+ use_row_checker: use_row_checker,
583
+ sorters: sorters,
584
+ pages: pages
585
+ }
586
+ }
587
+
588
+ /**
589
+ * @description 메타 설정에서 그리드 리스트 설정을 추출
590
+ ***********************************************
591
+ * @param {Object} pageView
592
+ * @returns {Object}
593
+ */
594
+ static getGridListSet(pageView) {
595
+ let gridList = {
596
+ fields: [ValueUtil.getParams(pageView.menuInfo, 'name'), ValueUtil.getParams(pageView.menuInfo, 'desc')]
597
+ }
598
+
599
+ // 기본 리스트 설정과 메타에서 가져온 설정 merge
600
+ if (pageView.gridConfig.list) {
601
+ Object.assign(gridList, pageView.gridConfig.list)
602
+ }
603
+
604
+ return gridList
605
+ }
606
+
607
+ /**
608
+ * @description 메타 설정에서 그리드 Row 옵션을 추출
609
+ *********************************************
610
+ * @param {Object} pageView
611
+ * @returns {Object}
612
+ */
613
+ static getGridRowSet(pageView) {
614
+ // 기본 옵션, 메타와 상관없이 그리드 로우 멀티 셀렉트는 false, click 이벤트는 없다.
615
+ let { multiple_select = false, click = undefined } = ValueUtil.isEmpty(pageView.gridConfig.row)
616
+ ? {}
617
+ : pageView.gridConfig.row
618
+
619
+ let retObject = {
620
+ selectable: { multiple: multiple_select },
621
+ appendable: pageView.useButtonAdd
622
+ }
623
+
624
+ if (ValueUtil.isNotEmpty(click)) {
625
+ retObject.handlers = { click: click }
626
+ }
627
+
628
+ return retObject
629
+ }
630
+
631
+ /**
632
+ * @description 메타 설정에서 그리드 버튼 옵션을 추출
633
+ *********************************************
634
+ * @param {Object} pageView
635
+ * @returns {Array}
636
+ */
637
+ static getGridButtonSet(pageView) {
638
+ let gridButtons = pageView.gridConfig.button
639
+ return ValueUtil.isEmpty(gridButtons)
640
+ ? []
641
+ : // 그리드 버튼 정보로 버튼 설정 정보를 구성하여 리턴
642
+ gridButtons.map(btn => {
643
+ // 기본 그리드 버튼
644
+ let button = { type: 'gutter', gutterName: 'button' }
645
+
646
+ // 레코드 특정 필드 값 조건에 따라서 아이콘을 변경 ...
647
+ if (ValueUtil.isNotEmpty(btn.icon)) {
648
+ // 아이콘이 Array 타입
649
+ if (Array.isArray(btn.icon)) {
650
+ // 지정 된 조건에 따라 아이콘을 바꾼다.
651
+ button.icon = record => {
652
+ if (ValueUtil.isNotEmpty(record)) {
653
+ // 지정된 조건 Loop
654
+ for (let i = 0; i < btn.icon.length; i++) {
655
+ let logic = btn.icon[i]
656
+ let useIcon = ValueUtil.compareObjectValues(
657
+ logic,
658
+ record,
659
+ Object.keys(logic).filter(key => key != 'icon')
660
+ )
661
+ if (useIcon === true) {
662
+ return logic.icon
663
+ }
664
+ }
665
+ }
666
+ }
667
+ } else {
668
+ button.icon = btn.icon
669
+ }
670
+ }
671
+
672
+ // 이름이 지정되어 있으면 변환
673
+ if (ValueUtil.isNotEmpty(btn.name)) {
674
+ button.name = btn.name
675
+ }
676
+ // Tooltip
677
+ if (ValueUtil.isNotEmpty(btn.label)) {
678
+ button.title = TermsUtil.tButton(btn.label)
679
+ }
680
+
681
+ // 버튼 유형이 기본이면 ( 그리스트 기본 제공 기능 및 추가 기능 )
682
+ if (btn.type === 'basic') {
683
+ let logicTxt = ValueUtil.getParams(btn, 'logic')
684
+
685
+ if (['history_copy', 'history_json'].includes(logicTxt)) {
686
+ button.handlers = {
687
+ click: (_columns, _data, _column, record, _rowIdx) => {
688
+ if (record.id) {
689
+ let action = {
690
+ title: 'data_history_list',
691
+ type: 'popup',
692
+ tagname: logicTxt == 'history_copy' ? 'history-copy-list-popup' : 'history-json-list-popup',
693
+ location:
694
+ logicTxt == 'history_copy'
695
+ ? 'pages/hgistory/history-copy-list-popup.js'
696
+ : 'pages/hgistory/history-json-list-popup.js',
697
+ menu: pageView.currentRouting,
698
+ size: 'large',
699
+ popup_field: 'open_param',
700
+ parent_field: 'id'
701
+ }
702
+
703
+ let params = {
704
+ ...record,
705
+ gqlFunc: ValueUtil.getParams(pageView.gqlInfo, 'query', 'find_one_func'),
706
+ gristConfig: JSON.parse(JSON.stringify(pageView.grist.config))
707
+ }
708
+
709
+ MetaUiUtil.gristButtonHandler(pageView, { logic: action }, params)
710
+ }
711
+ }
712
+ }
713
+ } else {
714
+ button.handlers = { click: logicTxt }
715
+ }
716
+
717
+ // 버튼 유형이 커스텀이면 ...
718
+ } else {
719
+ button.handlers = {
720
+ click: (_columns, _data, _column, record, _rowIdx) => {
721
+ if (record.id) {
722
+ MetaUiUtil.gristButtonHandler(pageView, btn, record)
723
+ }
724
+ }
725
+ }
726
+ }
727
+
728
+ return button
729
+ })
730
+ }
731
+
732
+ /**
733
+ * @description 메타 데이터에서 그리드 렌더링 config 을 생성
734
+ ***************************************************
735
+ * @param {Object} pageView 페이지
736
+ * @returns {Array} 그리드 컬럼 설정 리스트
737
+ */
738
+ static async getGridColumnSet(pageView) {
739
+ let gridColumnConfig = pageView.gridColumnConfig
740
+
741
+ // 메타에 그리드 컬럼 정보가 없으면
742
+ if (ValueUtil.isEmpty(gridColumnConfig)) {
743
+ return []
744
+ }
745
+
746
+ // CARD, LIST인 경우 컬럼 정보를 표시하기 위한 컬럼들 추출
747
+ let mobileDisplayColumns =
748
+ UiUtil.isMobileEnv() && pageView.etcConfig && pageView.etcConfig.mobile_display_columns
749
+ ? pageView.etcConfig.mobile_display_columns.split(',')
750
+ : []
751
+
752
+ let defaultValueGenerators = Object.keys(getValueGenerators())
753
+ let columns = []
754
+ for (let idx = 0; idx < gridColumnConfig.length; idx++) {
755
+ let {
756
+ type = 'string',
757
+ name = undefined,
758
+ header = undefined,
759
+ hidden = false,
760
+ editable = true,
761
+ mandatory = false,
762
+ sortable = false,
763
+ align = 'left',
764
+ width = 0,
765
+ exportable = false,
766
+ select_opt = undefined,
767
+ object_opt = undefined,
768
+ format = undefined,
769
+ default_value = undefined,
770
+ accumulator = undefined,
771
+ fixed = false
772
+ } = gridColumnConfig[idx]
773
+
774
+ // 컬럼 config 으로 변경 및 문자열 처리
775
+ let column = {
776
+ type: type,
777
+ name: name,
778
+ header: ValueUtil.isEmpty(header) ? '' : TermsUtil.tLabel(header),
779
+ hidden: hidden,
780
+ sortable: sortable,
781
+ width: width,
782
+ fixed: fixed,
783
+ record: { editable: editable, mandatory: mandatory, align: align, format: format }
784
+ }
785
+
786
+ // accumulator 설정이 있고 type이 number이면 accumulator 설정 추가
787
+ if (type.indexOf('number') >= 0 && accumulator) {
788
+ column.accumulator = accumulator
789
+ }
790
+
791
+ if (default_value) {
792
+ let defaultSet = default_value.split(',')
793
+ let defaultName = defaultSet[0]
794
+
795
+ if (defaultValueGenerators.includes(defaultName)) {
796
+ column.record.defaultValue =
797
+ defaultSet.length == 1 ? { name: defaultName } : { name: defaultName, params: { calc: defaultSet[1] } }
798
+ } else {
799
+ column.record.defaultValue = type === 'number' ? Number(default_value) : default_value
800
+ }
801
+ }
802
+
803
+ if (type == 'boolean-all') {
804
+ column.type = 'boolean'
805
+ let colHeaderTxt = column.header
806
+ column.header = {
807
+ renderer: function (col) {
808
+ return html`
809
+ <span>${colHeaderTxt}
810
+ <input type='checkbox'
811
+ @change=${e => {
812
+ e.stopPropagation()
813
+
814
+ let targetElement = e.target
815
+ let selected = targetElement.checked
816
+ let grist = pageView.grist
817
+
818
+ if (grist._data && grist._data.records) {
819
+ let records = grist._data.records
820
+ for (let idx = 0; idx < records.length; idx++) {
821
+ targetElement.dispatchEvent(
822
+ new CustomEvent('field-change', {
823
+ bubbles: true,
824
+ composed: true,
825
+ detail: {
826
+ before: records[idx][col.name],
827
+ after: selected,
828
+ record: records[idx],
829
+ column: col,
830
+ row: idx
831
+ }
832
+ })
833
+ )
834
+ }
835
+ }
836
+ }}
837
+ ></input>
838
+ </span>
839
+ `
840
+ }
841
+ }
842
+ }
843
+
844
+ // meta- 로 시작 되는 타입은 공통으로 메뉴를 조회 template 을 가져간다.
845
+ if (type.startsWith('meta-') && object_opt && object_opt.menu) {
846
+ object_opt = await MetaUiUtil.getGristMetaObjectOptions(type, object_opt)
847
+ }
848
+
849
+ // select-option
850
+ if (type === 'select' && ValueUtil.isNotEmpty(select_opt)) {
851
+ if (Array.isArray(select_opt)) {
852
+ column.record.options = select_opt
853
+ } else {
854
+ // 공통 코드
855
+ if (select_opt.type === 'code') {
856
+ column.record.options = await ServiceUtil.getCodeSelectorData(
857
+ select_opt.values ? select_opt.values : select_opt.name
858
+ )
859
+ // 시나리오
860
+ } else if (select_opt.type === 'scenario') {
861
+ column.record.options = await ServiceUtil.getCodeByScenario(select_opt.name, select_opt.args)
862
+ // 엔티티
863
+ } else if (select_opt.type === 'entity') {
864
+ column.record.options = await ServiceUtil.getCodeByEntity(select_opt.args)
865
+ }
866
+ }
867
+ }
868
+
869
+ // object-option
870
+ if (type === 'meta-object-selector' && ValueUtil.isNotEmpty(object_opt)) {
871
+ column.record.options = { ...object_opt }
872
+ } else if (type === 'meta-code-selector' && ValueUtil.isNotEmpty(object_opt)) {
873
+ if (object_opt.dispField) {
874
+ object_opt.codes = await ServiceUtil.getCodeByEntity(object_opt)
875
+ }
876
+ column.record.options = { ...object_opt }
877
+ }
878
+
879
+ // attributes (확장 속성)
880
+ if (type === 'attributes' && format) {
881
+ column.record.options = await ServiceUtil.getAttributeSetOptionFor(format)
882
+ }
883
+
884
+ // mask
885
+ if (type == 'mask') {
886
+ column.record.renderer = (value, column, record, rowIndex, field) => {
887
+ let maskVal = ''
888
+ if (value) {
889
+ let valCount = value.length
890
+ for (let i = 1; i <= valCount; i++) {
891
+ maskVal += '*'
892
+ }
893
+ } else {
894
+ maskVal = '********'
895
+ }
896
+ return maskVal
897
+ }
898
+ }
899
+
900
+ // LIST, CARD 에서 컬럼 정보 표시
901
+ if (mobileDisplayColumns.includes(name)) {
902
+ column.record.renderer = (value, column, record, rowIndex, field) => {
903
+ return TermsUtil.tLabel(header) + ': ' + value
904
+ }
905
+ }
906
+
907
+ // 내보내기 버튼 사용, 컬럼 내보내기 옵션
908
+ if (pageView.useButtonExport === true && exportable === true) {
909
+ column.imex = { header: column.header, key: column.name, width: column.width / 6, type: column.type }
910
+ }
911
+
912
+ columns.push(column)
913
+ }
914
+
915
+ if (this.is_activity !== true) {
916
+ return columns
917
+ }
918
+
919
+ return columns
920
+ }
921
+
922
+ /**
923
+ * @description meta-object, meta-code에 대한 설정을 가져온다. (menu or 설정)
924
+ ***********************************************************************
925
+ * @param {String} type 컬럼 타입
926
+ * @param {Object} options 컬럼 오브젝트 설정
927
+ * @returns {Object} 메타 변경 결과
928
+ */
929
+ static async getGristMetaObjectOptions(type, options) {
930
+ let data = await MetaUiUtil.getMenuMetaData(options.menu)
931
+
932
+ if (data && data.records && data.records.length > 0 && data.records[0].template) {
933
+ let menu = data.records[0]
934
+ let menu_template = JSON.parse(MetaCrypto.dec(menu.template))
935
+
936
+ if (!options.queryName) {
937
+ options.queryName = menu_template.gql.query.list_func
938
+ }
939
+
940
+ if (!options.select) {
941
+ options.select = menu_template.grid_column
942
+ }
943
+
944
+ if (!options.filterFields || options.filterFields.length == 0) {
945
+ options.filterFields = menu_template.search
946
+ }
947
+
948
+ if (!options.selectorName) {
949
+ options.selectorName = menu_template.menu.title
950
+ }
951
+
952
+ if (!options.filters) {
953
+ options.filters = menu_template.gql.query.filters || []
954
+ }
955
+
956
+ if (type.includes('-object-')) {
957
+ if (!options.nameField) {
958
+ options.nameField = menu_template.menu.name || 'name'
959
+ }
960
+ } else if (type.includes('-code-')) {
961
+ if (!options.codeField) {
962
+ options.codeField = menu_template.menu.name || 'code'
963
+ }
964
+
965
+ if (!options.dispField) {
966
+ options.dispField = menu_template.menu.desc || undefined
967
+ }
968
+ }
969
+ }
970
+
971
+ return options
972
+ }
973
+
974
+ /**
975
+ * @description 필터 폼에 검색 조건 필드 정의
976
+ *****************************************
977
+ * @param {HTMLElement} pageView 페이지
978
+ * @param {Array} gridColumns 그리드 컬럼 설정
979
+ * @returns
980
+ */
981
+ static setGridColumnSearchOption(pageView, gridColumns) {
982
+ let searchConfig = pageView.searchConfig
983
+
984
+ if (searchConfig) {
985
+ searchConfig.forEach(config => {
986
+ if (typeof config === 'string') {
987
+ gridColumns
988
+ .filter(col => col.name == config)
989
+ .forEach(col => {
990
+ col.filter = 'search'
991
+ })
992
+ } else if (typeof config === 'object') {
993
+ gridColumns
994
+ .filter(col => col.name == config.name)
995
+ .forEach(col => {
996
+ col.filter = config
997
+ col.filter.type = col.type === 'datetime' && config.operator == 'eq' ? 'datetime-local' : col.type
998
+ let defValue = col.filter.value
999
+ let dateKeywords = ['today', 'year', 'month', 'date', 'hour', 'min', 'sec']
1000
+
1001
+ if (
1002
+ defValue &&
1003
+ typeof defValue == 'string' &&
1004
+ dateKeywords.filter(x => defValue.startsWith(x)).length > 0
1005
+ ) {
1006
+ let defRes = []
1007
+ let dateFormat = { date: 'YYYY-MM-DD', datetime: 'YYYY-MM-DD HH:mm:ss' }
1008
+ let durationType = {
1009
+ today: 'day',
1010
+ year: 'year',
1011
+ month: 'month',
1012
+ date: 'day',
1013
+ hour: 'hour',
1014
+ min: 'minute',
1015
+ sec: 'second'
1016
+ }
1017
+ let defDates = defValue.split(',')
1018
+
1019
+ defDates.forEach(x => {
1020
+ const todayMoment = moment()
1021
+
1022
+ if (dateKeywords.includes(x)) {
1023
+ defRes.push(todayMoment.startOf(durationType[x]).format(dateFormat[col.type]))
1024
+ } else if (x.includes('-')) {
1025
+ let wordSplits = x.split('-')
1026
+ let key = wordSplits[0]
1027
+ let value = wordSplits[1]
1028
+ defRes.push(
1029
+ todayMoment
1030
+ .subtract(value, durationType[key])
1031
+ .startOf(durationType[key])
1032
+ .format(dateFormat[col.type])
1033
+ )
1034
+ } else if (x.includes('+')) {
1035
+ let wordSplits = x.split('+')
1036
+ let key = wordSplits[0]
1037
+ let value = wordSplits[1]
1038
+ defRes.push(
1039
+ todayMoment.add(value, durationType[key]).startOf(durationType[key]).format(dateFormat[col.type])
1040
+ )
1041
+ }
1042
+ })
1043
+
1044
+ if (defRes.length > 0) col.filter.value = defRes.length == 1 ? defRes[0] : defRes
1045
+ }
1046
+ })
1047
+ }
1048
+ })
1049
+ }
1050
+
1051
+ return gridColumns
1052
+ }
1053
+
1054
+ /**
1055
+ * @description 컨텍스트 오브젝트 리턴
1056
+ **********************************
1057
+ * @param {Object} pageView
1058
+ * @returns {Object} 컨텍스트 오브젝트
1059
+ */
1060
+ static getContextObject(pageView) {
1061
+ let ctx =
1062
+ pageView && pageView.menuInfo
1063
+ ? {
1064
+ title: TermsUtil.tTitle(pageView.menuInfo.title),
1065
+ help: pageView.menuInfo.help || '',
1066
+ actions: [...MetaUiUtil.getContextButtons(pageView)]
1067
+ }
1068
+ : {}
1069
+
1070
+ // export 버튼
1071
+ if (pageView.useButtonExport) {
1072
+ ctx.exportable = {
1073
+ name: ctx.title,
1074
+ data: pageView.export.bind(pageView)
1075
+ }
1076
+ }
1077
+
1078
+ // import 버튼
1079
+ if (pageView.useButtonImport) {
1080
+ ctx.importable = {
1081
+ handler: pageView.import.bind(pageView)
1082
+ }
1083
+ }
1084
+
1085
+ // Context
1086
+ return ctx
1087
+ }
1088
+
1089
+ /**
1090
+ * @description 메뉴 컨텍스트에서 사용할 버튼 정보 추출
1091
+ **********************************************
1092
+ * @param {Object} pageView 페이지 뷰
1093
+ * @returns {Array} 버튼 엘리먼트 리스트
1094
+ */
1095
+ static getContextButtons(pageView) {
1096
+ let buttonConfig = pageView.buttonConfig
1097
+ return ValueUtil.isEmpty(buttonConfig)
1098
+ ? []
1099
+ : buttonConfig
1100
+ .filter(b => b.name != 'export' && b.name != 'import' && b.name != 'add')
1101
+ .map(btn => {
1102
+ let {
1103
+ name = undefined,
1104
+ label = undefined,
1105
+ style = undefined,
1106
+ type = 'basic',
1107
+ action = undefined,
1108
+ logic = undefined
1109
+ } = btn
1110
+
1111
+ if (ValueUtil.isEmpty(name)) {
1112
+ return { title: '-1' }
1113
+ }
1114
+
1115
+ if (ValueUtil.isEmpty(label)) label = name
1116
+ if (ValueUtil.isEmpty(style)) style = name
1117
+ if (ValueUtil.isEmpty(action)) action = name
1118
+
1119
+ let btnStylecss = ValueUtil.isNotEmpty(style)
1120
+ ? CommonButtonStyles[style] || {
1121
+ icon: style,
1122
+ emphasis: {
1123
+ raised: true,
1124
+ outlined: false,
1125
+ dense: false,
1126
+ danger: false
1127
+ }
1128
+ }
1129
+ : {}
1130
+
1131
+ return {
1132
+ title: TermsUtil.tButton(label),
1133
+ action: MetaUiUtil.getButtonActionHandler(pageView, type, action, logic),
1134
+ ...btnStylecss
1135
+ }
1136
+ })
1137
+ .filter(btn => btn.title != '-1')
1138
+ }
1139
+
1140
+ /**
1141
+ * @description element로 로드시 버튼 element를 container에 직접 추가
1142
+ ****************************************************************
1143
+ * @param {Object} pageView 페이지 뷰
1144
+ * @returns {Array} 버튼 엘리먼트 리스트
1145
+ */
1146
+ static getContainerButtons(pageView) {
1147
+ let buttonConfig = pageView && pageView.buttonConfig ? pageView.buttonConfig : null
1148
+ return !buttonConfig
1149
+ ? []
1150
+ : buttonConfig
1151
+ .filter(b => b.name != 'add')
1152
+ .map(b => {
1153
+ let {
1154
+ name = undefined,
1155
+ label = undefined,
1156
+ style = undefined,
1157
+ type = 'basic',
1158
+ action = undefined,
1159
+ logic = undefined
1160
+ } = b
1161
+
1162
+ if (ValueUtil.isEmpty(label)) label = name
1163
+ if (ValueUtil.isEmpty(style)) style = name
1164
+ if (ValueUtil.isEmpty(action)) action = name
1165
+
1166
+ let btn = MetaUiUtil.createButtonElement(TermsUtil.tButton(label), style)
1167
+ btn.onclick = MetaUiUtil.getButtonActionHandler(pageView, type, action, logic)
1168
+ return btn
1169
+ })
1170
+ }
1171
+
1172
+ /**
1173
+ * @description 버튼 엘리먼트를 생성
1174
+ ********************************
1175
+ * @param {String} buttonName
1176
+ * @param {String} style
1177
+ * @returns {HTMLElement} 버튼 엘리먼트
1178
+ */
1179
+ static createButtonElement(buttonName, style) {
1180
+ let btnStyle = CommonButtonStyles[style] || {
1181
+ icon: style,
1182
+ emphasis: {
1183
+ raised: true,
1184
+ outlined: false,
1185
+ dense: false,
1186
+ danger: false
1187
+ }
1188
+ }
1189
+
1190
+ const { raised, outlined, dense, danger, icon } = btnStyle.emphasis
1191
+
1192
+ const template = html`
1193
+ <button ?dense=${dense} ?raised=${raised} ?outlined=${outlined} ?danger=${danger}>
1194
+ <md-icon>${icon || 'done'}</md-icon>
1195
+ ${buttonName}
1196
+ </button>
1197
+ `
1198
+
1199
+ const container = document.createElement('div')
1200
+ render(template, container)
1201
+
1202
+ const element = container.firstElementChild
1203
+ return container.removeChild(element)
1204
+ }
1205
+
1206
+ /**
1207
+ * @description 버튼과 연결할 핸들러를 리턴
1208
+ *************************************
1209
+ * @param {Object} pageView 화면
1210
+ * @param {String} type 버튼 유형 (기본, 커스텀, 그리드 버튼)
1211
+ * @param {String} action 버튼 명
1212
+ * @param {Object} logic 버튼 로직 (String || JSON)
1213
+ * @returns {Function} 버튼 클릭시 실행될 액션 핸들러
1214
+ */
1215
+ static getButtonActionHandler(pageView, type, action, logic) {
1216
+ if (type == 'basic') {
1217
+ if (pageView[action]) {
1218
+ return pageView[action].bind(pageView)
1219
+ }
1220
+ } else if (type == 'custom') {
1221
+ return () => MetaUiUtil.customButtonHandler(pageView, action, logic)
1222
+ }
1223
+
1224
+ // 버튼과 연결된 함수가 없습니다.
1225
+ return () => {
1226
+ UiUtil.showAlertPopup('title.warning', 'text.button_bind_func_is_not_exist', 'info', 'confirm')
1227
+ }
1228
+ }
1229
+
1230
+ /**
1231
+ * @description 그리스트 버튼 (그리드에 추가된 버튼)에 대한 커스텀 액션 분기
1232
+ ***************************************************************
1233
+ * @param {Object} pageView
1234
+ * @param {Object} actionInfo
1235
+ * @param {Object} record
1236
+ */
1237
+ static async gristButtonHandler(pageView, actionInfo, record) {
1238
+ let action = ValueUtil.getParams(actionInfo, 'logic')
1239
+ let buttonName = ValueUtil.getParams(actionInfo, 'name')
1240
+ let eventType = ValueUtil.getParams(action, 'type')
1241
+
1242
+ // 폼 팝업
1243
+ if (eventType === 'form') {
1244
+ // 상세 폼 뷰는 기본 정의된 로직으로 셋팅
1245
+ action.tagname = 'meta-form-element'
1246
+ action.location = 'pages/meta-form-element.js'
1247
+ action.parent_field = 'id'
1248
+ await MetaUiUtil.commonButtonOpenPopup(pageView, action, record)
1249
+
1250
+ // 다른 객체로 파라미터 전달
1251
+ } else if (eventType === 'pass_param') {
1252
+ MetaUiUtil.gristButtonPassParam(pageView, action, record)
1253
+
1254
+ // 페이지 이동
1255
+ } else if (eventType === 'page') {
1256
+ let passParams = action.param_field ? {} : record
1257
+
1258
+ if (action.param_field) {
1259
+ Object.keys(record)
1260
+ .filter(x => Object.keys(action.param_field).includes(x))
1261
+ .map(x => {
1262
+ if (Object.keys(action.param_field).includes(x)) {
1263
+ let data = record[x]
1264
+ passParams[action.param_field[x]] =
1265
+ Array.isArray(data) === false && typeof data === 'object' && data.id ? { id: data.id } : data
1266
+ }
1267
+ })
1268
+ }
1269
+
1270
+ UiUtil.pageNavigate(action.url, [passParams])
1271
+
1272
+ // 팝업 실행
1273
+ } else if (eventType === 'popup') {
1274
+ await MetaUiUtil.commonButtonOpenPopup(pageView, action, record)
1275
+
1276
+ // 서비스 호출
1277
+ } else if (eventType === 'graphql') {
1278
+ await MetaUiUtil.commonButtonCallService(pageView, action, record, record)
1279
+
1280
+ // 시나리오 호출
1281
+ } else if (eventType === 'scenario') {
1282
+ await MetaUiUtil.commonButtonCallScenario(pageView, buttonName, action, record)
1283
+
1284
+ // 설정된 조건에 따라 커스텀 로직 처리
1285
+ } else if (eventType === 'value_reference') {
1286
+ await MetaUiUtil.gristButtonValueReference(pageView, action, record)
1287
+
1288
+ // 첨부파일 다운로드 처리
1289
+ } else if (eventType === 'download') {
1290
+ MetaUiUtil.downloadFile(action, record)
1291
+ }
1292
+ }
1293
+
1294
+ /**
1295
+ * @description 폼 화면에서의 커스텀 버튼에 대한 액션
1296
+ *********************************************
1297
+ * @param {Object} pageView
1298
+ * @param {String} buttonName
1299
+ * @param {Object} logic
1300
+ */
1301
+ static async formScreenCustomButtonHandler(pageView, buttonName, logic) {
1302
+ if (ValueUtil.isEmpty(logic)) return
1303
+
1304
+ // TODO 현재 폼 엘리먼트에 검색 폼 기능이 없으므로 추후 추가 필요 && gristButtonHandler 함수와 거의 동일하게 로직이 처리되어야 하는데 로직이 약간 다름...
1305
+ let formData = pageView.currentData
1306
+ let eventType = ValueUtil.getParams(logic, 'type')
1307
+ let paramInfo = ValueUtil.getParams(logic, 'param')
1308
+ let params = { form: paramInfo && paramInfo.includes('form_changed') ? pageView.patchData() : formData }
1309
+
1310
+ /**
1311
+ * data 정보가 있는 경우 폼 데이터로 부터 아래 구조대로 변형 처리
1312
+ * data: {
1313
+ * "id": "$id",
1314
+ * "name": "$name",
1315
+ * "description": "$description",
1316
+ * "activeFlag": true,
1317
+ * "cuFlag": "M"
1318
+ * }
1319
+ */
1320
+ let dataSchema = ValueUtil.getParams(logic, 'data')
1321
+ // data schema (데이터 형태) 추출
1322
+ if (ValueUtil.isNotEmpty(dataSchema)) {
1323
+ params.form = ValueUtil.replaceVariableObject(dataSchema, formData)
1324
+ }
1325
+
1326
+ // 페이지 이동
1327
+ if (eventType === 'page') {
1328
+ UiUtil.pageNavigate(logic.url, [params.form])
1329
+
1330
+ // 팝업 오픈
1331
+ } else if (eventType === 'popup') {
1332
+ await MetaUiUtil.commonButtonOpenPopup(pageView, logic, params)
1333
+
1334
+ // 서비스 호출
1335
+ } else if (eventType === 'graphql') {
1336
+ await MetaUiUtil.commonButtonCallService(pageView, logic, params.filter, params.form)
1337
+
1338
+ // 시나리오 호출
1339
+ } else if (eventType === 'scenario') {
1340
+ await MetaUiUtil.commonButtonCallScenario(pageView, buttonName, logic, params)
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * @description 그리드 화면에서의 커스텀 버튼에 대한 액션
1346
+ ************************************************
1347
+ * @param {Object} pageView 화면
1348
+ * @param {String} buttonName 버튼 명
1349
+ * @param {Object} logic 버튼 커스텀 로직 JSON
1350
+ */
1351
+ static async gristScreenCustomButtonHandler(pageView, buttonName, logic) {
1352
+ if (ValueUtil.isEmpty(logic)) return
1353
+
1354
+ let grist = pageView.grist
1355
+ let filterForm = pageView.filterForm
1356
+
1357
+ let eventType = ValueUtil.getParams(logic, 'type')
1358
+ let paramInfo = ValueUtil.getParams(logic, 'param')
1359
+ let includeGristParams = paramInfo.filter(p => p.startsWith('grist'))
1360
+
1361
+ // 오류 메시지 처리
1362
+ if (ValueUtil.isNotEmpty(includeGristParams)) {
1363
+ // 그리스트 존재 여부
1364
+ if (!grist) {
1365
+ // 그리드가 없습니다.
1366
+ UiUtil.showAlertPopup('title.info', 'text.grid_is_not_exist', 'info', 'confirm')
1367
+ return
1368
+ }
1369
+
1370
+ // 그리스트 데이터 존재 여부 (편집중 데이터도 체크)
1371
+ if ((grist?.data?.records || []).length == 0 && (grist?.dirtyData?.records || []).length == 0) {
1372
+ // 비어있는 그리드 입니다.
1373
+ UiUtil.showAlertPopup('title.info', 'text.grid_data_is_empty', 'info', 'confirm')
1374
+ return
1375
+ }
1376
+
1377
+ if (paramInfo.includes('grist_one') || paramInfo.includes('grist_selected')) {
1378
+ // 선택된 행이 있는지 확인
1379
+ let selectedRows = grist.selected
1380
+
1381
+ if (ValueUtil.isEmpty(selectedRows)) {
1382
+ // 선택된 항목이 없습니다.
1383
+ UiUtil.showAlertPopup('text.nothing_selected', 'text.there_is_no_selected_items', 'info', 'confirm')
1384
+ return
1385
+ }
1386
+
1387
+ if (paramInfo.includes('grist_one')) {
1388
+ // 하나의 행만 선택 되었는지 확인
1389
+ if (selectedRows.length > 1) {
1390
+ // 하나의 항목만 선택해주세요.
1391
+ UiUtil.showAlertPopup('title.info', 'text.please_select_only_one', 'info', 'confirm')
1392
+ return
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+
1398
+ let params = {}
1399
+
1400
+ if (paramInfo.includes('filter')) {
1401
+ if (!filterForm) {
1402
+ // 서치폼이 없습니다.
1403
+ UiUtil.showAlertPopup('title.info', 'text.search_form_is_not_exist', 'info', 'confirm')
1404
+ return
1405
+ }
1406
+
1407
+ let filterValues = filterForm ? await filterForm.getQueryFilters() : []
1408
+ params['filter'] = filterValues
1409
+ }
1410
+
1411
+ if (paramInfo.includes('grist_all')) {
1412
+ params['grist'] = grist.data.records
1413
+ } else if (paramInfo.includes('grist_one')) {
1414
+ params['grist'] = grist.selected[0]
1415
+ } else if (paramInfo.includes('grist_selected')) {
1416
+ params['grist'] = grist.selected
1417
+ } else if (paramInfo.includes('grist_changed')) {
1418
+ let changedList = ServiceUtil.patchesForUpdateMultiple(grist)
1419
+ if (!changedList) {
1420
+ return
1421
+ }
1422
+
1423
+ // 화면이 팝업 화면이라면 부모 ID를 레코드에 추가한다.
1424
+ let pId = pageView.parent_id ? pageView.parent_id : null
1425
+ let parentIdField = ValueUtil.getParams(logic, 'parent_field')
1426
+
1427
+ changedList.forEach(item => {
1428
+ if (pId && parentIdField && !item[parentIdField]) {
1429
+ item[parentIdField] = pId
1430
+ }
1431
+ })
1432
+
1433
+ params['grist'] = changedList
1434
+ }
1435
+
1436
+ // 페이지 이동
1437
+ if (eventType === 'page') {
1438
+ let records = Array.isArray(params['grist']) ? params['grist'] : [params['grist']]
1439
+ let passParams = logic.param_field ? null : records
1440
+
1441
+ if (logic.param_field) {
1442
+ passParams = records.map(record => {
1443
+ let recordParams = {}
1444
+ Object.keys(record)
1445
+ .filter(x => Object.keys(logic.param_field).includes(x))
1446
+ .map(x => {
1447
+ if (Object.keys(logic.param_field).includes(x)) {
1448
+ let data = record[x]
1449
+ recordParams[logic.param_field[x]] =
1450
+ Array.isArray(data) === false && typeof data === 'object' && data.id ? { id: data.id } : data
1451
+ }
1452
+ })
1453
+ return recordParams
1454
+ })
1455
+ }
1456
+
1457
+ UiUtil.pageNavigate(logic.url, passParams)
1458
+
1459
+ // 팝업 오픈
1460
+ } else if (eventType === 'popup') {
1461
+ let convParam = {
1462
+ ...(params['grist'] ? params['grist'] : {}),
1463
+ ...(params['filter'] ? params['filter'] : {})
1464
+ }
1465
+ await MetaUiUtil.commonButtonOpenPopup(pageView, logic, convParam)
1466
+
1467
+ // 서비스 호출
1468
+ } else if (eventType === 'graphql') {
1469
+ let filterParam = params['filter'] ? params['filter'] : {}
1470
+ let dataParams = params['grist'] ? params['grist'] : filterParam
1471
+ await MetaUiUtil.commonButtonCallService(pageView, logic, filterParam, dataParams)
1472
+
1473
+ // 시나리오 호출
1474
+ } else if (eventType === 'scenario') {
1475
+ await MetaUiUtil.commonButtonCallScenario(pageView, buttonName, logic, params)
1476
+
1477
+ // 첨부파일 다운로드 처리
1478
+ } else if (eventType == 'download' && paramInfo.includes('grist_one')) {
1479
+ MetaUiUtil.downloadFile(logic, params['grist'])
1480
+ }
1481
+ }
1482
+
1483
+ /**
1484
+ * @description 커스텀 버튼에 대한 액션 처리
1485
+ **************************************
1486
+ * @param {Object} pageView 화면
1487
+ * @param {String} buttonName 버튼 명
1488
+ * @param {Object} logic 커스텀 버튼 로직 JSON
1489
+ */
1490
+ static async customButtonHandler(pageView, buttonName, logic) {
1491
+ // 폼 화면의 버튼 핸들링인지 그리드 계열 화면의 버튼 핸들링 인지 구분
1492
+ if (pageView.localName.startsWith('meta-form')) {
1493
+ await MetaUiUtil.formScreenCustomButtonHandler(pageView, buttonName, logic)
1494
+ } else {
1495
+ await MetaUiUtil.gristScreenCustomButtonHandler(pageView, buttonName, logic)
1496
+ }
1497
+ }
1498
+
1499
+ /**
1500
+ * @description 파라미터 전달 버튼 처리
1501
+ **********************************
1502
+ * @param {Object} pageView
1503
+ * @param {Object} action
1504
+ * @param {Object} record
1505
+ */
1506
+ static gristButtonPassParam(pageView, action, record) {
1507
+ // 파라미터 전달 정보
1508
+ let passInfoAll = action.pass_field
1509
+ let elementTagName = pageView.tagName.toLowerCase()
1510
+ let pageElement = pageView
1511
+ let passInfo =
1512
+ elementTagName.startsWith('meta-grist-tab-') || elementTagName.startsWith('meta-master-detail-')
1513
+ ? passInfoAll.detail
1514
+ ? { ...passInfoAll } // 그리스트 탭 페이지는 하단으로 보내는 이벤트만 허용
1515
+ : { detail: { ...passInfoAll } }
1516
+ : { ...passInfoAll } // tab+tab or tab+etc or main-tab
1517
+
1518
+ Object.keys(passInfo).forEach(tab => {
1519
+ if (tab == 'master') {
1520
+ pageElement = pageView.rootElement.masterElement
1521
+ } else if (tab == 'detail') {
1522
+ pageElement = pageView.rootElement ? pageView.rootElement.detailElement : pageView.detailElement
1523
+ } else if (tab == 'self') {
1524
+ pageElement = pageView
1525
+ }
1526
+
1527
+ if (pageElement) {
1528
+ if (pageElement.tagName.toLowerCase().startsWith('meta-tab-element')) {
1529
+ // Object Key로 Element 검색.
1530
+ Object.keys(passInfo[tab]).forEach(elementId => {
1531
+ let targetElement = pageElement.shadowRoot.querySelector(`#${elementId}`)
1532
+ // 상위 데이터 ID
1533
+ if (record.id) {
1534
+ targetElement.setParentId(record.id)
1535
+ }
1536
+
1537
+ // 대상 엘리먼트에 매치할 필드 정보
1538
+ let matchInfo = passInfo[tab][elementId]
1539
+
1540
+ // 필드 정보 Loop tField : target , sField : source
1541
+ Object.keys(matchInfo).forEach(tField => {
1542
+ let sField = matchInfo[tField]
1543
+ // param 값이 * 이면 record 전체
1544
+ let param = sField === '*' ? record : record[sField]
1545
+ // tElement.tField에 파라미터 set
1546
+ targetElement[tField] = param
1547
+ })
1548
+ })
1549
+ } else {
1550
+ let targetElement = pageElement
1551
+ // 상위 데이터 ID
1552
+ if (record.id) {
1553
+ targetElement.setParentId(record.id)
1554
+ }
1555
+
1556
+ // 대상 엘리먼트에 매치할 필드 정보
1557
+ let matchInfo = passInfo[tab]
1558
+
1559
+ // 필드 정보 Loop tField : target , sField : source
1560
+ Object.keys(matchInfo).forEach(tField => {
1561
+ let sField = matchInfo[tField]
1562
+ // param 값이 * 이면 record 전체
1563
+ let param = sField === '*' ? record : record[sField]
1564
+ // tElement.tField에 파라미터 set
1565
+ targetElement[tField] = param
1566
+ })
1567
+ }
1568
+ }
1569
+ })
1570
+ }
1571
+
1572
+ /**
1573
+ * @description 팝업 오픈 버튼 처리
1574
+ ********************************
1575
+ * @param {Object} pageView
1576
+ * @param {Object} action
1577
+ * @param {Object} record
1578
+ */
1579
+ static async commonButtonOpenPopup(pageView, action, record) {
1580
+ // 타이틀 변환
1581
+ let title = TermsUtil.tTitle(action.title)
1582
+
1583
+ // 타이틀 상세 필드 정의 확인
1584
+ if (ValueUtil.isNotEmpty(action.title_detail)) {
1585
+ title = `${title}(${ValueUtil.getParams(record, ...action.title_detail.split('.'))})`
1586
+ }
1587
+
1588
+ // closeCallback
1589
+ let closeCallback =
1590
+ action.after && typeof action.after === 'string' && action.after === 'fetch' && pageView && pageView.fetch
1591
+ ? pageView.fetch.bind(pageView)
1592
+ : undefined
1593
+
1594
+ // 팝업 오픈
1595
+ UiUtil.openDynamicPopup(title, action, record, closeCallback)
1596
+ }
1597
+
1598
+ /**
1599
+ * @description 서비스 호출 버튼 처리
1600
+ *************************************
1601
+ * @param {Object} pageView 페이지 뷰
1602
+ * @param {Object} action 액션
1603
+ * @param {Object or Array} filterParam 필터 파라미터
1604
+ * @param {Object or Array} records 선택 레코드
1605
+ */
1606
+ static async commonButtonCallService(pageView, action, filterParam, records) {
1607
+ // Graphql 서비스 호출
1608
+ let response = await ServiceUtil.callService(pageView, action, filterParam, records)
1609
+
1610
+ // 후 처리 액션 처리
1611
+ if (!response.errors && action.after && ValueUtil.isNotEmpty(action.after)) {
1612
+ let afterParams = null
1613
+
1614
+ if (typeof action.after === 'object') {
1615
+ afterParams = Array.isArray(response) ? response : [response]
1616
+ }
1617
+
1618
+ await MetaUiUtil.afterActionHandler(pageView, action, afterParams)
1619
+ }
1620
+ }
1621
+
1622
+ /**
1623
+ * @description 시나리오 호출 버튼 처리
1624
+ ***********************************
1625
+ * @param {Object} pageView
1626
+ * @param {String} buttonName
1627
+ * @param {Object} action
1628
+ * @param {Object or Array} params
1629
+ */
1630
+ static async commonButtonCallScenario(pageView, buttonName, action, params) {
1631
+ // 시나리오 호출
1632
+ let response = await ServiceUtil.callScenario(buttonName, action.name, params)
1633
+
1634
+ // 처리 중 에러가 발생했거나 시나리오 호출 후 처리가 없으면 return
1635
+ if (!response.errors && action.after && ValueUtil.isNotEmpty(action.after)) {
1636
+ let afterParams = null
1637
+
1638
+ // after가 오브젝트이면 후 처리 로직
1639
+ if (typeof action.after === 'object') {
1640
+ let after = action.after
1641
+ afterParams = ValueUtil.getParams(response.data.runScenario.result, ...after.result_name.split('.'))
1642
+
1643
+ if (Array.isArray(afterParams) == false) {
1644
+ afterParams = [afterParams]
1645
+ }
1646
+ }
1647
+
1648
+ // 후 처리 처리
1649
+ await MetaUiUtil.afterActionHandler(pageView, action, afterParams)
1650
+ }
1651
+ }
1652
+
1653
+ /**
1654
+ * @description 버튼 처리 후 처리
1655
+ *******************************
1656
+ * @param {Object} pageView
1657
+ * @param {Object} action
1658
+ * @param {Object or Array} afterParams
1659
+ */
1660
+ static async afterActionHandler(pageView, action, afterParams) {
1661
+ // after가 오브젝트이면 후 처리 로직
1662
+ if (typeof action.after === 'object') {
1663
+ let after = action.after
1664
+
1665
+ // 상세 폼 뷰 팝업 실행
1666
+ if (after.type === 'form') {
1667
+ after.tagname = 'meta-form-element'
1668
+ after.location = 'pages/meta-form-element.js'
1669
+ after.parent_field = 'id'
1670
+ afterParams = afterParams[0]
1671
+ await MetaUiUtil.commonButtonOpenPopup(pageView, after, afterParams)
1672
+
1673
+ // 페이지 이동 처리
1674
+ } else if (after.type === 'page') {
1675
+ let records = afterParams
1676
+ let passParams = after.param_field ? null : records
1677
+
1678
+ if (after.param_field) {
1679
+ passParams = records.map(record => {
1680
+ let recordParams = {}
1681
+ Object.keys(record)
1682
+ .filter(x => Object.keys(after.param_field).includes(x))
1683
+ .map(x => {
1684
+ if (Object.keys(after.param_field).includes(x)) {
1685
+ let data = record[x]
1686
+ recordParams[after.param_field[x]] =
1687
+ Array.isArray(data) === false && typeof data === 'object' && data.id ? { id: data.id } : data
1688
+ }
1689
+ })
1690
+ return recordParams
1691
+ })
1692
+ }
1693
+
1694
+ UiUtil.pageNavigate(after.url, passParams)
1695
+
1696
+ // 다른 객체로 파라미터 전달
1697
+ } else if (after.type === 'pass_param') {
1698
+ afterParams = afterParams[0]
1699
+ MetaUiUtil.gristButtonPassParam(pageView, after, afterParams)
1700
+
1701
+ // 팝업 오픈
1702
+ } else if (after.type === 'popup') {
1703
+ afterParams = afterParams[0]
1704
+ await MetaUiUtil.commonButtonOpenPopup(pageView, after, afterParams)
1705
+
1706
+ // 설정된 조건에 따라 커스텀 로직 처리
1707
+ } else if (after.type === 'value_reference') {
1708
+ afterParams = afterParams[0]
1709
+ await MetaUiUtil.gristButtonValueReference(pageView, after, afterParams)
1710
+ }
1711
+
1712
+ // after가 fetch이면 리프레쉬
1713
+ } else if (action.after === 'fetch') {
1714
+ pageView.fetch()
1715
+
1716
+ // after가 close이면 팝업 닫기
1717
+ } else if (action.after == 'close') {
1718
+ UiUtil.closePopupBy(pageView)
1719
+ }
1720
+ }
1721
+
1722
+ /**
1723
+ * @description UI에서 파일 다운로드 처리
1724
+ *************************************
1725
+ * @param {Object} config 설정
1726
+ * @param {Object} record 레코드
1727
+ */
1728
+ static downloadFile(config, record) {
1729
+ let attachmentRoot = config.attachment_root
1730
+ let attachColumn = config.attachment_column
1731
+ let downloadColumn = config.download_name_column ? config.download_name_column : null
1732
+
1733
+ if (!attachColumn) {
1734
+ throw new Error('attachmentColumn 정보가 없습니다.')
1735
+ }
1736
+
1737
+ let attachColumns = attachColumn.split('.')
1738
+ let fileData = record[attachColumns[0]]
1739
+ let fileName = attachColumns.length == 1 ? fileData : fileData[attachColumns[1]]
1740
+ let downloadName = downloadColumn ? record[downloadColumn] : fileName
1741
+
1742
+ const link = document.createElement('a')
1743
+ link.setAttribute('href', `./${attachmentRoot}/${fileName}`)
1744
+ link.setAttribute('download', downloadName)
1745
+ document.body.appendChild(link)
1746
+ link.click()
1747
+ link.parentNode.removeChild(link)
1748
+ }
1749
+
1750
+ /**
1751
+ * @description 설정된 조건과 record 의 값을 비교. 로직 처리
1752
+ ****************************************************
1753
+ * @param {Object} pageView
1754
+ * @param {Object} config
1755
+ * @param {Object} record
1756
+ */
1757
+ static async gristButtonValueReference(pageView, config, record) {
1758
+ // 지정된 조건 Loop
1759
+ for (let idx = 0; idx < config.relation.length; idx++) {
1760
+ let relation = config.relation[idx]
1761
+ let useRelation = ValueUtil.compareObjectValues(
1762
+ relation,
1763
+ record,
1764
+ Object.keys(relation).filter(key => key != 'logic')
1765
+ )
1766
+ if (useRelation === true) {
1767
+ // 조건 체크 후 버튼 핸들러 호출
1768
+ await MetaUiUtil.gristButtonHandler(pageView, relation, record)
1769
+ }
1770
+ }
1771
+ }
1772
+
1773
+ /**
1774
+ * @description 그리스트 Hidden 컬럼 설정
1775
+ *************************************
1776
+ * @param {String} type
1777
+ * @param {String} name
1778
+ * @returns {Object} 그리스트 Hidden 컬럼 설정
1779
+ */
1780
+ static getGristHiddenColumnConfig(type, name) {
1781
+ return {
1782
+ type: type,
1783
+ name: name,
1784
+ sortable: false,
1785
+ hidden: true
1786
+ }
1787
+ }
1788
+
1789
+ /**
1790
+ * @description 그리스트 기본 컬럼 설정
1791
+ ***********************************
1792
+ * @param {String} type
1793
+ * @param {String} name
1794
+ * @param {String} align
1795
+ * @param {Boolean} editable
1796
+ * @param {Boolean} sortable
1797
+ * @param {Number} width
1798
+ * @returns {Object} 그리스트 기본 컬럼 설정
1799
+ */
1800
+ static getGristColumnConfig(type, name, align, editable, sortable, width) {
1801
+ let dispName = TermsUtil.tLabel(name)
1802
+ return {
1803
+ type: type,
1804
+ name: name,
1805
+ header: dispName,
1806
+ record: {
1807
+ align: align,
1808
+ editable: editable
1809
+ },
1810
+ sortable: sortable,
1811
+ width: width,
1812
+ imex: {
1813
+ width: Math.floor(width / 6),
1814
+ header: dispName,
1815
+ type: type,
1816
+ key: name
1817
+ }
1818
+ }
1819
+ }
1820
+
1821
+ /**
1822
+ * @description 그리스트 컬럼 설정
1823
+ *******************************
1824
+ * @param {String} type
1825
+ * @param {String} name
1826
+ * @param {String} displayName
1827
+ * @param {String} align
1828
+ * @param {Boolean} editable
1829
+ * @param {Boolean} sortable
1830
+ * @param {Number} width
1831
+ * @returns {Object} 그리스트 기본 컬럼 설정
1832
+ */
1833
+ static getGristColumnConfig2(type, name, displayName, align, editable, sortable, width) {
1834
+ let dispName = TermsUtil.tLabel(displayName)
1835
+ return {
1836
+ type: type,
1837
+ name: name,
1838
+ header: dispName,
1839
+ record: {
1840
+ align: align,
1841
+ editable: editable
1842
+ },
1843
+ sortable: sortable,
1844
+ width: width,
1845
+ imex: {
1846
+ width: Math.floor(width / 6),
1847
+ header: dispName,
1848
+ type: type,
1849
+ key: name
1850
+ }
1851
+ }
1852
+ }
1853
+
1854
+ /**
1855
+ * @description 그리스트 컬럼 설정
1856
+ *******************************
1857
+ * @param {String} type
1858
+ * @param {String} name
1859
+ * @param {String} displayName
1860
+ * @param {String} align
1861
+ * @param {Boolean} editable
1862
+ * @param {Boolean} sortable
1863
+ * @param {Boolean} mandatory
1864
+ * @param {Number} width
1865
+ * @returns {Object} 그리스트 기본 컬럼 설정
1866
+ */
1867
+ static getGristColumnConfig3(type, name, displayName, align, editable, sortable, mandatory, width) {
1868
+ let dispName = TermsUtil.tLabel(displayName)
1869
+ return {
1870
+ type: type,
1871
+ name: name,
1872
+ header: dispName,
1873
+ record: {
1874
+ align: align,
1875
+ editable: editable,
1876
+ mandatory: mandatory
1877
+ },
1878
+ sortable: sortable,
1879
+ width: width,
1880
+ imex: {
1881
+ width: Math.floor(width / 6),
1882
+ header: dispName,
1883
+ type: type,
1884
+ key: name
1885
+ }
1886
+ }
1887
+ }
1888
+
1889
+ /**
1890
+ * @description 그리스트 Selector 컬럼 설정
1891
+ ***************************************
1892
+ * @param {String} name
1893
+ * @param {String} displayName
1894
+ * @param {String} align
1895
+ * @param {Boolean} sortable
1896
+ * @param {Number} width
1897
+ * @param {Boolean} mandatory
1898
+ * @param {Array} optionValues
1899
+ * @returns {Object} 그리스트 기본 컬럼 설정
1900
+ */
1901
+ static getGristSelectorColumnConfig(name, displayName, align, sortable, width, mandatory, optionValues) {
1902
+ let dispName = TermsUtil.tLabel(displayName)
1903
+ return {
1904
+ type: 'select',
1905
+ name: name,
1906
+ header: dispName,
1907
+ record: {
1908
+ align: align,
1909
+ editable: true,
1910
+ mandatory: mandatory,
1911
+ options: optionValues
1912
+ },
1913
+ sortable: sortable,
1914
+ width: width,
1915
+ imex: {
1916
+ width: Math.floor(width / 6),
1917
+ header: dispName,
1918
+ type: 'string',
1919
+ key: name
1920
+ }
1921
+ }
1922
+ }
1923
+
1924
+ /**
1925
+ * @description 그리스트 Code Selector 컬럼 설정
1926
+ ********************************************
1927
+ * @param {String} name
1928
+ * @param {String} displayName
1929
+ * @param {String} align
1930
+ * @param {Boolean} sortable
1931
+ * @param {Number} width
1932
+ * @param {Boolean} mandatory
1933
+ * @param {String} codeName
1934
+ * @returns {Object} 그리스트 기본 컬럼 설정
1935
+ */
1936
+ static async getGristCodeSelectorColumnConfig(name, displayName, align, sortable, width, mandatory, codeName) {
1937
+ let optionValue = await ServiceUtil.getCodeSelectorData(codeName)
1938
+ return MetaUiUtil.getGristSelectorColumnConfig(name, displayName, align, sortable, width, mandatory, optionValue)
1939
+ }
1940
+
1941
+ /**
1942
+ * @description 그리스트 검색 편집기 설정
1943
+ ************************************
1944
+ * @param {String} name
1945
+ * @param {String} type
1946
+ * @param {String} label
1947
+ * @param {String} operator
1948
+ * @param {Array} optionValues
1949
+ * @returns {Object} 그리스트 검색 편집기 설정
1950
+ */
1951
+ static getGristSearchColumnConfig(name, type, label, operator, optionValues) {
1952
+ let dispName = TermsUtil.tLabel(label)
1953
+ let column = {
1954
+ name: name,
1955
+ type: type,
1956
+ label: dispName,
1957
+ operator: operator,
1958
+ imex: {
1959
+ width: 16,
1960
+ header: dispName,
1961
+ type: type,
1962
+ key: name
1963
+ }
1964
+ }
1965
+
1966
+ if (optionValues) {
1967
+ column.options = optionValues
1968
+ }
1969
+
1970
+ return column
1971
+ }
1972
+
1973
+ /**
1974
+ * @description 그리스트 검색 코드 편집기 설정
1975
+ ***************************************
1976
+ * @param {String} name 검색 필드 명
1977
+ * @param {String} type 검색 편집기 유형
1978
+ * @param {String} label 검색 필드 라벨명
1979
+ * @param {String} operator 검색 연산자
1980
+ * @param {String} codeName 코드 이름
1981
+ * @returns {Object} 그리스트 검색 코드 편집기 설정
1982
+ */
1983
+ static async getGristSearchCodeColumnConfig(name, type, label, operator, codeName) {
1984
+ let optionValues = await ServiceUtil.getCodeSelectorData(codeName)
1985
+ return MetaUiUtil.getGristSearchColumnConfig(name, type, label, operator, optionValues)
1986
+ }
1987
+
1988
+ /**
1989
+ * @description 기본 컨텍스트 버튼이 아닌 커스텀 버튼 컨테이너 스타일
1990
+ *********************************************************
1991
+ * @returns {Array} 커스텀 버튼 컨테이너 CSS 리턴
1992
+ */
1993
+ static getCustomButtonContainerStyles() {
1994
+ return [ButtonContainerStyles]
1995
+ }
1996
+
1997
+ /**
1998
+ * @description 그리스트 강조 스타일 리턴
1999
+ ************************************
2000
+ * @returns {Array} 그리스트 강조 스타일 CSS
2001
+ */
2002
+ static getGristEmphasizedStyles() {
2003
+ return [
2004
+ css`
2005
+ :host {
2006
+ --grid-record-emphasized-background-color: red;
2007
+ --grid-record-emphasized-color: yellow;
2008
+ }
2009
+ `
2010
+ ]
2011
+ }
2012
+
2013
+ /**
2014
+ * @description 기본 그리스트 스타일 리턴
2015
+ ************************************
2016
+ * @returns {Array} 기본 그리스트 스타일 CSS 리턴
2017
+ */
2018
+ static getBasicGristStyles() {
2019
+ return [
2020
+ ScrollbarStyles,
2021
+ CommonGristStyles,
2022
+ CommonHeaderStyles,
2023
+ css`
2024
+ :host {
2025
+ display: flex;
2026
+ flex-direction: column;
2027
+ overflow-x: overlay;
2028
+ background-color: var(--md-sys-color-background);
2029
+ }
2030
+
2031
+ ox-grist {
2032
+ overflow-y: auto;
2033
+ flex: 1;
2034
+ }
2035
+
2036
+ ox-filters-form {
2037
+ flex: 1;
2038
+ }
2039
+ `,
2040
+ ...MetaUiUtil.getGristEmphasizedStyles(),
2041
+ ...MetaUiUtil.getCustomButtonContainerStyles()
2042
+ ]
2043
+ }
2044
+
2045
+ /**
2046
+ * @description 기본 그리스트 탭 스타일 리턴
2047
+ **************************************
2048
+ * @returns {Array} 기본 그리스트 탭 스타일 CSS 리턴
2049
+ */
2050
+ static getBasicGristTabStyles() {
2051
+ return [
2052
+ ScrollbarStyles,
2053
+ CommonGristStyles,
2054
+ CommonHeaderStyles,
2055
+ css`
2056
+ :host {
2057
+ display: flex;
2058
+ flex-direction: column;
2059
+ overflow-x: overlay;
2060
+ background-color: var(--md-sys-color-background);
2061
+ }
2062
+
2063
+ .container {
2064
+ flex: 1;
2065
+ display: grid;
2066
+ overflow: hidden;
2067
+ }
2068
+
2069
+ .container_detail {
2070
+ background-color: var(--md-sys-color-background);
2071
+ display: flex;
2072
+ flex-direction: column;
2073
+ flex: 1;
2074
+ overflow-y: auto;
2075
+ }
2076
+
2077
+ h2 {
2078
+ padding: var(--subtitle-padding);
2079
+ font: var(--subtitle-font);
2080
+ color: var(--subtitle-text-color);
2081
+ border-bottom: var(--subtitle-border-bottom);
2082
+ }
2083
+
2084
+ .container_detail h2 {
2085
+ margin: var(--grist-title-margin);
2086
+ border: var(--grist-title-border);
2087
+ font: var(--grist-title-font);
2088
+ color: var(--md-sys-color-secondary);
2089
+ }
2090
+
2091
+ .container_detail h2 md-icon {
2092
+ --md-icon-size: var(--grist-title-icon-size);
2093
+ vertical-align: middle;
2094
+ margin: var(--grist-title-icon-margin);
2095
+ color: var(--grist-title-icon-color);
2096
+ }
2097
+
2098
+ h2 {
2099
+ padding-bottom: var(--grist-title-with-grid-padding);
2100
+ }
2101
+
2102
+ ox-grist {
2103
+ overflow-y: auto;
2104
+ flex: 1;
2105
+ }
2106
+
2107
+ ox-filters-form {
2108
+ flex: 1;
2109
+ }
2110
+ `,
2111
+ ...MetaUiUtil.getGristEmphasizedStyles(),
2112
+ ...MetaUiUtil.getCustomButtonContainerStyles()
2113
+ ]
2114
+ }
2115
+
2116
+ /**
2117
+ * @description 기본 마스터 디테일 스타일 리턴
2118
+ ****************************************
2119
+ * @returns {Array} 기본 마스터 디테일 스타일 CSS 리턴
2120
+ */
2121
+ static getBasicMasterDetailStyles() {
2122
+ return [
2123
+ ScrollbarStyles,
2124
+ CommonGristStyles,
2125
+ CommonHeaderStyles,
2126
+ css`
2127
+ :host {
2128
+ display: flex;
2129
+ flex-direction: column;
2130
+ overflow-x: overlay;
2131
+ background-color: var(--md-sys-color-background);
2132
+ }
2133
+
2134
+ .container {
2135
+ flex: 1;
2136
+ display: grid;
2137
+ overflow: hidden;
2138
+ }
2139
+
2140
+ .container_detail {
2141
+ background-color: var(--md-sys-color-background);
2142
+ display: flex;
2143
+ flex-direction: column;
2144
+ flex: 1;
2145
+ overflow-y: auto;
2146
+ }
2147
+
2148
+ h2 {
2149
+ padding: var(--subtitle-padding);
2150
+ font: var(--subtitle-font);
2151
+ color: var(--subtitle-text-color);
2152
+ border-bottom: var(--subtitle-border-bottom);
2153
+ }
2154
+
2155
+ .container_detail h2 {
2156
+ margin: var(--grist-title-margin);
2157
+ border: var(--grist-title-border);
2158
+ font: var(--grist-title-font);
2159
+ color: var(--md-sys-color-secondary);
2160
+ }
2161
+
2162
+ .container_detail h2 md-icon {
2163
+ --md-icon-size: var(--grist-title-icon-size);
2164
+ vertical-align: middle;
2165
+ margin: var(--grist-title-icon-margin);
2166
+ color: var(--grist-title-icon-color);
2167
+ }
2168
+
2169
+ h2 {
2170
+ padding-bottom: var(--grist-title-with-grid-padding);
2171
+ }
2172
+
2173
+ ox-grist {
2174
+ overflow-y: auto;
2175
+ flex: 1;
2176
+ }
2177
+
2178
+ ox-filters-form {
2179
+ flex: 1;
2180
+ }
2181
+ `,
2182
+ ...MetaUiUtil.getGristEmphasizedStyles(),
2183
+ ...MetaUiUtil.getCustomButtonContainerStyles()
2184
+ ]
2185
+ }
2186
+
2187
+ /**
2188
+ * @description 기본 TAB 디테일 스타일 리턴
2189
+ **************************************
2190
+ * @returns {Array} 기본 TAB 디테일 스타일 CSS 리턴
2191
+ */
2192
+ static getBasicTabDetailStyles() {
2193
+ return [
2194
+ ScrollbarStyles,
2195
+ CommonGristStyles,
2196
+ CommonHeaderStyles,
2197
+ css`
2198
+ :host {
2199
+ display: flex;
2200
+ flex-direction: column;
2201
+ overflow-x: overlay;
2202
+ background-color: var(--md-sys-color-background);
2203
+ }
2204
+
2205
+ .container {
2206
+ flex: 1;
2207
+ display: grid;
2208
+ overflow: hidden;
2209
+ }
2210
+
2211
+ .container_detail {
2212
+ background-color: var(--md-sys-color-background);
2213
+ display: flex;
2214
+ flex-direction: column;
2215
+ flex: 1;
2216
+ overflow-y: auto;
2217
+ }
2218
+
2219
+ h2 {
2220
+ padding: var(--subtitle-padding);
2221
+ font: var(--subtitle-font);
2222
+ color: var(--subtitle-text-color);
2223
+ border-bottom: var(--subtitle-border-bottom);
2224
+ }
2225
+
2226
+ .container_detail h2 {
2227
+ margin: var(--grist-title-margin);
2228
+ border: var(--grist-title-border);
2229
+ font: var(--grist-title-font);
2230
+ color: var(--md-sys-color-secondary);
2231
+ }
2232
+
2233
+ .container_detail h2 md-icon {
2234
+ --md-icon-size: var(--grist-title-icon-size);
2235
+ vertical-align: middle;
2236
+ margin: var(--grist-title-icon-margin);
2237
+ color: var(--grist-title-icon-color);
2238
+ }
2239
+
2240
+ h2 {
2241
+ padding-bottom: var(--grist-title-with-grid-padding);
2242
+ }
2243
+
2244
+ ox-grist {
2245
+ overflow-y: auto;
2246
+ flex: 1;
2247
+ }
2248
+
2249
+ ox-filters-form {
2250
+ flex: 1;
2251
+ }
2252
+ `,
2253
+ ...MetaUiUtil.getGristEmphasizedStyles(),
2254
+ ...MetaUiUtil.getCustomButtonContainerStyles()
2255
+ ]
2256
+ }
2257
+
2258
+ /**
2259
+ * @description 기본 MAIN TAB 스타일 리턴
2260
+ **************************************
2261
+ * @returns {Array} 기본 MAIN TAB 스타일 CSS 리턴
2262
+ */
2263
+ static getBasicMainTabStyles() {
2264
+ return [
2265
+ ScrollbarStyles,
2266
+ CommonGristStyles,
2267
+ CommonHeaderStyles,
2268
+ css`
2269
+ :host {
2270
+ display: flex;
2271
+ flex-direction: column;
2272
+ overflow-x: overlay;
2273
+ background-color: var(--md-sys-color-background);
2274
+ }
2275
+
2276
+ .container {
2277
+ flex: 1;
2278
+ display: grid;
2279
+ overflow: hidden;
2280
+ }
2281
+
2282
+ ox-grist {
2283
+ overflow-y: auto;
2284
+ flex: 1;
2285
+ }
2286
+
2287
+ ox-filters-form {
2288
+ flex: 1;
2289
+ }
2290
+ `,
2291
+ ...MetaUiUtil.getGristEmphasizedStyles(),
2292
+ ...MetaUiUtil.getCustomButtonContainerStyles()
2293
+ ]
2294
+ }
2295
+
2296
+ /**
2297
+ * @description 기본 마스터 디테일 그리스트 스타일 리턴
2298
+ *********************************************************
2299
+ * @param {String} masterDetailType top-down / left-right
2300
+ * @returns {Array} 마스터 디테일 그리스트 스타일 CSS
2301
+ */
2302
+ static getBasicMasterDetailGristStyle(masterDetailType) {
2303
+ if (masterDetailType == 'top-down') {
2304
+ return MetaUiUtil.getMasterDetailTopDownGristStyle()
2305
+ } else {
2306
+ return MetaUiUtil.getMasterDetailLeftRightGristStyle()
2307
+ }
2308
+ }
2309
+
2310
+ /**
2311
+ * @description top-down 형태의 마스터 디테일 그리스트 스타일 리턴
2312
+ ************************************************************
2313
+ * @returns {Array} top-down 형태의 마스터 디테일 그리스트 스타일 CSS
2314
+ */
2315
+ static getMasterDetailTopDownGristStyle() {
2316
+ return [
2317
+ ButtonContainerStyles,
2318
+ CommonGristStyles,
2319
+ CommonHeaderStyles,
2320
+ ScrollbarStyles,
2321
+ css`
2322
+ :host {
2323
+ display: flex;
2324
+ flex-direction: column;
2325
+ overflow-x: auto;
2326
+ background-color: var(--md-sys-color-background);
2327
+ }
2328
+
2329
+ .container {
2330
+ flex: 1;
2331
+ display: grid;
2332
+ overflow: hidden;
2333
+ grid-template-rows: 50% 50%;
2334
+ }
2335
+
2336
+ .container_detail {
2337
+ background-color: var(--md-sys-color-background);
2338
+ display: flex;
2339
+ flex-direction: column;
2340
+ flex: 1;
2341
+ overflow-y: auto;
2342
+ }
2343
+
2344
+ h2 {
2345
+ padding: var(--subtitle-padding);
2346
+ font: var(--subtitle-font);
2347
+ color: var(--subtitle-text-color);
2348
+ border-bottom: var(--subtitle-border-bottom);
2349
+ }
2350
+
2351
+ .container_detail h2 {
2352
+ margin: var(--grist-title-margin);
2353
+ border: var(--grist-title-border);
2354
+ font: var(--grist-title-font);
2355
+ color: var(--md-sys-color-secondary);
2356
+ }
2357
+
2358
+ .container_detail h2 md-icon {
2359
+ --md-icon-size: var(--grist-title-icon-size);
2360
+ vertical-align: middle;
2361
+ margin: var(--grist-title-icon-margin);
2362
+ color: var(--grist-title-icon-color);
2363
+ }
2364
+
2365
+ h2 {
2366
+ padding-bottom: var(--grist-title-with-grid-padding);
2367
+ }
2368
+
2369
+ ox-grist {
2370
+ overflow-y: auto;
2371
+ flex: 1;
2372
+ }
2373
+
2374
+ ox-filters-form {
2375
+ flex: 1;
2376
+ }
2377
+ `
2378
+ ]
2379
+ }
2380
+
2381
+ /**
2382
+ * @description left-rigth 형태의 마스터 디테일 그리스트 스타일 리턴
2383
+ **************************************************************
2384
+ * @returns {Array} left-rigth 형태의 마스터 디테일 그리스트 스타일 CSS
2385
+ */
2386
+ static getMasterDetailLeftRightGristStyle() {
2387
+ return [
2388
+ ButtonContainerStyles,
2389
+ CommonGristStyles,
2390
+ CommonHeaderStyles,
2391
+ ScrollbarStyles,
2392
+ css`
2393
+ :host {
2394
+ display: flex;
2395
+ flex-direction: column;
2396
+ overflow-x: auto;
2397
+ background-color: var(--md-sys-color-background);
2398
+ }
2399
+
2400
+ .container {
2401
+ flex: 1;
2402
+ display: grid;
2403
+ overflow: hidden;
2404
+ grid-template-columns: 50% 50%;
2405
+ }
2406
+
2407
+ .container_detail {
2408
+ background-color: var(--md-sys-color-background);
2409
+ display: flex;
2410
+ flex-direction: column;
2411
+ flex: 1;
2412
+ overflow-y: auto;
2413
+ }
2414
+
2415
+ h2 {
2416
+ padding: var(--subtitle-padding);
2417
+ font: var(--subtitle-font);
2418
+ color: var(--subtitle-text-color);
2419
+ border-bottom: var(--subtitle-border-bottom);
2420
+ }
2421
+
2422
+ .container_detail h2 {
2423
+ margin: var(--grist-title-margin);
2424
+ border: var(--grist-title-border);
2425
+ font: var(--grist-title-font);
2426
+ color: var(--md-sys-color-secondary);
2427
+ }
2428
+
2429
+ .container_detail h2 md-icon {
2430
+ --md-icon-size: var(--grist-title-icon-size);
2431
+ vertical-align: middle;
2432
+ margin: var(--grist-title-icon-margin);
2433
+ color: var(--grist-title-icon-color);
2434
+ }
2435
+
2436
+ h2 {
2437
+ padding-bottom: var(--grist-title-with-grid-padding);
2438
+ }
2439
+
2440
+ ox-grist {
2441
+ overflow-y: auto;
2442
+ flex: 1;
2443
+ }
2444
+
2445
+ ox-filters-form {
2446
+ flex: 1;
2447
+ }
2448
+ `
2449
+ ]
2450
+ }
2451
+
2452
+ /**
2453
+ * @description 기본 폼 스타일 리턴
2454
+ ************************************
2455
+ * @returns {Array} 기본 폼 스타일 CSS 리턴
2456
+ */
2457
+ static getBasicFormStyles() {
2458
+ return [
2459
+ ScrollbarStyles,
2460
+ CommonHeaderStyles,
2461
+ css`
2462
+ :host {
2463
+ display: flex;
2464
+ flex: 1;
2465
+ flex-direction: column;
2466
+ overflow-x: overlay;
2467
+ background-color: var(--md-sys-color-background);
2468
+ }
2469
+
2470
+ .container {
2471
+ display: grid;
2472
+ grid-auto-rows: min-content;
2473
+ grid-gap: var(--record-view-gap);
2474
+ padding: var(--record-view-padding);
2475
+ overflow-y: auto;
2476
+ height: 100%;
2477
+ border-bottom: var(--record-view-border-bottom);
2478
+ }
2479
+
2480
+ .root_container {
2481
+ display: flex;
2482
+ flex: 1;
2483
+ flex-direction: column;
2484
+ padding: var(--record-view-padding);
2485
+ overflow-y: auto;
2486
+ height: 100%;
2487
+ border-bottom: var(--record-view-border-bottom);
2488
+ }
2489
+
2490
+ .content_container {
2491
+ display: grid;
2492
+ grid-auto-rows: min-content;
2493
+ grid-gap: var(--record-view-gap);
2494
+ padding: var(--record-view-padding);
2495
+ }
2496
+
2497
+ h2 {
2498
+ border: var(--grist-title-border);
2499
+ font: var(--grist-title-font);
2500
+ color: var(--md-sys-color-secondary);
2501
+ margin: var(--grist-title-margin);
2502
+ padding-bottom: var(--grist-title-with-grid-padding);
2503
+ padding-top: var(--grist-title-with-grid-padding);
2504
+ border-bottom: var(--subtitle-border-bottom);
2505
+ }
2506
+
2507
+ h2 md-icon {
2508
+ --md-icon-size: var(--grist-title-icon-size);
2509
+ vertical-align: middle;
2510
+ margin: var(--grist-title-icon-margin);
2511
+ color: var(--grist-title-icon-color);
2512
+ }
2513
+
2514
+ label {
2515
+ display: flex;
2516
+ align-items: center;
2517
+ position: relative;
2518
+ text-transform: capitalize;
2519
+
2520
+ padding: var(--record-view-item-padding);
2521
+ border-bottom: var(--record-view-border-bottom);
2522
+ font: var(--record-view-label-font);
2523
+ color: var(--record-view-label-color);
2524
+ }
2525
+
2526
+ label md-icon {
2527
+ display: none;
2528
+ }
2529
+
2530
+ label[editable] md-icon {
2531
+ --md-icon-size: var(--record-view-label-icon-size);
2532
+ display: inline-block;
2533
+ opacity: 0.5;
2534
+ }
2535
+
2536
+ ox-grid-field {
2537
+ border-top: none;
2538
+ border-bottom: var(--record-view-border-bottom);
2539
+ font: var(--record-view-font);
2540
+ color: var(--record-view-color);
2541
+ background-color: transparent;
2542
+ margin-right: 10px;
2543
+ }
2544
+
2545
+ ox-grid-field[editing='true'] {
2546
+ border-bottom: var(--record-view-edit-border-bottom);
2547
+ }
2548
+ `,
2549
+ ...MetaUiUtil.getCustomButtonContainerStyles()
2550
+ ]
2551
+ }
2552
+
2553
+ /**
2554
+ * @description 기본 탭 스타일 리턴
2555
+ ************************************
2556
+ * @returns {Array} 기본 탭 스타일 CSS 리턴
2557
+ */
2558
+ static getBasicTabStyles() {
2559
+ return [
2560
+ css`
2561
+ :host {
2562
+ display: flex;
2563
+ flex: 1;
2564
+ flex-direction: column;
2565
+ overflow-x: overlay;
2566
+ background-color: var(--md-sys-color-background);
2567
+ }
2568
+
2569
+ .tabs {
2570
+ display: flex;
2571
+ padding: 0 0 0 var(--spacing-medium);
2572
+ border-bottom: 1px solid var(--md-sys-color-primary);
2573
+ }
2574
+
2575
+ .tab {
2576
+ background-color: var(--md-sys-color-secondary-container);
2577
+ color: var(--md-sys-color-on-secondary-container);
2578
+ margin-top: var(--spacing-medium);
2579
+ padding: var(--spacing-small) var(--spacing-large) 0 var(--spacing-large);
2580
+ border-radius: 9px 9px 0 0;
2581
+ border-right: 1px solid rgba(0, 0, 0, 0.4);
2582
+ text-transform: capitalize;
2583
+ opacity: 0.7;
2584
+ font-size: 15px;
2585
+ cursor: pointer;
2586
+ }
2587
+
2588
+ .tab[activate] {
2589
+ background-color: var(--md-sys-color-primary);
2590
+ color: var(--md-sys-color-on-primary);
2591
+ box-shadow: 2px -2px 2px 0px rgba(0, 0, 0, 0.15);
2592
+ opacity: 1;
2593
+ font-weight: bold;
2594
+ }
2595
+
2596
+ .tab > md-icon {
2597
+ width: 15px;
2598
+ height: 20px;
2599
+ padding: 0;
2600
+ margin: 0;
2601
+ --md-icon-size: 15px;
2602
+ vertical-align: middle;
2603
+ }
2604
+
2605
+ .content-container {
2606
+ flex: 1;
2607
+ display: flex;
2608
+ flex-direction: column;
2609
+ background-color: var(--md-sys-color-surface);
2610
+ border-radius: 0 var(--border-radius) var(--border-radius) var(--border-radius);
2611
+ border: var(--border-dim-color);
2612
+ border-width: 0 1px 1px 1px;
2613
+ box-shadow: var(--box-shadow);
2614
+ overflow: auto;
2615
+ }
2616
+
2617
+ .tab-contents {
2618
+ display: flex;
2619
+ flex: 1;
2620
+ flex-direction: column;
2621
+ overflow-x: overlay;
2622
+ background-color: var(--md-sys-color-background);
2623
+ }
2624
+ `
2625
+ ]
2626
+ }
2627
+
2628
+ /**
2629
+ * @description 그리드 설정에 버튼 이름이 buttonName인 버튼이 있는지 체크
2630
+ **************************************************************
2631
+ * @param {Object} buttonConfig 버튼 설정
2632
+ * @param {String} buttonName 버튼 명
2633
+ * @returns
2634
+ */
2635
+ static isButtonExist(buttonConfig, buttonName) {
2636
+ let button = buttonConfig
2637
+ ? buttonConfig.find(b => {
2638
+ return b.name == buttonName
2639
+ })
2640
+ : null
2641
+ return button ? true : false
2642
+ }
2643
+
2644
+ /**
2645
+ * @description 그리드 설정에 버튼 이름이 buttonName인 버튼이 있는지 체크
2646
+ **************************************************************
2647
+ * @param {Object} gridConfig 그리드 설정
2648
+ * @param {String} buttonName 버튼 명
2649
+ * @returns
2650
+ */
2651
+ static isGridButtonExist(gridConfig, buttonName) {
2652
+ let button =
2653
+ gridConfig && gridConfig.button
2654
+ ? gridConfig.button.find(b => {
2655
+ return b.name == buttonName
2656
+ })
2657
+ : null
2658
+ return button ? true : false
2659
+ }
2660
+
2661
+ /**
2662
+ * @description 그리드 설정에서 셀렉트 컬럼을 추출
2663
+ *******************************************
2664
+ * @param {Array} gridColumns 그리드 컬럼 설정
2665
+ * @returns {String} 셀렉트 필드 정보
2666
+ */
2667
+ static getSelectColumns(gridColumns) {
2668
+ let fields = ''
2669
+
2670
+ gridColumns
2671
+ .filter(x => x.type != 'gutter')
2672
+ .forEach(c => {
2673
+ fields += '\n'
2674
+ fields += ['meta-object-selector', 'object', 'resource-object'].includes(c.type)
2675
+ ? `${c.name}\n{${MetaUiUtil.getObjctColumnSelectFields(c)}}`
2676
+ : c.type == 'board'
2677
+ ? `${c.name}\n{${'\nid\nname\nthumbnail'}}`
2678
+ : c.name
2679
+ })
2680
+
2681
+ return fields
2682
+ }
2683
+
2684
+ /**
2685
+ * Object 타입의 컬럼에 대한 셀렉트 필드 추출 후 리턴
2686
+ ********************************************
2687
+ * @param {Object} gridCol
2688
+ * @returns {String} Object 타입의 필드 문자열
2689
+ */
2690
+ static getObjctColumnSelectFields(gridCol) {
2691
+ let colStr = ''
2692
+
2693
+ if (
2694
+ ValueUtil.isNotEmpty(gridCol.record) &&
2695
+ ValueUtil.isNotEmpty(gridCol.record.options) &&
2696
+ ValueUtil.isNotEmpty(gridCol.record.options.select)
2697
+ ) {
2698
+ // 설정에 정의가 되어 있으면
2699
+ gridCol.record.options.select.forEach(x => {
2700
+ if (x.name && x.name !== '') {
2701
+ if (['meta-object-selector', 'object', 'resource-object'].includes(x.type)) {
2702
+ colStr += `\n${x.name} { ${MetaUiUtil.getObjctColumnSelectFields(x)} \n }`
2703
+ } else {
2704
+ colStr += `\n${x.name}`
2705
+ }
2706
+ }
2707
+ })
2708
+ }
2709
+
2710
+ if (colStr == '') {
2711
+ // 설정에 정의가 되어 있지 않으면 기본 id, name, description 필드
2712
+ colStr += '\nid\nname\ndescription'
2713
+ }
2714
+
2715
+ return colStr
2716
+ }
2717
+
2718
+ /**
2719
+ * @description pageView의 그리드 정보로 기본 그리스트 Html을 생성하여 리턴
2720
+ *****************************************************************
2721
+ * @param {String} pageView 페이지 뷰
2722
+ * @returns {HTMLElement} 그리스트 HTML
2723
+ */
2724
+ static getBasicGristHtml(pageView) {
2725
+ let gristConfigSet = pageView.gristConfigSet
2726
+ if (!gristConfigSet) {
2727
+ return html``
2728
+ } else {
2729
+ let gristElementId = pageView.gristId ? pageView.gristId : 'ox-grist'
2730
+ let gridMode = pageView.gridMode
2731
+ return html`
2732
+ <ox-grist
2733
+ id=${gristElementId}
2734
+ .config=${gristConfigSet}
2735
+ .mode=${gridMode}
2736
+ auto-fetch
2737
+ .personalConfigProvider=${pageView.getPagePreferenceProvider(gristElementId)}
2738
+ .fetchHandler=${pageView.fetchHandler.bind(pageView)}
2739
+ >
2740
+ ${MetaUiUtil.getGridDetailHtml(pageView)}
2741
+
2742
+ <ox-grist-personalizer slot="setting"></ox-grist-personalizer>
2743
+ </ox-grist>
2744
+ `
2745
+ }
2746
+ }
2747
+
2748
+ /**
2749
+ * @description pageView의 폼 정보로 기본 폼 Html을 생성하여 리턴
2750
+ *********************************************************
2751
+ * @param {Object} pageView 페이지 뷰
2752
+ * @returns {HTMLElement} 폼을 HTML
2753
+ */
2754
+ static getBasicFormHtml(pageView) {
2755
+ if (!pageView) return html``
2756
+ if (!pageView.formConfigSet) return html``
2757
+
2758
+ let cols = pageView.formConfigSet
2759
+ let record = pageView.record || {}
2760
+ let columns = cols.filter(x => x.hidden == false)
2761
+
2762
+ // 폼 렌더링 옵션 여부
2763
+ if (ValueUtil.isEmpty(pageView.formRenderConfig)) {
2764
+ return MetaUiUtil.getBasicFormHtmlNoOption(pageView, columns, record)
2765
+ } else {
2766
+ return MetaUiUtil.getBasicFormHtmlOnOption(pageView, columns, record)
2767
+ }
2768
+ }
2769
+
2770
+ /**
2771
+ * @description pageView의 폼 정보로 폼렌더링 옵션을 적용한 폼 Html을 생성하여 리턴
2772
+ ***********************************************************************
2773
+ * @param {Object} pageView 페이지 뷰
2774
+ * @param {Array} columns 구성 컬럼
2775
+ * @param {Object} record 표현 데이터
2776
+ * @returns {HTMLElement} 폼을 HTML
2777
+ */
2778
+ static getBasicFormHtmlOnOption(pageView, columns, record) {
2779
+ let renderConfig = pageView.formRenderConfig
2780
+
2781
+ return html`
2782
+ <div class="root_container">
2783
+ ${renderConfig.map(config => {
2784
+ let renderCols = []
2785
+
2786
+ config.column_list.forEach(x => {
2787
+ let columnInfo = columns.filter(col => x == col.name) || []
2788
+
2789
+ if (columnInfo.length > 0) {
2790
+ renderCols.push(columnInfo[0])
2791
+ }
2792
+ })
2793
+
2794
+ // 그리드 구분 스타일
2795
+ let divStyle = 'height:auto;grid-template-columns:'
2796
+ for (let i = 0; i < config.column; i++) {
2797
+ divStyle += ' 1fr 2fr'
2798
+ }
2799
+
2800
+ divStyle += ';'
2801
+ let rowIndex = 0
2802
+ return html`
2803
+ <h2><md-icon>list_alt</md-icon>${TermsUtil.tTitle(config.display)}</h2>
2804
+ <div id="content_container" class="content_container" style="${divStyle}">
2805
+ ${renderCols.map(column => {
2806
+ let { editable = false, mandatory = false } = column.record
2807
+ let dirtyFields = record['__dirtyfields__'] || {}
2808
+
2809
+ return html`
2810
+ <label ?editable=${editable}
2811
+ ><span>${mandatory ? '*' : ''}${column.header_txt}</span>
2812
+ ${editable ? html`<md-icon>edit</md-icon>` : ''}
2813
+ </label>
2814
+ <ox-grid-field
2815
+ .rowIndex=${rowIndex}
2816
+ .column=${column}
2817
+ .record=${record}
2818
+ .value=${record[column.name]}
2819
+ ?dirty=${!!dirtyFields[column.name]}
2820
+ ></ox-grid-field>
2821
+ `
2822
+ })}
2823
+ </div>
2824
+ `
2825
+ })}
2826
+ </div>
2827
+ ${pageView.isPage ? html`` : MetaUiUtil.getButtonContainer(pageView)}
2828
+ `
2829
+ }
2830
+
2831
+ /**
2832
+ * @description pageView의 폼 정보로 기본 폼 Html을 생성하여 리턴
2833
+ *********************************************************
2834
+ * @param {Object} pageView 페이지 뷰
2835
+ * @param {Array} columns 구성 컬럼
2836
+ * @param {Object} record 표현 데이터
2837
+ * @returns {HTMLElement} 폼을 HTML
2838
+ */
2839
+ static getBasicFormHtmlNoOption(pageView, columns, record) {
2840
+ // 그리드 구분 스타일
2841
+ let divStyle = 'grid-template-columns:'
2842
+ if (!pageView.column || pageView.column == 0) {
2843
+ pageView.column = 1
2844
+ }
2845
+
2846
+ pageView.column = parseInt((pageView.etcConfig || {}).default_form_column || pageView.column)
2847
+
2848
+ for (let i = 0; i < pageView.column; i++) {
2849
+ divStyle += ' 1fr 2fr'
2850
+ }
2851
+
2852
+ divStyle += ';'
2853
+ let rowIndex = 0
2854
+
2855
+ return html`
2856
+ <div id="container" class="container" style="${divStyle}">
2857
+ ${columns.map(column => {
2858
+ let { editable = false, mandatory = false } = column.record
2859
+ let dirtyFields = record['__dirtyfields__'] || {}
2860
+
2861
+ return html`
2862
+ <label ?editable=${editable}
2863
+ ><span>${mandatory ? '*' : ''}${column.header_txt}</span>
2864
+ ${editable ? html`<md-icon>edit</md-icon>` : ''}
2865
+ </label>
2866
+ <ox-grid-field
2867
+ .rowIndex=${rowIndex}
2868
+ .column=${column}
2869
+ .record=${record}
2870
+ .value=${record[column.name]}
2871
+ ?dirty=${!!dirtyFields[column.name]}
2872
+ ></ox-grid-field>
2873
+ `
2874
+ })}
2875
+ </div>
2876
+ ${pageView.isPage ? html`` : MetaUiUtil.getButtonContainer(pageView)}
2877
+ `
2878
+ }
2879
+
2880
+ /**
2881
+ * @description pageView의 탭 정보로 기본 탭 Html을 생성하여 리턴
2882
+ **********************************************************
2883
+ * @param {Object} pageView 페이지 뷰
2884
+ * @returns {HTMLElement} 탭 HTML
2885
+ */
2886
+ static getBasicTabHtml(pageView) {
2887
+ if (!pageView) return html``
2888
+ if (!pageView.tabRenderConfig) return html``
2889
+
2890
+ let tabs = pageView.tabRenderConfig
2891
+ if (!pageView.currentTabKey) pageView.currentTabKey = tabs[0]?.name || undefined
2892
+
2893
+ return html`
2894
+ <div class="tabs">
2895
+ ${tabs.map(tab => {
2896
+ let label = TermsUtil.tLabel(tab.display)
2897
+ let key = tab.name
2898
+
2899
+ return html`
2900
+ <div
2901
+ class="tab"
2902
+ ?activate="${key === pageView.currentTabKey}"
2903
+ @click="${() => (pageView.currentTabKey = key)}"
2904
+ >
2905
+ ${tab.icon ? html`<md-icon>${tab.icon}</md-icon>` : html``}
2906
+ <span>${label}</span>
2907
+ </div>
2908
+ `
2909
+ })}
2910
+ </div>
2911
+ <div class="content-container">
2912
+ ${tabs.map(tab => {
2913
+ let displayStyle = 'display:none'
2914
+ let key = tab.name
2915
+ if (key == pageView.currentTabKey) {
2916
+ displayStyle = 'display:flex'
2917
+ }
2918
+
2919
+ return html`
2920
+ <div class="tab-contents" style="${displayStyle}">${MetaUiUtil.getBasicTabContent(pageView, tab)}</div>
2921
+ `
2922
+ })}
2923
+ </div>
2924
+ `
2925
+ }
2926
+
2927
+ /**
2928
+ * @description Grist 탭 Html을 생성하여 리턴
2929
+ *****************************************
2930
+ * @param {Object} pageView 페이지 뷰
2931
+ * @returns {HTMLElement} 탭 HTML
2932
+ */
2933
+ static getGristTabHtml(pageView) {
2934
+ let type = (pageView.etcConfig || {}).master_detail_type || 'top_bottom'
2935
+ let size = (pageView.etcConfig || {}).master_detail_size || '50% 50%'
2936
+
2937
+ let layoutContainerStyle = type == 'left_right' ? `grid-template-columns:${size};` : `grid-template-rows:${size};`
2938
+ let currentRouting = pageView.isPage ? UiUtil.currentRouting() : pageView.route_name
2939
+
2940
+ return html`
2941
+ ${pageView.useMasterFilterForm == false
2942
+ ? html``
2943
+ : html`
2944
+ <div slot="headroom" class="header">
2945
+ <div class="filters">
2946
+ <ox-filters-form-base
2947
+ id="ox-filters-form"
2948
+ .filters=${pageView.searchFields || []}
2949
+ .value=${pageView.searchFieldValues || []}
2950
+ @filters-change=${e => {
2951
+ pageView.grist?.fetch()
2952
+ }}
2953
+ ></ox-filters-form-base>
2954
+ </div>
2955
+ </div>
2956
+ `}
2957
+ <div id="container" class="container" style="${layoutContainerStyle}">
2958
+ <div class="container_detail">
2959
+ ${MetaApi.getBasicGristHtml(pageView)} ${MetaUiUtil.getButtonContainer(pageView)}
2960
+ </div>
2961
+ <div class="container_detail">
2962
+ ${pageView.is_activity === true
2963
+ ? html`${pageView.is_readonly === true
2964
+ ? html`<meta-tab-element
2965
+ id="detail"
2966
+ route_name="${currentRouting}"
2967
+ .activityDataSet=${(pageView.activityDataSet || {}).detail}
2968
+ is_readonly="true"
2969
+ is_activity="true"
2970
+ is_detail="true"
2971
+ >
2972
+ </meta-tab-element>`
2973
+ : html`<meta-tab-element
2974
+ id="detail"
2975
+ route_name="${currentRouting}"
2976
+ .activityDataSet=${(pageView.activityDataSet || {}).detail}
2977
+ is_activity="true"
2978
+ is_detail="true"
2979
+ >
2980
+ </meta-tab-element>`}`
2981
+ : html`${pageView.is_readonly === true
2982
+ ? html`<meta-tab-element
2983
+ id="detail"
2984
+ route_name="${currentRouting}"
2985
+ is_readonly="true"
2986
+ is_detail="true"
2987
+ ></meta-tab-element>`
2988
+ : html`<meta-tab-element
2989
+ id="detail"
2990
+ route_name="${currentRouting}"
2991
+ is_detail="true"
2992
+ ></meta-tab-element>`}`}
2993
+ </div>
2994
+ </div>
2995
+ `
2996
+ }
2997
+
2998
+ /**
2999
+ * @description Tab Detail Html을 생성하여 리턴
3000
+ ********************************************
3001
+ * @param {Object} pageView 페이지 뷰
3002
+ * @returns {HTMLElement} 탭 HTML
3003
+ */
3004
+ static getTabDetailHtml(pageView) {
3005
+ let type = pageView.etcConfig.master_detail_type || 'top_bottom'
3006
+ let size = pageView.etcConfig.master_detail_size || '50% 50%'
3007
+ let detailInfoTxt = pageView.etcConfig.master_detail_element
3008
+ let detailInfo = JSON.parse(detailInfoTxt || '{}')
3009
+ let layoutContainerStyle = type == 'left_right' ? `grid-template-columns:${size};` : `grid-template-rows:${size};`
3010
+
3011
+ MetaUiUtil.createMasterElement(pageView)
3012
+ MetaUiUtil.createDetailElement(pageView, detailInfo)
3013
+
3014
+ return html`
3015
+ ${pageView.useMasterFilterForm == false
3016
+ ? html``
3017
+ : html`
3018
+ <div slot="headroom" class="header">
3019
+ <div class="filters">
3020
+ <ox-filters-form-base
3021
+ id="ox-filters-form"
3022
+ .filters=${pageView.searchFields || []}
3023
+ .value=${pageView.searchFieldValues || []}
3024
+ @filters-change=${e => {
3025
+ pageView.fetch()
3026
+ }}
3027
+ ></ox-filters-form-base>
3028
+ </div>
3029
+ </div>
3030
+ `}
3031
+ <div id="container" class="container" style="${layoutContainerStyle}">
3032
+ <div class="container_detail">${pageView.masterElement}</div>
3033
+ <div class="container_detail">
3034
+ ${detailInfo.tagname == '' || detailInfo.tagname == 'meta-tab-element'
3035
+ ? html``
3036
+ : html`<h2 id="detail_title"><md-icon>list_alt</md-icon>${TermsUtil.tTitle(`${detailInfo.display}`)}</h2>`}
3037
+ ${pageView.detailElement}
3038
+ </div>
3039
+ </div>
3040
+ `
3041
+ }
3042
+
3043
+ /**
3044
+ * @description 메인 탭 Html을 생성하여 리턴
3045
+ ***************************************
3046
+ * @param {Object} pageView 페이지 뷰
3047
+ * @returns {HTMLElement} 탭 HTML
3048
+ */
3049
+ static getMainTabHtml(pageView) {
3050
+ MetaUiUtil.createMasterElement(pageView)
3051
+
3052
+ return html`
3053
+ ${pageView.useMasterFilterForm == false
3054
+ ? html``
3055
+ : html`
3056
+ <div slot="headroom" class="header">
3057
+ <div class="filters">
3058
+ <ox-filters-form-base
3059
+ id="ox-filters-form"
3060
+ .filters=${pageView.searchFields || []}
3061
+ .value=${pageView.searchFieldValues || []}
3062
+ @filters-change=${e => {
3063
+ pageView.fetch()
3064
+ }}
3065
+ ></ox-filters-form-base>
3066
+ </div>
3067
+ </div>
3068
+ `}
3069
+ <div id="container" class="container">${pageView.masterElement}</div>
3070
+ `
3071
+ }
3072
+
3073
+ /**
3074
+ * @description 마스터 디테일 Html을 생성하여 리턴
3075
+ ********************************************
3076
+ * @param {Object} pageView 페이지 뷰
3077
+ * @returns {HTMLElement} 탭 HTML
3078
+ */
3079
+ static getMasterDetailHtml(pageView) {
3080
+ let type = pageView.etcConfig.master_detail_type || 'top_bottom'
3081
+ let size = pageView.etcConfig.master_detail_size || '50% 50%'
3082
+ let masterDisplay = (pageView.masterOption || {}).display
3083
+ let detailInfoTxt = pageView.etcConfig.master_detail_element
3084
+ let detailInfo = JSON.parse(detailInfoTxt || '{}')
3085
+ let layoutContainerStyle = type == 'left_right' ? `grid-template-columns:${size};` : `grid-template-rows:${size};`
3086
+ MetaUiUtil.createDetailElement(pageView, detailInfo)
3087
+
3088
+ return html`
3089
+ ${pageView.useMasterFilterForm == false
3090
+ ? html``
3091
+ : html`
3092
+ <div slot="headroom" class="header">
3093
+ <div class="filters">
3094
+ <ox-filters-form-base
3095
+ id="ox-filters-form"
3096
+ .filters=${pageView.searchFields || []}
3097
+ .value=${pageView.searchFieldValues || []}
3098
+ @filters-change=${e => {
3099
+ pageView.grist?.fetch()
3100
+ }}
3101
+ ></ox-filters-form-base>
3102
+ </div>
3103
+ </div>
3104
+ `}
3105
+ <div id="container" class="container" style="${layoutContainerStyle}">
3106
+ <div class="container_detail">
3107
+ ${pageView.useFilterForm
3108
+ ? masterDisplay
3109
+ ? html`<h2 id="detail_title"><md-icon>list_alt</md-icon>${TermsUtil.tTitle(`${masterDisplay}`)}</h2>`
3110
+ : html``
3111
+ : html``}
3112
+ ${MetaApi.getBasicGristHtml(pageView)} ${MetaUiUtil.getButtonContainer(pageView)}
3113
+ </div>
3114
+ <div class="container_detail">
3115
+ ${detailInfo.tagname == '' || detailInfo.tagname == 'meta-tab-element'
3116
+ ? html``
3117
+ : detailInfo.display
3118
+ ? html`<h2 id="detail_title"><md-icon>list_alt</md-icon>${TermsUtil.tTitle(`${detailInfo.display}`)}</h2>`
3119
+ : html``}
3120
+ ${pageView.detailElement}
3121
+ </div>
3122
+ </div>
3123
+ `
3124
+ }
3125
+
3126
+ /**
3127
+ * @descrtiption 디테일 엘리먼트 렌더링
3128
+ **********************************
3129
+ * @param {Object} pageView
3130
+ * @param {Object} detailInfo
3131
+ * @returns htmlElement
3132
+ */
3133
+ static createDetailElement(pageView, detailInfo) {
3134
+ if (!pageView.detailElement) {
3135
+ let name = detailInfo.name ? detailInfo.name : 'detail'
3136
+ let elementTxt = `<${detailInfo.tagname} id="${name}" name="${name}" route_name="${detailInfo.routing}" ${
3137
+ pageView.is_activity === true ? 'is_activity=true' : ''
3138
+ } ${pageView.is_readonly === true ? 'is_readonly=true' : ''} is_detail=true style="flex:1;"></${detailInfo.tagname}>`
3139
+ pageView.detailElement = UiUtil.htmlToElement(elementTxt)
3140
+ pageView.detailElement.activityDataSet = (pageView.activityDataSet || {}).detail
3141
+
3142
+ if (pageView.tagName.toLowerCase().startsWith('meta-tab-detail-')) {
3143
+ pageView.detailElement.rootElement = pageView
3144
+ }
3145
+ }
3146
+ }
3147
+ /**
3148
+ * @descrtiption 마스터 엘리먼트 렌더링
3149
+ **********************************
3150
+ * @param {Object} pageView
3151
+ * @returns {HTMLElement} 마스터 엘리먼트 HTML
3152
+ */
3153
+ static createMasterElement(pageView) {
3154
+ if (!pageView.masterElement && pageView.filterForm) {
3155
+ let name = 'master'
3156
+ let elementTxt = `<meta-tab-element id="${name}" name="${name}" route_name="${pageView.currentRouting}" ${
3157
+ pageView.is_activity === true ? 'is_activity=true' : ''
3158
+ } ${pageView.is_readonly === true ? 'is_readonly=true' : ''} is_detail=true style="flex:1;"></meta-tab-element>`
3159
+ pageView.masterElement = UiUtil.htmlToElement(elementTxt)
3160
+ pageView.masterElement.activityDataSet = (pageView.activityDataSet || {}).master
3161
+ pageView.masterElement.includeMainList = true
3162
+ pageView.masterElement.mainFilterForm = pageView.filterForm
3163
+ pageView.masterElement.rootElement = pageView
3164
+ }
3165
+ }
3166
+
3167
+ /**
3168
+ * @descrtiption 기본 탭 컨텐트 리턴
3169
+ ********************************
3170
+ * @param {Object} pageView
3171
+ * @param {Object} tabConfig
3172
+ * @returns {HTMLElement} 탭 컨텐트 HTML
3173
+ */
3174
+ static getBasicTabContent(pageView, tabConfig) {
3175
+ if (!pageView.tabElements) {
3176
+ pageView.tabElements = {}
3177
+ }
3178
+
3179
+ if (Object.keys(pageView.tabElements).filter(key => key == tabConfig.name).length > 0) {
3180
+ return pageView.tabElements[tabConfig.name]
3181
+ }
3182
+
3183
+ let htmlText = `<${tabConfig.tagname} id='${tabConfig.name}' route_name='${tabConfig.menu}' style="flex:1;" ${
3184
+ pageView.is_activity === true ? 'is_activity=true' : ''
3185
+ } ${pageView.is_readonly === true ? 'is_readonly=true' : ''} is_detail=true></${tabConfig.tagname}>`
3186
+ let tabElement = UiUtil.htmlToElement(htmlText)
3187
+ tabElement.activityDataSet = (pageView.activityDataSet || {})[tabConfig.name]
3188
+
3189
+ if (pageView.parent_id) {
3190
+ tabElement.parent_id = pageView.parent_id
3191
+ }
3192
+
3193
+ if (pageView.rootElement) {
3194
+ tabElement.rootElement = pageView.rootElement
3195
+ } else {
3196
+ tabElement.detailElement = pageView
3197
+ }
3198
+
3199
+ if (tabConfig.filter_from === false) {
3200
+ tabElement.use_filter_form = false
3201
+ }
3202
+
3203
+ if (tabConfig.main_filter_form) {
3204
+ tabElement.mainFilterForm = tabConfig.main_filter_form
3205
+ }
3206
+
3207
+ pageView.tabElements[tabConfig.name] = tabElement
3208
+ return tabElement
3209
+ }
3210
+
3211
+ /**
3212
+ * @description 그리드 상세 내용 HTML 생성
3213
+ **************************************
3214
+ * @param {Object} pageView 페이지 뷰
3215
+ * @returns {HTMLElement} 그리드 상세 내용 HTML
3216
+ */
3217
+ static getGridDetailHtml(pageView) {
3218
+ let useAddButton = pageView.useButtonAdd
3219
+ let useFilterForm = pageView.useFilterForm
3220
+ let useFilterAddButton = JSON.parse((pageView.etcConfig || {}).use_filter_add_button || 'false')
3221
+ let recordCreationCallback = pageView.recordCreationCallback
3222
+ let useSortButton = JSON.parse((pageView.etcConfig || {}).use_grid_sort_button || 'false')
3223
+ useFilterAddButton = useFilterAddButton === true && useAddButton === true
3224
+
3225
+ return html`
3226
+ ${useFilterForm
3227
+ ? html`
3228
+ <div slot="headroom" class="header">
3229
+ <div class="filters">
3230
+ <ox-filters-form></ox-filters-form>
3231
+ ${useSortButton
3232
+ ? html`
3233
+ <div id="sorters">
3234
+ <md-icon
3235
+ @click=${e => {
3236
+ const target = e.currentTarget
3237
+ target.nextElementSibling.open({
3238
+ right: 0,
3239
+ top: target.offsetTop + target.offsetHeight
3240
+ })
3241
+ }}
3242
+ >sort</md-icon
3243
+ >
3244
+ <ox-popup id="sorter-control">
3245
+ <ox-sorters-control></ox-sorters-control>
3246
+ </ox-popup>
3247
+ </div>
3248
+ `
3249
+ : nothing}
3250
+ ${MetaUiUtil.getGridViewOption(pageView)}
3251
+ <ox-record-creator id="add" ?hidden="${!useFilterAddButton}" .callback=${recordCreationCallback}>
3252
+ <button style="display: flex; justify-content: center">
3253
+ <md-icon>add</md-icon>
3254
+ </button>
3255
+ </ox-record-creator>
3256
+ </div>
3257
+ </div>
3258
+ `
3259
+ : nothing}
3260
+ `
3261
+ }
3262
+
3263
+ /**
3264
+ * @description 그리드 보기 옵션 버튼을 그린다.
3265
+ ****************************************
3266
+ * @param {Object} pageView 페이지 뷰
3267
+ * @return {HTMLElement} 그리드 뷰 옵션 HTML
3268
+ */
3269
+ static getGridViewOption(pageView) {
3270
+ if (ValueUtil.isEmpty(pageView.gridViewOptions) || pageView.gridViewOptions.length == 1) {
3271
+ return html``
3272
+ } else {
3273
+ return html`
3274
+ <div id="modes">
3275
+ ${pageView.gridViewOptions.includes('GRID')
3276
+ ? html`<md-icon @click="${() => (pageView.gridMode = 'GRID')}" ?active="${pageView.gridMode == 'GRID'}"
3277
+ >grid_on</md-icon
3278
+ >`
3279
+ : ``}
3280
+ ${pageView.gridViewOptions.includes('LIST')
3281
+ ? html`<md-icon @click="${() => (pageView.gridMode = 'LIST')}" ?active="${pageView.gridMode == 'LIST'}"
3282
+ >format_list_bulleted</md-icon
3283
+ >`
3284
+ : ``}
3285
+ ${pageView.gridViewOptions.includes('CARD')
3286
+ ? html`<md-icon @click="${() => (pageView.gridMode = 'CARD')}" ?active="${pageView.gridMode == 'CARD'}"
3287
+ >apps</md-icon
3288
+ >`
3289
+ : ``}
3290
+ </div>
3291
+ `
3292
+ }
3293
+ }
3294
+
3295
+ /**
3296
+ * @description 버튼 컨테이너 엘리먼트 생성
3297
+ *************************************
3298
+ * @param {Object} pageView
3299
+ * @returns {HTMLElement}
3300
+ */
3301
+ static getButtonContainer(pageView) {
3302
+ return html`<div class="button-container">${MetaUiUtil.getContainerButtons(pageView)}</div>`
3303
+ }
3304
+ }