@operato/dataset 7.1.31 → 7.1.32

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 (50) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/package.json +12 -12
  4. package/.editorconfig +0 -29
  5. package/.storybook/main.js +0 -3
  6. package/.storybook/preview.js +0 -52
  7. package/.storybook/server.mjs +0 -8
  8. package/demo/favicon.ico +0 -0
  9. package/demo/index.html +0 -349
  10. package/demo/ox-data-ooc-brief-view-test.html +0 -349
  11. package/src/grist-editor/index.ts +0 -10
  12. package/src/grist-editor/ox-grist-editor-data-item-spec.ts +0 -93
  13. package/src/grist-editor/ox-popup-data-item-spec.ts +0 -93
  14. package/src/index.ts +0 -2
  15. package/src/ox-data-entry-form.ts +0 -303
  16. package/src/ox-data-entry-subgroup-form.ts +0 -157
  17. package/src/ox-data-entry-subgroup-view.ts +0 -221
  18. package/src/ox-data-entry-view.ts +0 -241
  19. package/src/ox-data-item-spec.ts +0 -131
  20. package/src/ox-data-ooc-badge.ts +0 -73
  21. package/src/ox-data-ooc-brief-view.ts +0 -43
  22. package/src/ox-data-ooc-correction-part.ts +0 -107
  23. package/src/ox-data-ooc-history.ts +0 -74
  24. package/src/ox-data-ooc-view.ts +0 -51
  25. package/src/ox-data-sample-subgroup-view.ts +0 -210
  26. package/src/ox-data-sample-view.ts +0 -271
  27. package/src/ox-data-summary-view.ts +0 -198
  28. package/src/types.ts +0 -178
  29. package/src/usecase/ccp/index.ts +0 -10
  30. package/src/usecase/ccp/ox-data-use-case-ccp.ts +0 -147
  31. package/src/usecase/ccp/ox-input-ccp-limits.ts +0 -184
  32. package/src/usecase/ccp/ox-property-editor-ccp-limits.ts +0 -23
  33. package/src/usecase/ox-data-use-case.ts +0 -178
  34. package/src/usecase/qc/index.ts +0 -10
  35. package/src/usecase/qc/ox-data-use-case-qc.ts +0 -76
  36. package/src/usecase/qc/ox-input-qc-limits.ts +0 -183
  37. package/src/usecase/qc/ox-property-editor-qc-limits.ts +0 -23
  38. package/src/usecase/spc/index.ts +0 -10
  39. package/src/usecase/spc/ox-data-use-case-spc.ts +0 -147
  40. package/src/usecase/spc/ox-input-spc-limits.ts +0 -184
  41. package/src/usecase/spc/ox-property-editor-spc-limits.ts +0 -23
  42. package/stories/ox-data-entry-form.stories.ts +0 -253
  43. package/stories/ox-data-item-spec.stories.ts +0 -257
  44. package/stories/ox-data-ooc-brief-view.stories.ts +0 -333
  45. package/stories/ox-data-ooc-view.stories.ts +0 -327
  46. package/stories/ox-data-sample-view.stories.ts +0 -313
  47. package/stories/ox-grist-editor-data-item-spec.stories.ts +0 -412
  48. package/tsconfig.json +0 -24
  49. package/web-dev-server.config.mjs +0 -27
  50. package/web-test-runner.config.mjs +0 -41
