@operato/dataset 1.5.56 → 1.7.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.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/dataset",
3
3
  "description": "WebApplication dataset supporting components following open-wc recommendations",
4
4
  "author": "heartyoh",
5
- "version": "1.5.56",
5
+ "version": "1.7.0",
6
6
  "main": "dist/src/index.js",
7
7
  "module": "dist/src/index.js",
8
8
  "exports": {
@@ -137,5 +137,5 @@
137
137
  "prettier --write"
138
138
  ]
139
139
  },
140
- "gitHead": "c5a8906354b25cfea9d3178df5fada02108046e8"
140
+ "gitHead": "c81df18d34366841dec7a36dbfa1a42252b8aa4c"
141
141
  }
@@ -1,9 +1,11 @@
1
1
  import '@operato/input/ox-input-file.js'
2
+ import './ox-data-entry-subgroup-form.js'
2
3
 
3
- import { css, html, LitElement } from 'lit'
4
- import { customElement, property } from 'lit/decorators.js'
4
+ import { css, html, LitElement, TemplateResult } from 'lit'
5
+ import { customElement, property, queryAll } from 'lit/decorators.js'
5
6
 
6
- import { DataSet } from './types.js'
7
+ import { DataSet, DataItem, DataSample } from './types.js'
8
+ import { OxDataEntrySubgroupForm } from './ox-data-entry-subgroup-form.js'
7
9
 
8
10
  @customElement('ox-data-entry-form')
