@things-factory/ops-master 10.1.33 → 10.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +1 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/master-change-approval.d.ts +14 -0
- package/dist-client/master-change-approval.js +36 -0
- package/dist-client/master-change-approval.js.map +1 -0
- package/dist-client/pages/master/material-definition.d.ts +4 -11
- package/dist-client/pages/master/material-definition.js +5 -53
- package/dist-client/pages/master/material-definition.js.map +1 -1
- package/dist-client/pages/master/material-revision-fields.d.ts +2 -0
- package/dist-client/pages/master/material-revision-fields.js +43 -0
- package/dist-client/pages/master/material-revision-fields.js.map +1 -0
- package/dist-client/pages/master/recipe-definition.d.ts +4 -135
- package/dist-client/pages/master/recipe-definition.js +7 -397
- package/dist-client/pages/master/recipe-definition.js.map +1 -1
- package/dist-client/pages/master/revision-comparison.d.ts +48 -0
- package/dist-client/pages/master/revision-comparison.js +553 -0
- package/dist-client/pages/master/revision-comparison.js.map +1 -0
- package/dist-client/pages/master/revision-view.d.ts +19 -0
- package/dist-client/pages/master/revision-view.js +133 -0
- package/dist-client/pages/master/revision-view.js.map +1 -0
- package/dist-client/pages/master/revision-workspace.d.ts +50 -0
- package/dist-client/pages/master/revision-workspace.js +752 -0
- package/dist-client/pages/master/revision-workspace.js.map +1 -0
- package/dist-client/pages/master/test-specification.d.ts +4 -92
- package/dist-client/pages/master/test-specification.js +5 -440
- package/dist-client/pages/master/test-specification.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/asset/asset-master-resolver.js +38 -34
- package/dist-server/service/asset/asset-master-resolver.js.map +1 -1
- package/dist-server/service/asset/index.d.ts +2 -2
- package/dist-server/service/equipment/equipment-master-resolver.js +54 -49
- package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
- package/dist-server/service/equipment/index.d.ts +2 -2
- package/dist-server/service/index.d.ts +18 -2
- package/dist-server/service/index.js +18 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/location/index.d.ts +1 -1
- package/dist-server/service/location/location-master-resolver.js +34 -31
- package/dist-server/service/location/location-master-resolver.js.map +1 -1
- package/dist-server/service/master-axis.js +1 -1
- package/dist-server/service/master-axis.js.map +1 -1
- package/dist-server/service/master-change-policy.d.ts +23 -0
- package/dist-server/service/master-change-policy.js +134 -0
- package/dist-server/service/master-change-policy.js.map +1 -0
- package/dist-server/service/master-conformance-resolver.js +11 -1
- package/dist-server/service/master-conformance-resolver.js.map +1 -1
- package/dist-server/service/master-crud.js +9 -0
- package/dist-server/service/master-crud.js.map +1 -1
- package/dist-server/service/master-list.js +6 -0
- package/dist-server/service/master-list.js.map +1 -1
- package/dist-server/service/material/index.d.ts +3 -2
- package/dist-server/service/material/index.js +4 -2
- package/dist-server/service/material/index.js.map +1 -1
- package/dist-server/service/material/material-axis-status.d.ts +8 -0
- package/dist-server/service/material/material-axis-status.js +19 -0
- package/dist-server/service/material/material-axis-status.js.map +1 -0
- package/dist-server/service/material/material-definition.d.ts +3 -0
- package/dist-server/service/material/material-definition.js +13 -0
- package/dist-server/service/material/material-definition.js.map +1 -1
- package/dist-server/service/material/material-effective-list.d.ts +39 -0
- package/dist-server/service/material/material-effective-list.js +54 -0
- package/dist-server/service/material/material-effective-list.js.map +1 -0
- package/dist-server/service/material/material-master-resolver.js +59 -61
- package/dist-server/service/material/material-master-resolver.js.map +1 -1
- package/dist-server/service/material/material-query.js +9 -2
- package/dist-server/service/material/material-query.js.map +1 -1
- package/dist-server/service/material/material-revision-content.d.ts +10 -0
- package/dist-server/service/material/material-revision-content.js +92 -0
- package/dist-server/service/material/material-revision-content.js.map +1 -0
- package/dist-server/service/material/material-revision-resolver.d.ts +54 -0
- package/dist-server/service/material/material-revision-resolver.js +76 -0
- package/dist-server/service/material/material-revision-resolver.js.map +1 -0
- package/dist-server/service/material/material-revision-service.d.ts +144 -0
- package/dist-server/service/material/material-revision-service.js +534 -0
- package/dist-server/service/material/material-revision-service.js.map +1 -0
- package/dist-server/service/material/material-revision.d.ts +84 -0
- package/dist-server/service/material/material-revision.js +339 -0
- package/dist-server/service/material/material-revision.js.map +1 -0
- package/dist-server/service/operation/index.d.ts +1 -1
- package/dist-server/service/operation/operation-definition.js +1 -0
- package/dist-server/service/operation/operation-definition.js.map +1 -1
- package/dist-server/service/operation/operation-master-resolver.js +30 -30
- package/dist-server/service/operation/operation-master-resolver.js.map +1 -1
- package/dist-server/service/personnel/index.d.ts +2 -2
- package/dist-server/service/personnel/personnel-master-resolver.js +38 -34
- package/dist-server/service/personnel/personnel-master-resolver.js.map +1 -1
- package/dist-server/service/quality/nonconformance-cause-resolver.js +17 -14
- package/dist-server/service/quality/nonconformance-cause-resolver.js.map +1 -1
- package/dist-server/service/recipe/index.d.ts +3 -2
- package/dist-server/service/recipe/index.js +4 -2
- package/dist-server/service/recipe/index.js.map +1 -1
- package/dist-server/service/recipe/recipe-definition.d.ts +4 -0
- package/dist-server/service/recipe/recipe-definition.js +14 -1
- package/dist-server/service/recipe/recipe-definition.js.map +1 -1
- package/dist-server/service/recipe/recipe-effective-list.d.ts +49 -0
- package/dist-server/service/recipe/recipe-effective-list.js +96 -0
- package/dist-server/service/recipe/recipe-effective-list.js.map +1 -0
- package/dist-server/service/recipe/recipe-master-resolver.js +32 -28
- package/dist-server/service/recipe/recipe-master-resolver.js.map +1 -1
- package/dist-server/service/recipe/recipe-query.js +9 -2
- package/dist-server/service/recipe/recipe-query.js.map +1 -1
- package/dist-server/service/recipe/recipe-revision-content.d.ts +32 -0
- package/dist-server/service/recipe/recipe-revision-content.js +65 -0
- package/dist-server/service/recipe/recipe-revision-content.js.map +1 -0
- package/dist-server/service/recipe/recipe-revision-line.d.ts +30 -0
- package/dist-server/service/recipe/recipe-revision-line.js +95 -0
- package/dist-server/service/recipe/recipe-revision-line.js.map +1 -0
- package/dist-server/service/recipe/recipe-revision-resolver.d.ts +59 -0
- package/dist-server/service/recipe/recipe-revision-resolver.js +76 -0
- package/dist-server/service/recipe/recipe-revision-resolver.js.map +1 -0
- package/dist-server/service/recipe/recipe-revision-service.d.ts +147 -0
- package/dist-server/service/recipe/recipe-revision-service.js +591 -0
- package/dist-server/service/recipe/recipe-revision-service.js.map +1 -0
- package/dist-server/service/recipe/recipe-revision.d.ts +100 -0
- package/dist-server/service/recipe/recipe-revision.js +373 -0
- package/dist-server/service/recipe/recipe-revision.js.map +1 -0
- package/dist-server/service/recipe/route-definition.js +1 -0
- package/dist-server/service/recipe/route-definition.js.map +1 -1
- package/dist-server/service/resource/class-members.js +9 -0
- package/dist-server/service/resource/class-members.js.map +1 -1
- package/dist-server/service/status/master-status.js +2 -1
- package/dist-server/service/status/master-status.js.map +1 -1
- package/dist-server/service/test-spec/index.d.ts +3 -2
- package/dist-server/service/test-spec/index.js +4 -2
- package/dist-server/service/test-spec/index.js.map +1 -1
- package/dist-server/service/test-spec/test-spec-effective-list.d.ts +34 -0
- package/dist-server/service/test-spec/test-spec-effective-list.js +42 -0
- package/dist-server/service/test-spec/test-spec-effective-list.js.map +1 -0
- package/dist-server/service/test-spec/test-spec-master-resolver.d.ts +4 -14
- package/dist-server/service/test-spec/test-spec-master-resolver.js +12 -83
- package/dist-server/service/test-spec/test-spec-master-resolver.js.map +1 -1
- package/dist-server/service/test-spec/test-spec-query.js +7 -1
- package/dist-server/service/test-spec/test-spec-query.js.map +1 -1
- package/dist-server/service/test-spec/test-spec-revision-resolver.d.ts +53 -0
- package/dist-server/service/test-spec/test-spec-revision-resolver.js +76 -0
- package/dist-server/service/test-spec/test-spec-revision-resolver.js.map +1 -0
- package/dist-server/service/test-spec/test-spec-revision-service.d.ts +138 -0
- package/dist-server/service/test-spec/test-spec-revision-service.js +538 -0
- package/dist-server/service/test-spec/test-spec-revision-service.js.map +1 -0
- package/dist-server/service/test-spec/test-spec-revision.d.ts +84 -0
- package/dist-server/service/test-spec/test-spec-revision.js +334 -0
- package/dist-server/service/test-spec/test-spec-revision.js.map +1 -0
- package/dist-server/service/test-spec/test-specification.d.ts +6 -0
- package/dist-server/service/test-spec/test-specification.js +15 -2
- package/dist-server/service/test-spec/test-specification.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -6
- package/translations/en.json +148 -0
- package/translations/ko.json +148 -0
|
@@ -1,447 +1,12 @@
|
|
|
1
|
-
import { __decorate
|
|
2
|
-
import '
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { customElement, query, state } from 'lit/decorators.js';
|
|
6
|
-
import { DataGrist } from '@operato/data-grist';
|
|
7
|
-
import { client } from '@operato/graphql';
|
|
8
|
-
import { i18next } from '@operato/i18n';
|
|
9
|
-
import { CustomAlert } from '@operato/shell';
|
|
10
|
-
import { notify } from '@operato/layout';
|
|
11
|
-
import { EFFECTIVE, IDENTITY, MasterPageBase } from './master-page-base.js';
|
|
12
|
-
/**
|
|
13
|
-
* 무엇을 어떻게 검사하나 — 항목과 상하한과 방법.
|
|
14
|
-
*
|
|
15
|
-
* 인원 등급과 자산 등급이 이것을 요구 검사로 가리킨다.
|
|
16
|
-
*
|
|
17
|
-
* ── 왜 이 축만 자기 화면을 갖나 ────────────────────────────────────────────
|
|
18
|
-
* 다른 축의 칸은 글자 · 숫자 · 날짜라서 공통 그리드 하나로 충분하다. 검사 명세는 다르다. **명세 하나가
|
|
19
|
-
* 기준 여럿을 갖는다.** 기준 하나는 이름 · 설명 · 측정 속성 · 하한 · 상한 · 단위 · 표현식을 갖는다.
|
|
20
|
-
*
|
|
21
|
-
* 그것을 한 칸에 JSON 원문으로 보이면 품질팀장이 상한을 40.5 로 고치려고 중괄호를 세게 된다. 그것은
|
|
22
|
-
* 기준 정보를 다루는 화면이 아니라 개발자가 JSON 을 손보는 화면이다. 실제로 한동안 그랬다.
|
|
23
|
-
*
|
|
24
|
-
* ── 화면은 JSON 을 모른다 ──────────────────────────────────────────────────
|
|
25
|
-
* 서버가 기준을 **행으로** 낸다(`testSpecificationCriteria`) 그리고 행으로 받는다
|
|
26
|
-
* (`updateTestSpecificationCriteria`). 옮김은 서버의 `criteria-rows.ts` 한 곳이 맡는다.
|
|
27
|
-
*
|
|
28
|
-
* 그래서 저장이 언젠가 표로 내려가도 **이 파일은 바뀌지 않는다.** 화면이 JSON 을 풀면 그날 이 코드가
|
|
29
|
-
* 통째로 버려진다.
|
|
30
|
-
*
|
|
31
|
-
* ── 위아래로 나눈다 ────────────────────────────────────────────────────────
|
|
32
|
-
* 위는 명세 목록, 아래는 **고른 명세의 기준**이다. 아무것도 안 고른 상태를 빈 표로 두지 않는다 —
|
|
33
|
-
* 빈 표는 「기준이 없는 명세」와 같아 보이고, 그러면 사람이 기준을 다시 적는다.
|
|
34
|
-
*/
|
|
35
|
-
let OpsMasterTestSpecification = class OpsMasterTestSpecification extends MasterPageBase {
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
import { RevisionWorkspace } from './revision-workspace.js';
|
|
4
|
+
let OpsMasterTestSpecification = class OpsMasterTestSpecification extends RevisionWorkspace {
|
|
36
5
|
constructor() {
|
|
37
6
|
super(...arguments);
|
|
38
|
-
|
|
39
|
-
* 지금 체크된 명세들.
|
|
40
|
-
*
|
|
41
|
-
* 아래는 **하나의 기준만** 보이는데 위에서 여럿을 체크할 수 있다(명세 여럿을 한 번에 종료하는 데
|
|
42
|
-
* 쓴다). 그때 화면이 아무 말도 안 하면 보이는 기준이 **여럿을 합친 것**으로 읽힌다. 그래서 몇 개가
|
|
43
|
-
* 체크돼 있는지 세어 말한다.
|
|
44
|
-
*/
|
|
45
|
-
this.checked = new Set();
|
|
46
|
-
/**
|
|
47
|
-
* 아래 표에서 아직 저장하지 않은 기준이 몇 건인가.
|
|
48
|
-
*
|
|
49
|
-
* ── 왜 세어 보이나 (2026-09-05) ────────────────────────────────────────
|
|
50
|
-
* 이 화면은 표가 둘인데 「저장」은 하나다. 아래를 고쳐 놓고도 그것이 저장 대상인지 화면 어디에도
|
|
51
|
-
* 안 적혀 있었다. 「복잡한 편집인데 저장 버튼의 기능이 위험해 보인다」(사용자).
|
|
52
|
-
*
|
|
53
|
-
* 세어서 표 머리에 적으면 두 가지가 한꺼번에 풀린다. 저장을 누르기 전에 **무엇이 나가는지** 보이고,
|
|
54
|
-
* 다른 명세로 넘어가려 할 때 **버릴 것이 있다**는 사실도 보인다.
|
|
55
|
-
*/
|
|
56
|
-
this.dirtyCriteria = 0;
|
|
57
|
-
}
|
|
58
|
-
static { this.styles = [
|
|
59
|
-
...MasterPageBase.styles,
|
|
60
|
-
css `
|
|
61
|
-
:host {
|
|
62
|
-
display: flex;
|
|
63
|
-
flex-direction: column;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* 위가 목록, 아래가 그 명세의 기준. 기준 쪽을 조금 더 준다 — 줄이 여럿이다. */
|
|
67
|
-
.split {
|
|
68
|
-
display: flex;
|
|
69
|
-
flex-direction: column;
|
|
70
|
-
flex: 1;
|
|
71
|
-
min-height: 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.split > ox-grist {
|
|
75
|
-
flex: 4;
|
|
76
|
-
min-height: 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.criteria {
|
|
80
|
-
display: flex;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
flex: 5;
|
|
83
|
-
min-height: 0;
|
|
84
|
-
border-top: 1px solid var(--ui-line, #c9d4d9);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.criteria-head {
|
|
88
|
-
display: flex;
|
|
89
|
-
align-items: center;
|
|
90
|
-
justify-content: space-between;
|
|
91
|
-
gap: 8px;
|
|
92
|
-
padding: 6px 12px;
|
|
93
|
-
background-color: var(--ui-panel-2, #f3f6f7);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.criteria-head .what {
|
|
97
|
-
font-size: 13px;
|
|
98
|
-
font-weight: 600;
|
|
99
|
-
color: var(--ui-ink, #17272f);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* 아직 저장하지 않은 것이 있다 — 저장을 누르기 전에 무엇이 나가는지 여기서 읽는다. */
|
|
103
|
-
.criteria-head .pending {
|
|
104
|
-
margin-right: 8px;
|
|
105
|
-
font-family: inherit;
|
|
106
|
-
font-style: normal;
|
|
107
|
-
font-weight: 600;
|
|
108
|
-
color: var(--status-warning-color, #b26a00);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.criteria-head .among {
|
|
112
|
-
margin-left: 6px;
|
|
113
|
-
font-style: normal;
|
|
114
|
-
color: var(--status-warning-color, #b26a00);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.criteria-head .who {
|
|
118
|
-
font-family: var(--theme-font-mono, monospace);
|
|
119
|
-
font-size: 12px;
|
|
120
|
-
color: var(--ui-muted, #57676f);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.criteria ox-grist {
|
|
124
|
-
flex: 1;
|
|
125
|
-
min-height: 0;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/* 아직 아무 명세도 안 골랐다. 빈 표를 보이지 않는다. */
|
|
129
|
-
.nothing-picked {
|
|
130
|
-
flex: 1;
|
|
131
|
-
display: flex;
|
|
132
|
-
align-items: center;
|
|
133
|
-
justify-content: center;
|
|
134
|
-
padding: 24px;
|
|
135
|
-
text-align: center;
|
|
136
|
-
font-size: 13px;
|
|
137
|
-
line-height: 1.6;
|
|
138
|
-
color: var(--ui-muted, #57676f);
|
|
139
|
-
}
|
|
140
|
-
`
|
|
141
|
-
]; }
|
|
142
|
-
get spec() {
|
|
143
|
-
return {
|
|
144
|
-
key: 'testSpecification',
|
|
145
|
-
title: 'test-specification',
|
|
146
|
-
help: 'ops-master/test-specification',
|
|
147
|
-
query: 'testSpecifications',
|
|
148
|
-
mutation: 'updateMultipleTestSpecification',
|
|
149
|
-
columns: [...IDENTITY, ...EFFECTIVE]
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
countDirtyCriteria() {
|
|
153
|
-
this.dirtyCriteria = (this.criteriaGrist?.dirtyRecords ?? []).length;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* 기준 표의 구성.
|
|
157
|
-
*
|
|
158
|
-
* **단위 칸을 좁히지 않는다.** `MMT`(mm) · `CEL`(℃) 같은 UN/CEFACT 코드가 들어가고, 잘리면
|
|
159
|
-
* 「무엇으로 잰 값인지」가 화면에서 사라진다.
|
|
160
|
-
*/
|
|
161
|
-
get criteriaConfig() {
|
|
162
|
-
return {
|
|
163
|
-
/*
|
|
164
|
-
* 명세는 골랐는데 그 안에 판단 기준이 하나도 없는 자리다. 왼쪽의 빈 목록과 뜻이 다르다 —
|
|
165
|
-
* 「검사 명세가 없다」가 아니라 「이 명세가 아직 무엇으로 합격을 가르는지 말하지 않는다」이다.
|
|
166
|
-
*
|
|
167
|
-
* 고른 것이 없을 때는 아래 render 가 표 자체를 보이지 않는다(그 자리 주석 참조).
|
|
168
|
-
*/
|
|
169
|
-
emptyNote: {
|
|
170
|
-
title: i18next.t('text.criteria-empty'),
|
|
171
|
-
description: i18next.t('text.criteria-empty-hint')
|
|
172
|
-
},
|
|
173
|
-
list: { fields: ['id', 'description'], details: [] },
|
|
174
|
-
columns: [
|
|
175
|
-
{ type: 'gutter', gutterName: 'sequence', fixed: true },
|
|
176
|
-
{ type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },
|
|
177
|
-
{
|
|
178
|
-
type: 'string',
|
|
179
|
-
name: 'id',
|
|
180
|
-
header: i18next.t('field.criterion-id'),
|
|
181
|
-
record: { editable: true },
|
|
182
|
-
width: 160,
|
|
183
|
-
fixed: true
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
type: 'string',
|
|
187
|
-
name: 'description',
|
|
188
|
-
header: i18next.t('field.description'),
|
|
189
|
-
record: { editable: true },
|
|
190
|
-
width: 180
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
type: 'string',
|
|
194
|
-
name: 'evaluatedPropertyId',
|
|
195
|
-
header: i18next.t('field.evaluated-property'),
|
|
196
|
-
record: { editable: true },
|
|
197
|
-
width: 150
|
|
198
|
-
},
|
|
199
|
-
{ type: 'float', name: 'minimum', header: i18next.t('field.minimum'), record: { editable: true }, width: 100 },
|
|
200
|
-
{ type: 'float', name: 'maximum', header: i18next.t('field.maximum'), record: { editable: true }, width: 100 },
|
|
201
|
-
{ type: 'string', name: 'uom', header: i18next.t('field.uom'), record: { editable: true }, width: 90 },
|
|
202
|
-
{
|
|
203
|
-
type: 'string',
|
|
204
|
-
name: 'expression',
|
|
205
|
-
header: i18next.t('field.expression'),
|
|
206
|
-
record: { editable: true },
|
|
207
|
-
width: 200
|
|
208
|
-
}
|
|
209
|
-
],
|
|
210
|
-
rows: { appendable: true, selectable: { multiple: true } }
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
render() {
|
|
214
|
-
return html `
|
|
215
|
-
<div class="split">
|
|
216
|
-
<ox-grist
|
|
217
|
-
.mode=${this.mode}
|
|
218
|
-
.config=${this.gristConfig}
|
|
219
|
-
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
220
|
-
.personalConfigProvider=${this.getPagePreferenceProvider(`ox-grist-${this.spec.key}`)}
|
|
221
|
-
@select-record-change=${(e) => this.pick(e)}
|
|
222
|
-
>
|
|
223
|
-
<div slot="headroom" class="header">${this.renderScope()}</div>
|
|
224
|
-
<ox-grist-personalizer slot="setting"></ox-grist-personalizer>
|
|
225
|
-
</ox-grist>
|
|
226
|
-
|
|
227
|
-
<div class="criteria">
|
|
228
|
-
<div class="criteria-head">
|
|
229
|
-
<span class="what">${i18next.t('field.criteria')}</span>
|
|
230
|
-
<span class="who">
|
|
231
|
-
${this.dirtyCriteria
|
|
232
|
-
? html `<em class="pending">${i18next.t('text.n-unsaved-criteria', { count: this.dirtyCriteria })}</em>`
|
|
233
|
-
: ''}
|
|
234
|
-
${this.picked ?? ''}
|
|
235
|
-
<!--
|
|
236
|
-
여럿 체크했는데 하나만 보이는 상태를 **말한다.** 아무 말도 안 하면 보이는 기준이
|
|
237
|
-
여럿을 합친 것으로 읽힌다.
|
|
238
|
-
-->
|
|
239
|
-
${this.checked.size > 1
|
|
240
|
-
? html `<em class="among">${i18next.t('text.of-n-selected', { count: this.checked.size })}</em>`
|
|
241
|
-
: ''}
|
|
242
|
-
</span>
|
|
243
|
-
</div>
|
|
244
|
-
${this.picked
|
|
245
|
-
? html `
|
|
246
|
-
<ox-grist
|
|
247
|
-
id="criteria-grist"
|
|
248
|
-
mode="GRID"
|
|
249
|
-
.config=${this.criteriaConfig}
|
|
250
|
-
.fetchHandler=${this.fetchCriteria.bind(this)}
|
|
251
|
-
@field-change=${() => this.countDirtyCriteria()}
|
|
252
|
-
@record-change=${() => this.countDirtyCriteria()}
|
|
253
|
-
></ox-grist>
|
|
254
|
-
`
|
|
255
|
-
: html `
|
|
256
|
-
<!--
|
|
257
|
-
빈 표를 보이지 않는다. 빈 표는 「기준이 없는 명세」와 같아 보이고, 그러면 사람이
|
|
258
|
-
기준을 다시 적는다.
|
|
259
|
-
-->
|
|
260
|
-
<div class="nothing-picked">${i18next.t('text.pick-a-specification-to-see-criteria')}</div>
|
|
261
|
-
`}
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
`;
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* 위에서 고른 줄이 아래를 정한다.
|
|
268
|
-
*
|
|
269
|
-
* ── detail 모양이 거터 설정에 따라 다르다 ─────────────────────────────────
|
|
270
|
-
* `gutter-row-selector.js` 를 보면 두 모양을 낸다.
|
|
271
|
-
*
|
|
272
|
-
* ```
|
|
273
|
-
* multiple: true { added: [record], removed: [] } ← 이 화면이 쓰는 것
|
|
274
|
-
* 그 밖 { records: [record] }
|
|
275
|
-
* ```
|
|
276
|
-
*
|
|
277
|
-
* 한쪽만 읽으면 **오류보고없이 아무것도 안 골라진다.** 오류가 나지 않고 아래 표만 영원히 안내 문구로
|
|
278
|
-
* 남는다. 처음에 `records` 만 읽어서 실제로 그랬다. 그리드가 세우는 `__selected__` 를 읽는 방법도
|
|
279
|
-
* 있는데, 그 표시는 이 화면에 오지 않았다. 그래서 **둘 다 읽는다.**
|
|
280
|
-
*
|
|
281
|
-
* 여럿 고르면 마지막에 고른 것을 본다. 아래는 명세 하나의 기준을 보이는 자리라 둘을 함께 보일 수
|
|
282
|
-
* 없고, 아무 것이나 고르면 사람은 그것이 무엇의 기준인지 모른다.
|
|
283
|
-
*/
|
|
284
|
-
async pick(e) {
|
|
285
|
-
const detail = (e.detail ?? {});
|
|
286
|
-
const nameOf = (record) => String(record?.name ?? '');
|
|
287
|
-
const added = detail.added ?? detail.records ?? [];
|
|
288
|
-
const removed = detail.removed ?? [];
|
|
289
|
-
/* `records` 는 「지금 고른 것 전부」다. `added`·`removed` 는 「이번에 바뀐 것」이다. */
|
|
290
|
-
const next = detail.records ? new Set() : new Set(this.checked);
|
|
291
|
-
for (const record of removed)
|
|
292
|
-
next.delete(nameOf(record));
|
|
293
|
-
for (const record of added)
|
|
294
|
-
next.add(nameOf(record));
|
|
295
|
-
/* 아래에 무엇이 보일지 먼저 정하고, 바꾸기 전에 고치던 것이 있는지 본다. */
|
|
296
|
-
let picked = this.picked;
|
|
297
|
-
if (added.length) {
|
|
298
|
-
picked = nameOf(added[added.length - 1]) || undefined;
|
|
299
|
-
}
|
|
300
|
-
else if (removed.some(record => nameOf(record) === this.picked)) {
|
|
301
|
-
/* 지금 보고 있던 것을 풀었다. 남은 것이 하나면 그것을 보인다. */
|
|
302
|
-
picked = next.size ? [...next][next.size - 1] : undefined;
|
|
303
|
-
}
|
|
304
|
-
else if (detail.records && !detail.records.length) {
|
|
305
|
-
picked = undefined;
|
|
306
|
-
}
|
|
307
|
-
this.checked = next;
|
|
308
|
-
if (picked === this.picked) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
/*
|
|
312
|
-
* ── 고치던 기준을 **말없이 버리지 않는다** (2026-09-05) ────────────────
|
|
313
|
-
*
|
|
314
|
-
* 아래 표는 「지금 고른 명세」에 걸려 있다. 명세를 바꾸면 아래는 새 명세의 기준을 다시 읽어
|
|
315
|
-
* 오고, 고치던 값은 그때 사라진다. [확인함] 2026-09-05 · CP-CAM-LEAK 의 하한을 고쳐 두고
|
|
316
|
-
* CP-CAM-OQC 를 고른 뒤 돌아오니 원래 값이었다. 화면은 아무 말도 하지 않았다.
|
|
317
|
-
*
|
|
318
|
-
* 두 갈래로만 묻는다 — 어느 쪽을 골라도 넘어간다. 「머무르기」를 두면 위의 체크는 이미 옮겨
|
|
319
|
-
* 갔는데 아래만 옛 명세를 보이는, 화면과 사실이 어긋난 상태가 남는다.
|
|
320
|
-
*/
|
|
321
|
-
if (this.dirtyCriteria > 0) {
|
|
322
|
-
const answer = await CustomAlert({
|
|
323
|
-
type: 'question',
|
|
324
|
-
title: i18next.t('text.n-unsaved-criteria', { count: this.dirtyCriteria }),
|
|
325
|
-
text: i18next.t('text.leaving-discards-unsaved-criteria'),
|
|
326
|
-
confirmButton: { text: i18next.t('button.save-and-leave') },
|
|
327
|
-
cancelButton: { text: i18next.t('button.discard-and-leave') }
|
|
328
|
-
});
|
|
329
|
-
if (answer?.isConfirmed) {
|
|
330
|
-
await this.save();
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
this.picked = picked;
|
|
334
|
-
this.dirtyCriteria = 0;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* 고른 명세의 기준을 읽는다.
|
|
338
|
-
*
|
|
339
|
-
* 못 읽으면 **빈 목록으로 바꾸지 않는다.** 빈 목록은 「기준이 없다」와 같아 보이고, 그러면 사람이
|
|
340
|
-
* 기준을 다시 적어 넣는다. 무엇이 잘못됐는지 말하고 비운다.
|
|
341
|
-
*/
|
|
342
|
-
async fetchCriteria() {
|
|
343
|
-
if (!this.picked) {
|
|
344
|
-
return { total: 0, records: [] };
|
|
345
|
-
}
|
|
346
|
-
try {
|
|
347
|
-
const response = await client.query({
|
|
348
|
-
query: gql `
|
|
349
|
-
query ($name: String!) {
|
|
350
|
-
testSpecificationCriteria(name: $name) {
|
|
351
|
-
id
|
|
352
|
-
description
|
|
353
|
-
evaluatedPropertyId
|
|
354
|
-
minimum
|
|
355
|
-
maximum
|
|
356
|
-
uom
|
|
357
|
-
expression
|
|
358
|
-
keptFields
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
`,
|
|
362
|
-
variables: { name: this.picked },
|
|
363
|
-
fetchPolicy: 'no-cache'
|
|
364
|
-
});
|
|
365
|
-
const rows = (response.data?.testSpecificationCriteria ?? []);
|
|
366
|
-
/* 어느 줄인지 서버가 알아야 고칠 수 있다 — 이름은 고칠 수 있으므로 이름으로 찾을 수 없다. */
|
|
367
|
-
return { total: rows.length, records: rows.map((row, index) => ({ ...row, rowIndex: index })) };
|
|
368
|
-
}
|
|
369
|
-
catch (error) {
|
|
370
|
-
notify({ message: error.message, level: 'error' });
|
|
371
|
-
return { total: 0, records: [] };
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* 저장 — **위와 아래를 따로 낸다.**
|
|
376
|
-
*
|
|
377
|
-
* 명세 자체(이름 · 버전 · 유효 기간)는 공통 규칙이 저장하고, 기준은 이 축의 뮤테이션이 저장한다.
|
|
378
|
-
* 기준이 막히면 **명세 쪽도 되돌린다.** 절반만 저장되면 화면과 저장된 것이 달라진다.
|
|
379
|
-
*/
|
|
380
|
-
async save() {
|
|
381
|
-
const criteriaPatches = this.picked ? (this.criteriaGrist?.dirtyRecords ?? []) : [];
|
|
382
|
-
if (criteriaPatches.length) {
|
|
383
|
-
let response;
|
|
384
|
-
try {
|
|
385
|
-
response = await client.mutate({
|
|
386
|
-
mutation: gql `
|
|
387
|
-
mutation ($name: String!, $patches: [Object!]!) {
|
|
388
|
-
updateTestSpecificationCriteria(name: $name, patches: $patches) {
|
|
389
|
-
id
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
`,
|
|
393
|
-
variables: { name: this.picked, patches: criteriaPatches }
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
catch (error) {
|
|
397
|
-
/* 무엇이 왜 막혔는지 그대로 보인다 — 「저장할 수 없습니다」로는 고칠 곳을 모른다. */
|
|
398
|
-
notify({ message: error.message, level: 'error' });
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
/*
|
|
402
|
-
* **`errors` 를 직접 본다.** 이 앱의 GraphQL 클라이언트는 서버가 거절해도 던지지 않고
|
|
403
|
-
* `response.errors` 에 담아 돌려준다. `try/catch` 만 두면 거절이 오류 없이 지나가고, 화면은
|
|
404
|
-
* 아무 일 없었다는 듯 다음으로 넘어간다 — **고친 것이 버려졌는데 사람은 저장된 줄 안다.**
|
|
405
|
-
* 2026-09-02에 실제로 그랬다(단위를 지운 기준이 서버에서 막혔는데 화면은 「바뀐 것이 없습니다」).
|
|
406
|
-
*/
|
|
407
|
-
if (response?.errors?.length) {
|
|
408
|
-
notify({ message: response.errors.map((error) => error.message).join('\n'), level: 'error' });
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
await super.save();
|
|
413
|
-
if (criteriaPatches.length) {
|
|
414
|
-
this.criteriaGrist?.fetch();
|
|
415
|
-
}
|
|
7
|
+
this.axis = 'testSpecification';
|
|
416
8
|
}
|
|
417
9
|
};
|
|
418
|
-
__decorate([
|
|
419
|
-
state(),
|
|
420
|
-
__metadata("design:type", String)
|
|
421
|
-
], OpsMasterTestSpecification.prototype, "picked", void 0);
|
|
422
|
-
__decorate([
|
|
423
|
-
state(),
|
|
424
|
-
__metadata("design:type", Object)
|
|
425
|
-
], OpsMasterTestSpecification.prototype, "checked", void 0);
|
|
426
|
-
__decorate([
|
|
427
|
-
query('#criteria-grist'),
|
|
428
|
-
__metadata("design:type", DataGrist
|
|
429
|
-
/**
|
|
430
|
-
* 아래 표에서 아직 저장하지 않은 기준이 몇 건인가.
|
|
431
|
-
*
|
|
432
|
-
* ── 왜 세어 보이나 (2026-09-05) ────────────────────────────────────────
|
|
433
|
-
* 이 화면은 표가 둘인데 「저장」은 하나다. 아래를 고쳐 놓고도 그것이 저장 대상인지 화면 어디에도
|
|
434
|
-
* 안 적혀 있었다. 「복잡한 편집인데 저장 버튼의 기능이 위험해 보인다」(사용자).
|
|
435
|
-
*
|
|
436
|
-
* 세어서 표 머리에 적으면 두 가지가 한꺼번에 풀린다. 저장을 누르기 전에 **무엇이 나가는지** 보이고,
|
|
437
|
-
* 다른 명세로 넘어가려 할 때 **버릴 것이 있다**는 사실도 보인다.
|
|
438
|
-
*/
|
|
439
|
-
)
|
|
440
|
-
], OpsMasterTestSpecification.prototype, "criteriaGrist", void 0);
|
|
441
|
-
__decorate([
|
|
442
|
-
state(),
|
|
443
|
-
__metadata("design:type", Object)
|
|
444
|
-
], OpsMasterTestSpecification.prototype, "dirtyCriteria", void 0);
|
|
445
10
|
OpsMasterTestSpecification = __decorate([
|
|
446
11
|
customElement('ops-master-test-specification')
|
|
447
12
|
], OpsMasterTestSpecification);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-specification.js","sourceRoot":"","sources":["../../../client/pages/master/test-specification.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { CustomAlert } from '@operato/shell'\nimport { notify } from '@operato/layout'\n\nimport { EFFECTIVE, IDENTITY, MasterPageBase, MasterSpec } from './master-page-base.js'\n\n/**\n * 무엇을 어떻게 검사하나 — 항목과 상하한과 방법.\n *\n * 인원 등급과 자산 등급이 이것을 요구 검사로 가리킨다.\n *\n * ── 왜 이 축만 자기 화면을 갖나 ────────────────────────────────────────────\n * 다른 축의 칸은 글자 · 숫자 · 날짜라서 공통 그리드 하나로 충분하다. 검사 명세는 다르다. **명세 하나가\n * 기준 여럿을 갖는다.** 기준 하나는 이름 · 설명 · 측정 속성 · 하한 · 상한 · 단위 · 표현식을 갖는다.\n *\n * 그것을 한 칸에 JSON 원문으로 보이면 품질팀장이 상한을 40.5 로 고치려고 중괄호를 세게 된다. 그것은\n * 기준 정보를 다루는 화면이 아니라 개발자가 JSON 을 손보는 화면이다. 실제로 한동안 그랬다.\n *\n * ── 화면은 JSON 을 모른다 ──────────────────────────────────────────────────\n * 서버가 기준을 **행으로** 낸다(`testSpecificationCriteria`) 그리고 행으로 받는다\n * (`updateTestSpecificationCriteria`). 옮김은 서버의 `criteria-rows.ts` 한 곳이 맡는다.\n *\n * 그래서 저장이 언젠가 표로 내려가도 **이 파일은 바뀌지 않는다.** 화면이 JSON 을 풀면 그날 이 코드가\n * 통째로 버려진다.\n *\n * ── 위아래로 나눈다 ────────────────────────────────────────────────────────\n * 위는 명세 목록, 아래는 **고른 명세의 기준**이다. 아무것도 안 고른 상태를 빈 표로 두지 않는다 —\n * 빈 표는 「기준이 없는 명세」와 같아 보이고, 그러면 사람이 기준을 다시 적는다.\n */\n@customElement('ops-master-test-specification')\nexport class OpsMasterTestSpecification extends MasterPageBase {\n static styles = [\n ...MasterPageBase.styles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n }\n\n /* 위가 목록, 아래가 그 명세의 기준. 기준 쪽을 조금 더 준다 — 줄이 여럿이다. */\n .split {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n }\n\n .split > ox-grist {\n flex: 4;\n min-height: 0;\n }\n\n .criteria {\n display: flex;\n flex-direction: column;\n flex: 5;\n min-height: 0;\n border-top: 1px solid var(--ui-line, #c9d4d9);\n }\n\n .criteria-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 12px;\n background-color: var(--ui-panel-2, #f3f6f7);\n }\n\n .criteria-head .what {\n font-size: 13px;\n font-weight: 600;\n color: var(--ui-ink, #17272f);\n }\n\n /* 아직 저장하지 않은 것이 있다 — 저장을 누르기 전에 무엇이 나가는지 여기서 읽는다. */\n .criteria-head .pending {\n margin-right: 8px;\n font-family: inherit;\n font-style: normal;\n font-weight: 600;\n color: var(--status-warning-color, #b26a00);\n }\n\n .criteria-head .among {\n margin-left: 6px;\n font-style: normal;\n color: var(--status-warning-color, #b26a00);\n }\n\n .criteria-head .who {\n font-family: var(--theme-font-mono, monospace);\n font-size: 12px;\n color: var(--ui-muted, #57676f);\n }\n\n .criteria ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /* 아직 아무 명세도 안 골랐다. 빈 표를 보이지 않는다. */\n .nothing-picked {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n text-align: center;\n font-size: 13px;\n line-height: 1.6;\n color: var(--ui-muted, #57676f);\n }\n `\n ]\n\n protected get spec(): MasterSpec {\n return {\n key: 'testSpecification',\n title: 'test-specification',\n help: 'ops-master/test-specification',\n query: 'testSpecifications',\n mutation: 'updateMultipleTestSpecification',\n columns: [...IDENTITY, ...EFFECTIVE]\n }\n }\n\n /** 지금 고른 명세의 이름. 없으면 아래를 빈 표로 두지 않는다. */\n @state() private picked?: string\n\n /**\n * 지금 체크된 명세들.\n *\n * 아래는 **하나의 기준만** 보이는데 위에서 여럿을 체크할 수 있다(명세 여럿을 한 번에 종료하는 데\n * 쓴다). 그때 화면이 아무 말도 안 하면 보이는 기준이 **여럿을 합친 것**으로 읽힌다. 그래서 몇 개가\n * 체크돼 있는지 세어 말한다.\n */\n @state() private checked = new Set<string>()\n\n @query('#criteria-grist') private criteriaGrist!: DataGrist\n\n /**\n * 아래 표에서 아직 저장하지 않은 기준이 몇 건인가.\n *\n * ── 왜 세어 보이나 (2026-09-05) ────────────────────────────────────────\n * 이 화면은 표가 둘인데 「저장」은 하나다. 아래를 고쳐 놓고도 그것이 저장 대상인지 화면 어디에도\n * 안 적혀 있었다. 「복잡한 편집인데 저장 버튼의 기능이 위험해 보인다」(사용자).\n *\n * 세어서 표 머리에 적으면 두 가지가 한꺼번에 풀린다. 저장을 누르기 전에 **무엇이 나가는지** 보이고,\n * 다른 명세로 넘어가려 할 때 **버릴 것이 있다**는 사실도 보인다.\n */\n @state() private dirtyCriteria = 0\n\n private countDirtyCriteria() {\n this.dirtyCriteria = (this.criteriaGrist?.dirtyRecords ?? []).length\n }\n\n /**\n * 기준 표의 구성.\n *\n * **단위 칸을 좁히지 않는다.** `MMT`(mm) · `CEL`(℃) 같은 UN/CEFACT 코드가 들어가고, 잘리면\n * 「무엇으로 잰 값인지」가 화면에서 사라진다.\n */\n private get criteriaConfig(): any {\n return {\n /*\n * 명세는 골랐는데 그 안에 판단 기준이 하나도 없는 자리다. 왼쪽의 빈 목록과 뜻이 다르다 —\n * 「검사 명세가 없다」가 아니라 「이 명세가 아직 무엇으로 합격을 가르는지 말하지 않는다」이다.\n *\n * 고른 것이 없을 때는 아래 render 가 표 자체를 보이지 않는다(그 자리 주석 참조).\n */\n emptyNote: {\n title: i18next.t('text.criteria-empty'),\n description: i18next.t('text.criteria-empty-hint')\n },\n list: { fields: ['id', 'description'], details: [] },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n { type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },\n {\n type: 'string',\n name: 'id',\n header: i18next.t('field.criterion-id'),\n record: { editable: true },\n width: 160,\n fixed: true\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: true },\n width: 180\n },\n {\n type: 'string',\n name: 'evaluatedPropertyId',\n header: i18next.t('field.evaluated-property'),\n record: { editable: true },\n width: 150\n },\n { type: 'float', name: 'minimum', header: i18next.t('field.minimum'), record: { editable: true }, width: 100 },\n { type: 'float', name: 'maximum', header: i18next.t('field.maximum'), record: { editable: true }, width: 100 },\n { type: 'string', name: 'uom', header: i18next.t('field.uom'), record: { editable: true }, width: 90 },\n {\n type: 'string',\n name: 'expression',\n header: i18next.t('field.expression'),\n record: { editable: true },\n width: 200\n }\n ],\n rows: { appendable: true, selectable: { multiple: true } }\n }\n }\n\n render() {\n return html`\n <div class=\"split\">\n <ox-grist\n .mode=${this.mode}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider(`ox-grist-${this.spec.key}`)!}\n @select-record-change=${(e: CustomEvent) => this.pick(e)}\n >\n <div slot=\"headroom\" class=\"header\">${this.renderScope()}</div>\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n <div class=\"criteria\">\n <div class=\"criteria-head\">\n <span class=\"what\">${i18next.t('field.criteria')}</span>\n <span class=\"who\">\n ${this.dirtyCriteria\n ? html`<em class=\"pending\">${i18next.t('text.n-unsaved-criteria', { count: this.dirtyCriteria })}</em>`\n : ''}\n ${this.picked ?? ''}\n <!--\n 여럿 체크했는데 하나만 보이는 상태를 **말한다.** 아무 말도 안 하면 보이는 기준이\n 여럿을 합친 것으로 읽힌다.\n -->\n ${this.checked.size > 1\n ? html`<em class=\"among\">${i18next.t('text.of-n-selected', { count: this.checked.size })}</em>`\n : ''}\n </span>\n </div>\n ${this.picked\n ? html`\n <ox-grist\n id=\"criteria-grist\"\n mode=\"GRID\"\n .config=${this.criteriaConfig}\n .fetchHandler=${this.fetchCriteria.bind(this)}\n @field-change=${() => this.countDirtyCriteria()}\n @record-change=${() => this.countDirtyCriteria()}\n ></ox-grist>\n `\n : html`\n <!--\n 빈 표를 보이지 않는다. 빈 표는 「기준이 없는 명세」와 같아 보이고, 그러면 사람이\n 기준을 다시 적는다.\n -->\n <div class=\"nothing-picked\">${i18next.t('text.pick-a-specification-to-see-criteria')}</div>\n `}\n </div>\n </div>\n `\n }\n\n /**\n * 위에서 고른 줄이 아래를 정한다.\n *\n * ── detail 모양이 거터 설정에 따라 다르다 ─────────────────────────────────\n * `gutter-row-selector.js` 를 보면 두 모양을 낸다.\n *\n * ```\n * multiple: true { added: [record], removed: [] } ← 이 화면이 쓰는 것\n * 그 밖 { records: [record] }\n * ```\n *\n * 한쪽만 읽으면 **오류보고없이 아무것도 안 골라진다.** 오류가 나지 않고 아래 표만 영원히 안내 문구로\n * 남는다. 처음에 `records` 만 읽어서 실제로 그랬다. 그리드가 세우는 `__selected__` 를 읽는 방법도\n * 있는데, 그 표시는 이 화면에 오지 않았다. 그래서 **둘 다 읽는다.**\n *\n * 여럿 고르면 마지막에 고른 것을 본다. 아래는 명세 하나의 기준을 보이는 자리라 둘을 함께 보일 수\n * 없고, 아무 것이나 고르면 사람은 그것이 무엇의 기준인지 모른다.\n */\n private async pick(e: CustomEvent) {\n const detail = (e.detail ?? {}) as { added?: any[]; removed?: any[]; records?: any[] }\n const nameOf = (record: any) => String(record?.name ?? '')\n\n const added = detail.added ?? detail.records ?? []\n const removed = detail.removed ?? []\n\n /* `records` 는 「지금 고른 것 전부」다. `added`·`removed` 는 「이번에 바뀐 것」이다. */\n const next = detail.records ? new Set<string>() : new Set(this.checked)\n\n for (const record of removed) next.delete(nameOf(record))\n for (const record of added) next.add(nameOf(record))\n\n /* 아래에 무엇이 보일지 먼저 정하고, 바꾸기 전에 고치던 것이 있는지 본다. */\n let picked = this.picked\n\n if (added.length) {\n picked = nameOf(added[added.length - 1]) || undefined\n } else if (removed.some(record => nameOf(record) === this.picked)) {\n /* 지금 보고 있던 것을 풀었다. 남은 것이 하나면 그것을 보인다. */\n picked = next.size ? [...next][next.size - 1] : undefined\n } else if (detail.records && !detail.records.length) {\n picked = undefined\n }\n\n this.checked = next\n\n if (picked === this.picked) {\n return\n }\n\n /*\n * ── 고치던 기준을 **말없이 버리지 않는다** (2026-09-05) ────────────────\n *\n * 아래 표는 「지금 고른 명세」에 걸려 있다. 명세를 바꾸면 아래는 새 명세의 기준을 다시 읽어\n * 오고, 고치던 값은 그때 사라진다. [확인함] 2026-09-05 · CP-CAM-LEAK 의 하한을 고쳐 두고\n * CP-CAM-OQC 를 고른 뒤 돌아오니 원래 값이었다. 화면은 아무 말도 하지 않았다.\n *\n * 두 갈래로만 묻는다 — 어느 쪽을 골라도 넘어간다. 「머무르기」를 두면 위의 체크는 이미 옮겨\n * 갔는데 아래만 옛 명세를 보이는, 화면과 사실이 어긋난 상태가 남는다.\n */\n if (this.dirtyCriteria > 0) {\n const answer = await CustomAlert({\n type: 'question',\n title: i18next.t('text.n-unsaved-criteria', { count: this.dirtyCriteria }),\n text: i18next.t('text.leaving-discards-unsaved-criteria'),\n confirmButton: { text: i18next.t('button.save-and-leave') },\n cancelButton: { text: i18next.t('button.discard-and-leave') }\n })\n\n if (answer?.isConfirmed) {\n await this.save()\n }\n }\n\n this.picked = picked\n this.dirtyCriteria = 0\n }\n\n /**\n * 고른 명세의 기준을 읽는다.\n *\n * 못 읽으면 **빈 목록으로 바꾸지 않는다.** 빈 목록은 「기준이 없다」와 같아 보이고, 그러면 사람이\n * 기준을 다시 적어 넣는다. 무엇이 잘못됐는지 말하고 비운다.\n */\n private async fetchCriteria() {\n if (!this.picked) {\n return { total: 0, records: [] }\n }\n\n try {\n const response = await client.query({\n query: gql`\n query ($name: String!) {\n testSpecificationCriteria(name: $name) {\n id\n description\n evaluatedPropertyId\n minimum\n maximum\n uom\n expression\n keptFields\n }\n }\n `,\n variables: { name: this.picked },\n fetchPolicy: 'no-cache'\n })\n\n const rows = (response.data?.testSpecificationCriteria ?? []) as any[]\n\n /* 어느 줄인지 서버가 알아야 고칠 수 있다 — 이름은 고칠 수 있으므로 이름으로 찾을 수 없다. */\n return { total: rows.length, records: rows.map((row, index) => ({ ...row, rowIndex: index })) }\n } catch (error) {\n notify({ message: (error as Error).message, level: 'error' })\n return { total: 0, records: [] }\n }\n }\n\n /**\n * 저장 — **위와 아래를 따로 낸다.**\n *\n * 명세 자체(이름 · 버전 · 유효 기간)는 공통 규칙이 저장하고, 기준은 이 축의 뮤테이션이 저장한다.\n * 기준이 막히면 **명세 쪽도 되돌린다.** 절반만 저장되면 화면과 저장된 것이 달라진다.\n */\n protected async save() {\n const criteriaPatches = this.picked ? ((this.criteriaGrist?.dirtyRecords ?? []) as any[]) : []\n\n if (criteriaPatches.length) {\n let response: any\n\n try {\n response = await client.mutate({\n mutation: gql`\n mutation ($name: String!, $patches: [Object!]!) {\n updateTestSpecificationCriteria(name: $name, patches: $patches) {\n id\n }\n }\n `,\n variables: { name: this.picked, patches: criteriaPatches }\n })\n } catch (error) {\n /* 무엇이 왜 막혔는지 그대로 보인다 — 「저장할 수 없습니다」로는 고칠 곳을 모른다. */\n notify({ message: (error as Error).message, level: 'error' })\n return\n }\n\n /*\n * **`errors` 를 직접 본다.** 이 앱의 GraphQL 클라이언트는 서버가 거절해도 던지지 않고\n * `response.errors` 에 담아 돌려준다. `try/catch` 만 두면 거절이 오류 없이 지나가고, 화면은\n * 아무 일 없었다는 듯 다음으로 넘어간다 — **고친 것이 버려졌는데 사람은 저장된 줄 안다.**\n * 2026-09-02에 실제로 그랬다(단위를 지운 기준이 서버에서 막혔는데 화면은 「바뀐 것이 없습니다」).\n */\n if (response?.errors?.length) {\n notify({ message: response.errors.map((error: any) => error.message).join('\\n'), level: 'error' })\n return\n }\n }\n\n await super.save()\n\n if (criteriaPatches.length) {\n this.criteriaGrist?.fetch()\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"test-specification.js","sourceRoot":"","sources":["../../../client/pages/master/test-specification.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAIpD,IAAM,0BAA0B,GAAhC,MAAM,0BAA2B,SAAQ,iBAAiB;IAA1D;;QACK,SAAI,GAAiB,mBAAmB,CAAA;IACpD,CAAC;CAAA,CAAA;AAFY,0BAA0B;IADtC,aAAa,CAAC,+BAA+B,CAAC;GAClC,0BAA0B,CAEtC","sourcesContent":["import { customElement } from 'lit/decorators.js'\nimport { RevisionWorkspace } from './revision-workspace.js'\nimport { RevisionAxis } from './revision-view.js'\n\n@customElement('ops-master-test-specification')\nexport class OpsMasterTestSpecification extends RevisionWorkspace {\n protected axis: RevisionAxis = 'testSpecification'\n}\n"]}
|