@@ -1,241 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import '@operato/input/ox-input-file.js'
3
- import '@operato/input/ox-input-signature.js'
4
- import './ox-data-entry-subgroup-view.js'
5
-
6
- import { css, html, LitElement, nothing } from 'lit'
7
- import { customElement, property } from 'lit/decorators.js'
8
-
9
- import { i18next } from '@operato/i18n'
10
-
11
- import { DataItem, DataSet, DataSpecLimitSet } from './types.js'
12
- import { OxDataUseCase } from './usecase/ox-data-use-case.js'
13
-
14
- @customElement('ox-data-entry-view')
15
- export class OxDataEntryView extends LitElement {
16
- static styles = css`
17
- :host {
18
- display: flex;
19
- flex-direction: column;
20
-
21
- --signature-min-width: 100px;
22
- --signature-min-height: 60px;
23
- }
24
-
25
- form {
26
- flex: 1;
27
-
28
- display: flex;
29
- flex-direction: column;
30
- }
31
-
32
- h2 {
33
- margin: var(--title-margin);
34
- font: var(--title-font);
35
- color: var(--title-text-color);
36
- text-transform: capitalize;
37
- }
38
- [page-description] {
39
- margin: var(--page-description-margin);
40
- opacity: 0.7;
41
- font: var(--page-description-font);
42
- color: var(--md-sys-color-on-secondary-container);
43
- text-transform: capitalize;
44
- }
45
- [page-description] * {
46
- vertical-align: middle;
47
- }
48
- [page-description] md-icon {
49
- margin-top: -2px;
50
- font-size: 0.9rem;
51
- color: var(--page-description-color);
52
- }
53
-
54
- table {
55
- border-collapse: collapse;
56
- margin-bottom: var(--spacing-medium);
57
- }
58
- th {
59
- padding: var(--th-padding);
60
- border-top: var(--th-border-top);
61
- border-bottom: var(--td-border-bottom);
62
- text-transform: var(--th-text-transform);
63
- font: var(--th-font);
64
- color: var(--th-color);
65
- text-align: left;
66
- white-space: nowrap;
67
- }
68
- th[item] {
69
- min-width: 100px;
70
- }
71
- th[value] {
72
- min-width: 100px;
73
- }
74
- tr {
75
- background-color: var(--tr-background-color);
76
- }
77
- tr:nth-child(odd) {
78
- background-color: var(--tr-background-odd-color);
79
- }
80
- tr:hover {
81
- background-color: var(--tr-background-hover-color);
82
- }
83
- tr[ooc],
84
- tr[oos] {
85
- background-color: #fefbdf;
86
- }
87
- td {
88
- border-bottom: var(--td-border-bottom);
89
- padding: var(--td-padding);
90
- font: var(--td-font);
91
- color: var(--td-color);
92
- }
93
- td[name] {
94
- font-weight: bold;
95
- }
96
- td md-icon {
97
- color: var(--md-sys-color-error);
98
- }
99
-
100
- pre {
101
- tab-size: 2;
102
- }
103
-
104
- @media screen and (max-width: 480px) {
105
- th {
106
- min-width: 50px;
107
- }
108
- }
109
- `
110
-
111
- @property({ type: Object }) dataSet?: DataSet
112
- @property({ type: Object }) value?: { [tag: string]: any }
113
-
114
- render() {
115
- if (!this.dataSet) {
116
- return nothing
117
- }
118
-
119
- const { name, description, useCase, dataItems = [] } = this.dataSet!
120
-
121
- const data = this.value || {}
122
-
123
- const useCaseNames = useCase?.split(',').filter(useCase => useCase.trim()) || []
124
- const nonGroupDataItems = dataItems.filter(dataItem => !dataItem.group && !dataItem.hidden)
125
- const dataItemSubgroups = this.groupDataItemsByGroup(dataItems)
126
-
127
- return html` <h2>${name}</h2>
128
- <p page-description><md-icon>info</md-icon> ${description}<br /></p>
129
-
130
- <form>
131
- ${nonGroupDataItems.length > 0
132
- ? html`
133
- <table>
134
- <tr>
135
- <th item>${i18next.t('field.item')}</th>
136
- <th>${i18next.t('field.description')}</th>
137
- <th>${i18next.t('field.finalizing-function')}</th>
138
- <th>${i18next.t('field.unit')}</th>
139
- <th value>${i18next.t('field.value')}</th>
140
- <th>${i18next.t('field.spec')}</th>
141
- <th>${i18next.t('field.ooc')}</th>
142
- <th>${i18next.t('field.oos')}</th>
143
- </tr>
144
- ${nonGroupDataItems.map(dataItem => {
145
- const { name = '', tag = '', description = '', stat, unit = '', spec = {}, type } = dataItem
146
- const value = data[tag]
147
- const { ooc, oos } = OxDataUseCase.evaluateTag(this.dataSet!, dataItems, data, tag) || {}
148
-
149
- return html`
150
- <tr ?ooc=${ooc} ?oos=${oos}>
151
- <td name>${name}</td>
152
- <td>${description}</td>
153
- <td>${stat}</td>
154
- <td>${unit}</td>
155
- <td>${this.buildValue(type, value)}</td>
156
- <td><pre>${this.buildSpec(useCaseNames, spec)}</pre></td>
157
- <td>${ooc ? html`<md-icon>done</md-icon>` : ''}</td>
158
- <td>${oos ? html`<md-icon>done</md-icon>` : ''}</td>
159
- </tr>
160
- `
161
- })}
162
- </table>
163
- `
164
- : nothing}
165
- ${Object.keys(dataItemSubgroups).map(subgroup => {
166
- return html`
167
- <ox-data-entry-subgroup-view
168
- .subgroup=${subgroup}
169
- .dataSet=${this.dataSet}
170
- .data=${this.value}
171
- ></ox-data-entry-subgroup-view>
172
- `
173
- })}
174
- </form>`
175
- }
176
-
177
- private groupDataItemsByGroup(dataItems: DataItem[]): { [group: string]: DataItem[] } {
178
- const groupedDataItems: { [group: string]: DataItem[] } = {}
179
-
180
- for (const dataItem of dataItems) {
181
- const { group, hidden } = dataItem
182
-
183
- if (group && !hidden) {
184
- if (!groupedDataItems[group]) {
185
- groupedDataItems[group] = []
186
- }
187
-
188
- groupedDataItems[group].push(dataItem)
189
- }
190
- }
191
-
192
- return groupedDataItems
193
- }
194
-
195
- private buildSpec(useCaseNames: string[], spec: DataSpecLimitSet): string {
196
- return OxDataUseCase.elaborateDataItemSpec(useCaseNames, spec)
197
- }
198
-
199
- private download(file: { mimetype: string; name: string; fullpath: string }) {
200
- const element = document.createElement('a')
201
- element.setAttribute('href', file.fullpath)
202
- element.setAttribute('download', file.name!)
203
- document.body.appendChild(element)
204
- element.click()
205
- }
206
-
207
- private buildValue(type: string, value: any | any[]) {
208
- if (value === undefined) {
209
- return ''
210
- }
211
- const values = value instanceof Array ? value : [value]
212
-
213
- if (type == 'file') {
214
- const files = values.flat() as { mimetype: string; name: string; fullpath: string }[]
215
-
216
- return files.filter(Boolean).map(file => html`<a @click=${() => this.download(file)}>${file.name}</a></br>`)
217
- }
218
-
219
- if (type == 'signature') {
220
- return html` <ox-input-signature .value=${value} disabled></ox-input-signature>`
221
- }
222
-
223
- const elements = values.map((v: any, idx) => {
224
- switch (typeof v) {
225
- case 'boolean':
226
- return html` <input type="checkbox" .checked=${v} disabled />`
227
- break
228
-
229
- default:
230
- if (type == 'date') {
231
- return v ? new Date(v).toLocaleDateString() : ''
232
- } else if (type == 'datetime') {
233
- return v ? new Date(v).toLocaleString() : ''
234
- }
235
- return v ?? ''
236
- }
237
- })
238
-
239
- return typeof values[0] === 'boolean' ? elements : elements.join(', ')
240
- }
241
- }
@@ -1,131 +0,0 @@
1
- import '@operato/property-editor/ox-properties-dynamic-view.js'
2
-
3
- import { css, html, LitElement, PropertyValues } from 'lit'
4
- import { customElement, property, queryAll, state } from 'lit/decorators.js'
5
-
6
- import { DataItem, UseCaseDefinition } from './types.js'
7
- import { OxDataUseCase } from './usecase/ox-data-use-case.js'
8
-
9
- @customElement('ox-data-item-spec')
10
- export class OxDataItemSpec extends LitElement {
11
- static styles = css`
12
- :host {
13
- display: flex;
14
- flex-direction: row;
15
- --item-description-font: normal 0.8rem/1rem var(--theme-font);
16
- --item-description-color: var(--page-description-color);
17
- }
18
-
19
- form {
20
- flex: 1;
21
- display: flex;
22
- flex-direction: column;
23
- }
24
- label {
25
- display: grid;
26
-
27
- grid-template-rows: auto 1fr;
28
- grid-template-columns: 1fr 5fr;
29
- grid-template-areas: 'name description' 'empty inputs';
30
-
31
- grid-gap: 9px;
32
- align-items: center;
33
- margin-bottom: var(--spacing-medium);
34
- }
35
- label:nth-child(odd) {
36
- background-color: var(--md-sys-color-background);
37
- padding: var(--padding-default) 0;
38
- }
39
- div[name] {
40
- grid-area: name;
41
- font: var(--label-font);
42
- color: var(--label-color, var(--md-sys-color-on-surface));
43
- text-align: right;
44
- }
45
- div[description] {
46
- grid-area: description;
47
- opacity: 0.7;
48
- font: var(--item-description-font);
49
- color: var(--item-description-color);
50
- text-align: left;
51
- }
52
- div[description] * {
53
- vertical-align: middle;
54
- }
55
- div[description] md-icon {
56
- font-size: 0.9rem;
57
- }
58
- ox-properties-dynamic-view {
59
- grid-area: inputs;
60
- display: flex;
61
- flex-direction: column;
62
- flex-wrap: wrap;
63
- gap: 10px;
64
- padding-right: var(--padding-default);
65
- }
66
-
67
- @media only screen and (max-width: 460px) {
68
- label {
69
- display: grid;
70
-
71
- grid-template-rows: auto auto 1fr;
72
- grid-template-columns: 1fr;
73
- grid-template-areas: 'name' 'description' 'inputs';
74
-
75
- grid-gap: 9px;
76
- align-items: center;
77
- margin-bottom: var(--spacing-medium);
78
- }
79
-
80
- div[name] {
81
- text-align: left;
82
- }
83
- }
84
- `
85
-
86
- @property({ type: Object }) value?: { [specSetName: string]: any }
87
- @property({ type: Object }) dataItem?: DataItem
88
-
89
- @state() dataItemSpecSets: UseCaseDefinition[] = []
90
-
91
- @queryAll('ox-properties-dynamic-view') specSetViews!: NodeListOf<HTMLElement & { value: any }>
92
-
93
- render() {
94
- return html`<form @property-change=${(e: Event) => this.onChange(e)}>
95
- ${this.dataItemSpecSets.map(
96
- ({ name, description, specs }) =>
97
- html` <label
98
- ><div name>${name}</div>
99
- <div description><md-icon>info</md-icon> ${description}</div>
100
- <ox-properties-dynamic-view data-name=${name} .props=${specs} .value=${this.value?.[name]}>
101
- </ox-properties-dynamic-view
102
- ></label>`
103
- )}
104
- </form>`
105
- }
106
-
107
- updated(changes: PropertyValues<this>) {
108
- if (changes.has('dataItem')) {
109
- this.dataItemSpecSets = OxDataUseCase.getUseCases().map(useCase => useCase.getSpecification(this.dataItem!))
110
- }
111
- }
112
-
113
- private onChange(e: Event) {
114
- this.value = this.buildValue()
115
-
116
- this.dispatchEvent(
117
- new CustomEvent('change', {
118
- bubbles: true,
119
- composed: true,
120
- detail: { ...this.value }
121
- })
122
- )
123
- }
124
-
125
- private buildValue() {
126
- var value = {} as any
127
- this.specSetViews.forEach(view => (value[view.getAttribute('data-name')!] = view.value))
128
-
129
- return value
130
- }
131
- }
@@ -1,73 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import '@operato/input/ox-input-file.js'
3
- import './ox-data-sample-view'
4
-
5
- import { css, html, LitElement, nothing } from 'lit'
6
- import { customElement, property } from 'lit/decorators.js'
7
-
8
- @customElement('ox-data-ooc-badge')
9
- export class OxDataOocBadge extends LitElement {
10
- static styles = css`
11
- h3 {
12
- text-align: center;
13
- font: var(--title-font);
14
- margin: var(--spacing-small) 0;
15
- }
16
-
17
- md-icon {
18
- --md-icon-size: 80px;
19
- opacity: 0.4;
20
- color: var(--md-sys-color-secondary);
21
- }
22
-
23
- md-icon[filled] {
24
- font-variation-settings: 'FILL' 1;
25
- }
26
-
27
- div {
28
- position: absolute;
29
- top: 22px;
30
- left: 0;
31
- right: 0;
32
- font-size: 10px;
33
- color: var(--md-sys-color-surface);
34
- }
35
-
36
- [field-state] {
37
- display: block;
38
- border-radius: 4px;
39
- color: var(--md-sys-color-on-primary);
40
- background-color: var(--md-sys-color-primary);
41
- border-color: var(--md-sys-color-primary);
42
- box-shadow: var(--box-shadow);
43
- margin-top: var(--spacing-small);
44
- padding: 1px 3px;
45
- font-size: 0.8rem;
46
- }
47
-
48
- [danger] [field-state] {
49
- color: var(--md-sys-color-on-error);
50
- background-color: var(--md-sys-color-error);
51
- border-color: var(--md-sys-color-error);
52
- }
53
-
54
- [complete] [field-state] {
55
- color: var(--md-sys-color-on-error);
56
- background-color: var(--status-info-color);
57
- border-color: var(--status-info-color);
58
- }
59
- `
60
-
61
- @property({ type: String }) state?: string
62
-
63
- render() {
64
- const state = this.state
65
-
66
- return html`
67
- <h3 ?danger=${state != 'CORRECTED'} ?complete=${state == 'CORRECTED'}>
68
- <md-icon filled>shield</md-icon>
69
- <div>DATA OOC <span field-state>${state || ''}</span></div>
70
- </h3>
71
- `
72
- }
73
- }
@@ -1,43 +0,0 @@
1
- import '@operato/input/ox-input-file.js'
2
- import './ox-data-sample-view'
3
- import './ox-data-ooc-badge'
4
- import './ox-data-ooc-correction-part'
5
- import '@material/web/icon/icon.js'
6
-
7
- import { css, html, LitElement } from 'lit'
8
- import { customElement, property } from 'lit/decorators.js'
9
-
10
- import { DataOoc } from './types.js'
11
-
12
- @customElement('ox-data-ooc-brief-view')
13
- export class OxDataOocBriefView extends LitElement {
14
- static styles = css`
15
- :host {
16
- display: flex;
17
- flex-direction: column;
18
-
19
- position: relative;
20
- }
21
-
22
- ox-data-ooc-badge {
23
- position: absolute;
24
-
25
- margin: 0;
26
- padding: 0;
27
- right: 10px;
28
- width: 90px;
29
- }
30
- `
31
-
32
- @property({ type: Object }) dataOoc?: DataOoc
33
-
34
- render() {
35
- const { state } = this.dataOoc || {}
36
-
37
- return html`
38
- <ox-data-sample-view .dataSample=${this.dataOoc}></ox-data-sample-view>
39
- <ox-data-ooc-badge .state=${state}></ox-data-ooc-badge>
40
- <ox-data-ooc-correction-part .dataOoc=${this.dataOoc}></ox-data-ooc-correction-part>
41
- `
42
- }
43
- }
@@ -1,107 +0,0 @@
1
- import '@operato/input/ox-input-file.js'
2
- import '@material/web/icon/icon.js'
3
-
4
- import { css, html, LitElement, nothing } from 'lit'
5
- import { customElement, property } from 'lit/decorators.js'
6
-
7
- import { i18next } from '@operato/i18n'
8
-
9
- import { DataOoc } from './types.js'
10
-
11
- @customElement('ox-data-ooc-correction-part')
12
- export class OxDataOocCorrectionPart extends LitElement {
13
- static styles = css`
14
- :host {
15
- display: flex;
16
- flex-direction: column;
17
-
18
- position: relative;
19
- }
20
-
21
- h3 {
22
- margin: var(--title-margin);
23
- padding-top: 12px;
24
- font: var(--title-font);
25
- color: var(--title-text-color);
26
- font-size: 20px;
27
- }
28
-
29
- md-icon {
30
- font-size: 16px;
31
- }
32
-
33
- [field-state] {
34
- border-radius: 2px;
35
- background-color: var(--md-sys-color-on-primary-container);
36
- margin-left: var(--spacing-medium);
37
- padding: 1px 2px;
38
- font-size: 0.7rem;
39
- color: var(--md-sys-color-surface);
40
- }
41
-
42
- p {
43
- background-color: var(--md-sys-color-surface);
44
- box-shadow: var(--box-shadow);
45
- border-radius: var(--border-radius);
46
- margin: var(--page-description-margin);
47
- padding: var(--padding-default);
48
- font: var(--page-description-font);
49
- color: var(--md-sys-color-on-secondary-container);
50
- }
51
-
52
- md-icon {
53
- position: relative;
54
- top: 3px;
55
- margin: 0 2px 0 10px;
56
- }
57
-
58
- [field-info] {
59
- opacity: 0.7;
60
- }
61
-
62
- strong {
63
- display: block;
64
- font-weight: bold;
65
- font-size: 0.9rem;
66
- }
67
- `
68
-
69
- @property({ type: Object }) dataOoc?: DataOoc
70
-
71
- render() {
72
- const {
73
- correctiveInstruction = '',
74
- correctiveAction = '',
75
- reviewer,
76
- reviewedAt,
77
- corrector,
78
- correctedAt
79
- } = this.dataOoc || {}
80
- const formatter = new Intl.DateTimeFormat(navigator.language, { dateStyle: 'full', timeStyle: 'short' })
81
-
82
- return html`
83
- ${reviewer
84
- ? html`
85
- <h3>${i18next.t('label.corrective instruction')}</h3>
86
- <p>
87
- <span field-info
88
- >${formatter.format(new Date(reviewedAt!))} <md-icon>account_circle</md-icon>${reviewer.name}</span
89
- >
90
- <strong>${correctiveInstruction}</strong>
91
- </p>
92
- `
93
- : nothing}
94
- ${corrector
95
- ? html`
96
- <h3>${i18next.t('label.corrective action')}</h3>
97
- <p>
98
- <span field-info
99
- >${formatter.format(new Date(correctedAt!))} <md-icon>account_circle</md-icon>${corrector.name}</span
100
- >
101
- <strong>${correctiveAction}</strong>
102
- </p>
103
- `
104
- : nothing}
105
- `
106
- }
107
- }
@@ -1,74 +0,0 @@
1
- import '@operato/input/ox-input-file.js'
2
- import './ox-data-sample-view'
3
- import '@material/web/icon/icon.js'
4
-
5
- import { css, html, LitElement } from 'lit'
6
- import { customElement, property } from 'lit/decorators.js'
7
-
8
- import { DataOoc } from './types.js'
9
-
10
- @customElement('ox-data-ooc-history')
11
- export class OxDataOocHistory extends LitElement {
12
- static styles = css`
13
- md-icon {
14
- font-size: 16px;
15
- }
16
-
17
- [field-state] {
18
- border-radius: 2px;
19
- background-color: var(--md-sys-color-on-primary-container);
20
- margin-left: var(--spacing-medium);
21
- padding: 1px 2px;
22
- font-size: 0.7rem;
23
- color: var(--md-sys-color-surface);
24
- }
25
-
26
- p {
27
- background-color: var(--md-sys-color-surface);
28
- box-shadow: var(--box-shadow);
29
- border-radius: var(--border-radius);
30
- margin: var(--page-description-margin);
31
- padding: var(--padding-default);
32
- font: var(--page-description-font);
33
- color: var(--md-sys-color-on-secondary-container);
34
- }
35
-
36
- md-icon {
37
- position: relative;
38
- top: 3px;
39
- margin: 0 2px 0 10px;
40
- }
41
-
42
- [field-info] {
43
- opacity: 0.7;
44
- }
45
-
46
- strong {
47
- display: block;
48
- font-weight: bold;
49
- font-size: 0.9rem;
50
- }
51
- `
52
-
53
- @property({ type: Object }) dataOoc?: DataOoc
54
-
55
- render() {
56
- const { history = [] } = this.dataOoc || {}
57
- const formatter = new Intl.DateTimeFormat(navigator.language, { dateStyle: 'full', timeStyle: 'short' })
58
-
59
- return html`
60
- ${history.map(
61
- ({ user, state, comment, timestamp }) => html`
62
- <p page-history>
63
- <!--상태에 따라 추가로 danger, complete를 어트리뷰트로 추가시 배경컬러 변경되도록 해두었습니다-->
64
- <span field-info
65
- >${formatter.format(new Date(timestamp))} <md-icon>account_circle</md-icon>${user.name}</span
66
- >
67
- <span field-state>${state}</span>
68
- <strong>${comment}</strong>
69
- </p>
70
- `
71
- )}
72
- `
73
- }
74
- }