9
11
  export class OxDataEntryForm extends LitElement {
@@ -22,6 +24,7 @@ export class OxDataEntryForm extends LitElement {
22
24
  text-transform: capitalize;
23
25
  text-align: center;
24
26
  }
27
+
25
28
  h3 {
26
29
  margin: var(--page-description-margin);
27
30
  font: var(--page-description-font);
@@ -36,6 +39,7 @@ export class OxDataEntryForm extends LitElement {
36
39
  display: flex;
37
40
  flex-direction: column;
38
41
  }
42
+
39
43
  label {
40
44
  display: grid;
41
45
 
@@ -47,6 +51,7 @@ export class OxDataEntryForm extends LitElement {
47
51
  align-items: center;
48
52
  margin-bottom: var(--margin-default);
49
53
  }
54
+
50
55
  label:nth-child(odd) {
51
56
  background-color: var(--main-section-background-color);
52
57
  padding: var(--padding-default) 0;
@@ -65,13 +70,16 @@ export class OxDataEntryForm extends LitElement {
65
70
  color: var(--item-description-color);
66
71
  text-align: left;
67
72
  }
73
+
68
74
  div[description] * {
69
75
  vertical-align: middle;
70
76
  }
77
+
71
78
  div[description] mwc-icon {
72
79
  margin-top: -3px;
73
80
  font-size: 0.9rem;
74
81
  }
82
+
75
83
  div[elements] {
76
84
  grid-area: inputs;
77
85
  display: flex;
@@ -80,9 +88,11 @@ export class OxDataEntryForm extends LitElement {
80
88
  gap: 10px;
81
89
  padding-right: var(--padding-default);
82
90
  }
91
+
83
92
  div[elements] * {
84
93
  flex: 1;
85
94
  }
95
+
86
96
  div[elements] input,
87
97
  div[elements] select {
88
98
  border: var(--input-field-border);
@@ -91,6 +101,11 @@ export class OxDataEntryForm extends LitElement {
91
101
  font: var(--input-field-font);
92
102
  }
93
103
 
104
+ div[subgroup] {
105
+ grid-column: 1 / 3;
106
+ grid-row: 2;
107
+ }
108
+
94
109
  @media only screen and (max-width: 460px) {
95
110
  label {
96
111
  display: grid;
@@ -113,6 +128,8 @@ export class OxDataEntryForm extends LitElement {
113
128
  @property({ type: Object }) dataSet?: DataSet
114
129
  @property({ type: Object }) value?: { [tag: string]: any }
115
130
 
131
+ @queryAll('ox-data-entry-subgroup-form') subgroups!: NodeListOf<OxDataEntrySubgroupForm>
132
+
116
133
  render() {
117
134
  return html` <form @change=${(e: Event) => this.onChange(e)}>
118
135
  <h2>${this.dataSet?.name || ''}</h2>
@@ -134,8 +151,37 @@ export class OxDataEntryForm extends LitElement {
134
151
  }
135
152
 
136
153
  private buildInputs() {
137
- const dataItems = this.dataSet?.dataItems.filter(item => item.active)
154
+ const dataItems = this.dataSet?.dataItems.filter(item => item.active) || []
155
+ const nonGroupDataItems = dataItems.filter(dataItem => !dataItem.group)
156
+ const dataItemSubgroups = Object.entries(this.groupDataItemsByGroup(dataItems)).map(([subgroup, dataItems]) => {
157
+ const tags = dataItems.map(dataItem => dataItem.tag)
158
+ const value = tags.reduce((partial, key) => {
159
+ partial[key] = this.value?.[key]
160
+ return partial
161
+ }, {} as any)
162
+
163
+ return this.buildInputs4Subgroup(subgroup, dataItems, value)
164
+ })
165
+
166
+ return [...this.buildInputs4NonGrouped(nonGroupDataItems), ...dataItemSubgroups]
167
+ }
138
168
 
169
+ private buildInputs4Subgroup(subgroup: string, dataItems: DataItem[], value: { [tag: string]: any }): TemplateResult {
170
+ return html`
171
+ <label>
172
+ <div name>${subgroup}</div>
173
+ <div subgroup>
174
+ <ox-data-entry-subgroup-form
175
+ .subgroup=${subgroup}
176
+ .dataItems=${dataItems}
177
+ .value=${value}
178
+ ></ox-data-entry-subgroup-form>
179
+ </div>
180
+ </label>
181
+ `
182
+ }
183
+
184
+ private buildInputs4NonGrouped(dataItems: DataItem[]): TemplateResult[] {
139
185
  return (dataItems || []).map(dataItem => {
140
186
  const { name, description, tag, type, quota = 1, options = {}, unit } = dataItem
141
187
 
@@ -190,8 +236,9 @@ export class OxDataEntryForm extends LitElement {
190
236
 
191
237
  private buildValue() {
192
238
  const dataItems = this.dataSet!.dataItems
239
+ const nonGroupDataItems = dataItems.filter(dataItem => !dataItem.group)
193
240
 
194
- return (dataItems || []).reduce((sum, dataItem) => {
241
+ const nonGroupValue = (nonGroupDataItems || []).reduce((sum, dataItem) => {
195
242
  const { tag, type } = dataItem
196
243
 
197
244
  const editors = Array.prototype.slice.call(
@@ -204,5 +251,30 @@ export class OxDataEntryForm extends LitElement {
204
251
 
205
252
  return sum
206
253
  }, {} as { [tag: string]: any })
254
+
255
+ return Array.from(this.subgroups).reduce((value, subgroup) => {
256
+ return {
257
+ ...value,
258
+ ...subgroup.buildValue()
259
+ }
260
+ }, nonGroupValue || {})
261
+ }
262
+
263
+ private groupDataItemsByGroup(dataItems: DataItem[]): { [group: string]: DataItem[] } {
264
+ const groupedDataItems: { [group: string]: DataItem[] } = {}
265
+
266
+ for (const dataItem of dataItems) {
267
+ const { group } = dataItem
268
+
269
+ if (group) {
270
+ if (!groupedDataItems[group]) {
271
+ groupedDataItems[group] = []
272
+ }
273
+
274
+ groupedDataItems[group].push(dataItem)
275
+ }
276
+ }
277
+
278
+ return groupedDataItems
207
279
  }
208
280
  }
@@ -0,0 +1,146 @@
1
+ import '@operato/input/ox-input-file.js'
2
+ import '@operato/data-grist/ox-grist.js'
3
+
4
+ import { css, html, LitElement } from 'lit'
5
+ import { customElement, property, state, query } from 'lit/decorators.js'
6
+
7
+ import { ScrollbarStyles } from '@operato/styles'
8
+ import { isMobileDevice } from '@operato/utils'
9
+
10
+ import { DataItem } from './types.js'
11
+ import { DataGrist, FetchOption } from '@operato/data-grist'
12
+
13
+ @customElement('ox-data-entry-subgroup-form')
14
+ export class OxDataEntrySubgroupForm extends LitElement {
15
+ static styles = [
16
+ ScrollbarStyles,
17
+ css`
18
+ :host {
19
+ display: flex;
20
+ flex-direction: column;
21
+
22
+ width: 100%;
23
+ min-height: 100px;
24
+ }
25
+ `
26
+ ]
27
+
28
+ @property({ type: String }) subgroup?: string
29
+ @property({ type: Array }) dataItems?: DataItem[]
30
+ @property({ type: Object }) value?: { [tag: string]: any }
31
+
32
+ @state() gristConfig = this.buildGristConfiguration()
33
+ @query('ox-grist') grist!: DataGrist
34
+
35
+ render() {
36
+ return html`
37
+ <ox-grist
38
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
39
+ .config=${this.buildGristConfiguration()}
40
+ .fetchHandler=${this.fetchHandler.bind(this)}
41
+ >
42
+ </ox-grist>
43
+ `
44
+ }
45
+
46
+ private buildGristConfiguration() {
47
+ const columns = (this.dataItems || []).map(dataItem => {
48
+ const { name, description, tag, type, options = {}, unit } = dataItem
49
+ const columnConfig = {
50
+ type,
51
+ name: tag,
52
+ header: `${name}${unit ? ` (${unit})` : ''}`,
53
+ record: {
54
+ editable: true
55
+ },
56
+ width: 200
57
+ } as any
58
+
59
+ switch (type) {
60
+ case 'select':
61
+ columnConfig.record.options = [
62
+ '',
63
+ ...(options.options || []).map((option: any) => {
64
+ if (typeof option == 'string') {
65
+ return option
66
+ }
67
+ const { display, text, value } = option || {}
68
+
69
+ return {
70
+ display: display || text,
71
+ value
72
+ }
73
+ })
74
+ ]
75
+ return columnConfig
76
+
77
+ case 'boolean':
78
+ columnConfig.record.align = 'center'
79
+ return columnConfig
80
+
81
+ case 'number':
82
+ columnConfig.record.align = 'right'
83
+ return columnConfig
84
+
85
+ case 'date':
86
+ return columnConfig
87
+
88
+ case 'datetime':
89
+ return columnConfig
90
+
91
+ case 'file':
92
+ return columnConfig
93
+
94
+ case 'string':
95
+ return columnConfig
96
+
97
+ default:
98
+ return columnConfig
99
+ }
100
+ })
101
+
102
+ return {
103
+ list: { fields: ['name', 'data'] },
104
+ columns: [{ type: 'gutter', gutterName: 'sequence' }, ...columns],
105
+ rows: {
106
+ appendable: true
107
+ },
108
+ pagination: { infinite: true }
109
+ }
110
+ }
111
+
112
+ async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
113
+ const length = Object.entries(this.value || {}).reduce((max, [tag, value]) => {
114
+ return Math.max(max, Array.isArray(value) ? value.length : 1)
115
+ }, 0)
116
+
117
+ const tags = (this.dataItems || []).map(dataItem => dataItem.tag)
118
+
119
+ const records = Array.from({ length }, (_, index) => index).map(index => {
120
+ return tags.reduce((partial, tag) => {
121
+ const v = this.value?.[tag]
122
+ partial[tag] = Array.isArray(v) ? v[index] : index == 0 ? v : undefined
123
+ return partial
124
+ }, {} as any)
125
+ })
126
+
127
+ return {
128
+ total: records.length || 0,
129
+ records: records
130
+ }
131
+ }
132
+
133
+ public buildValue() {
134
+ const records = this.grist._data.records || []
135
+ const tags = (this.dataItems || []).map(dataItem => dataItem.tag)
136
+ this.grist.commit()
137
+
138
+ return tags.reduce((partial, tag) => {
139
+ partial[tag] = Array.from({ length: records.length }, (_, index) => index).map(index => {
140
+ const record = records[index]
141
+ return record?.[tag]
142
+ })
143
+ return partial
144
+ }, {} as any)
145
+ }
146
+ }
@@ -42,6 +42,7 @@ export class OxDataSampleSubgroupView extends LitElement {
42
42
 
43
43
  th.label {
44
44
  background-color: #aaa;
45
+ width: 120px;
45
46
  }
46
47
 
47
48
  tr {
@@ -63,6 +64,7 @@ export class OxDataSampleSubgroupView extends LitElement {
63
64
  td.label {
64
65
  background-color: #aaa;
65
66
  text-transform: var(--th-text-transform);
67
+ width: 120px;
66
68
  }
67
69
 
68
70
  td mwc-icon {
@@ -29,6 +29,7 @@ const dataSet = {
29
29
  description: '창고 온도는 섭씨 0도 이하로 유지되어야 합니다.',
30
30
  sequence: 1,
31
31
  tag: 'temp',
32
+ group: '측정데이타',
32
33
  type: 'number',
33
34
  quota: 1,
34
35
  active: true,
@@ -51,6 +52,7 @@ const dataSet = {
51
52
  description: '창고 습도는 30% 이하로 유지되어야 합니다.',
52
53
  sequence: 2,
53
54
  tag: 'humid',
55
+ group: '측정데이타',
54
56
  type: 'number',
55
57
  quota: 5,
56
58
  active: true,
@@ -73,6 +75,7 @@ const dataSet = {
73
75
  description: '육안 검사는 포장전 30분 내로 실행되어야 합니다.',
74
76
  sequence: 3,
75
77
  tag: 'inspection',
78
+ group: '측정데이타',
76
79
  type: 'boolean',
77
80
  quota: 3,
78
81
  active: true,
@@ -92,6 +95,7 @@ const dataSet = {
92
95
  description: '품평은 최우수/우수/보통/미달을 포함하여 간단히 평가.',
93
96
  sequence: 4,
94
97
  tag: 'evaluation',
98
+ group: '측정데이타',
95
99
  type: 'select',
96
100
  options: {
97
101
  options: [
@@ -150,6 +154,9 @@ const Template: Story<ArgTypes> = ({}: ArgTypes) =>
150
154
  body {
151
155
  }
152
156
  </style>
157
+ <link href="/themes/app-theme.css" rel="stylesheet" />
158
+ <link href="/themes/oops-theme.css" rel="stylesheet" />
159
+ <link href="/themes/grist-theme.css" rel="stylesheet" />
153
160
 
154
161
  <ox-data-entry-form
155
162
  .dataSet=${dataSet}