@things-factory/ops-master 10.1.36 → 10.1.37

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 (45) hide show
  1. package/dist-client/bootstrap.d.ts +1 -0
  2. package/dist-client/bootstrap.js +26 -0
  3. package/dist-client/bootstrap.js.map +1 -1
  4. package/dist-client/master-change-policy-axes.d.ts +26 -0
  5. package/dist-client/master-change-policy-axes.js +18 -0
  6. package/dist-client/master-change-policy-axes.js.map +1 -0
  7. package/dist-client/master-change-policy-panel.d.ts +49 -0
  8. package/dist-client/master-change-policy-panel.js +393 -0
  9. package/dist-client/master-change-policy-panel.js.map +1 -0
  10. package/dist-client/pages/master/applied-when-dialog.d.ts +2 -2
  11. package/dist-client/pages/master/applied-when-dialog.js +2 -2
  12. package/dist-client/pages/master/applied-when-dialog.js.map +1 -1
  13. package/dist-client/pages/master/assignment-fields.d.ts +8 -9
  14. package/dist-client/pages/master/assignment-fields.js +25 -18
  15. package/dist-client/pages/master/assignment-fields.js.map +1 -1
  16. package/dist-client/pages/master/master-page-base.d.ts +0 -2
  17. package/dist-client/pages/master/master-page-base.js +1 -3
  18. package/dist-client/pages/master/master-page-base.js.map +1 -1
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/dist-server/service/approval-route.d.ts +37 -0
  21. package/dist-server/service/approval-route.js +34 -0
  22. package/dist-server/service/approval-route.js.map +1 -0
  23. package/dist-server/service/assignment/assignment-history.d.ts +21 -16
  24. package/dist-server/service/assignment/assignment-history.js +91 -52
  25. package/dist-server/service/assignment/assignment-history.js.map +1 -1
  26. package/dist-server/service/assignment/assignment-write.d.ts +21 -4
  27. package/dist-server/service/assignment/assignment-write.js +41 -9
  28. package/dist-server/service/assignment/assignment-write.js.map +1 -1
  29. package/dist-server/service/assignment/{assignment-claim.d.ts → master-value-claim.d.ts} +31 -7
  30. package/dist-server/service/assignment/{assignment-claim.js → master-value-claim.js} +45 -31
  31. package/dist-server/service/assignment/master-value-claim.js.map +1 -0
  32. package/dist-server/service/index.d.ts +4 -2
  33. package/dist-server/service/index.js +8 -1
  34. package/dist-server/service/index.js.map +1 -1
  35. package/dist-server/service/master-change-policy.d.ts +65 -2
  36. package/dist-server/service/master-change-policy.js +166 -21
  37. package/dist-server/service/master-change-policy.js.map +1 -1
  38. package/dist-server/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +6 -4
  40. package/translations/en.json +59 -2
  41. package/translations/ja.json +58 -1
  42. package/translations/ko.json +59 -2
  43. package/translations/ms.json +58 -1
  44. package/translations/zh.json +60 -1
  45. package/dist-server/service/assignment/assignment-claim.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"applied-when-dialog.js","sourceRoot":"","sources":["../../../client/pages/master/applied-when-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\n\nimport { ASSIGNMENT_FIELDS } from './assignment-fields.js'\n\n/**\n * **언제부터 그러한가를 묻는 자리.**\n *\n * ── 왜 날짜 칸 하나가 아닌가 ───────────────────────────────────────────────\n * 그리드에 날짜 열을 하나 더하면 사람은 그것을 **또 하나의 값**으로 읽는다. 이 셋(자리 배정 · 근무조\n * 선언 · 부모 관계)은 값이 아니라 **언제부터 그렇게 볼 것인가**를 함께 정하는 일이고, 과거를 적으면\n * **지연 기록인지 정정인지**까지 갈라야 한다. 그래서 저장 직전에 한 번 묻는다.\n *\n * ```\n * 바뀌기 전 → 바뀐 뒤\n * 적용 시작 기본값 「지금부터」\n * 과거 시각이면 지연 기록인가 이미 적은 것의 정정인가\n * 미래 시각이면 지금 값은 그대로이고 변경이 예약된다고 알린다\n * ```\n *\n * ⚠ **기록 시각은 안 묻는다.** 사람이 넣으면 「언제 알았나」를 사람이 정하게 된다 — 서버가 붙인다.\n */\nexport interface AppliedAnswer {\n at?: string\n kind?: 'change' | 'correction'\n until?: string\n}\n\nexport interface AssignmentChange {\n rowName: string\n field: string\n before: string\n after: string\n}\n\n@customElement('applied-when-dialog')\nexport class AppliedWhenDialog extends LitElement {\n static styles = css`\n :host {\n display: block;\n padding: var(--spacing-medium, 12px);\n }\n .what {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: var(--spacing-tiny, 4px) var(--spacing-small, 8px);\n margin-bottom: var(--spacing-medium, 12px);\n }\n .what span {\n color: var(--md-sys-color-on-surface-variant);\n }\n .note {\n color: var(--md-sys-color-on-surface-variant);\n margin-top: var(--spacing-small, 8px);\n }\n .buttons {\n display: flex;\n justify-content: flex-end;\n gap: var(--spacing-small, 8px);\n margin-top: var(--spacing-medium, 12px);\n }\n `\n\n @property({ type: Array }) changes: AssignmentChange[] = []\n @property({ type: Object }) onAnswer?: (answer: AppliedAnswer) => void\n @property({ type: Object }) onCancel?: () => void\n\n @state() private at = ''\n @state() private kind: 'change' | 'correction' = 'change'\n @state() private until = ''\n\n /** 안 적으면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */\n private get when(): number {\n return this.at ? new Date(this.at).getTime() : Date.now()\n }\n\n private get past(): boolean {\n return this.when < Date.now() - 60000\n }\n\n private get future(): boolean {\n return this.when > Date.now() + 60000\n }\n\n render() {\n return html`\n <div class=\"what\">\n ${this.changes.map(\n one => html`\n <span>${one.rowName} · ${labelOf(one.field)}</span>\n <div>${one.before || i18next.t('text.none')} → <b>${one.after || i18next.t('text.none')}</b></div>\n `\n )}\n </div>\n\n <label>\n ${i18next.t('label.applied-from')}\n <input\n type=\"datetime-local\"\n .value=${this.at}\n @change=${(e: Event) => (this.at = (e.target as HTMLInputElement).value)}\n placeholder=${i18next.t('text.from-now')}\n />\n </label>\n\n ${this.past\n ? html`\n <div>\n <label>\n <input type=\"radio\" name=\"kind\" ?checked=${this.kind === 'change'} @change=${() => (this.kind = 'change')} />\n ${i18next.t('label.late-record')}\n </label>\n <label>\n <input type=\"radio\" name=\"kind\" ?checked=${this.kind === 'correction'} @change=${() => (this.kind = 'correction')} />\n ${i18next.t('label.correct-what-was-recorded')}\n </label>\n ${this.kind === 'correction'\n ? html`\n <label>\n ${i18next.t('label.applied-until')}\n <input type=\"datetime-local\" .value=${this.until} @change=${(e: Event) => (this.until = (e.target as HTMLInputElement).value)} />\n </label>\n `\n : ''}\n </div>\n `\n : ''}\n ${this.future ? html`<div class=\"note\">${i18next.t('text.change-is-scheduled')}</div>` : ''}\n\n <div class=\"buttons\">\n <button @click=${() => this.onCancel?.()}>${i18next.t('button.cancel')}</button>\n <button\n @click=${() =>\n this.onAnswer?.({\n ...(this.at ? { at: new Date(this.at).toISOString() } : {}),\n ...(this.past ? { kind: this.kind } : {}),\n ...(this.past && this.kind === 'correction' && this.until ? { until: new Date(this.until).toISOString() } : {})\n })}\n >\n ${i18next.t('button.save')}\n </button>\n </div>\n `\n }\n}\n\nfunction labelOf(field: string): string {\n return i18next.t(`field.${field}`, { defaultValue: field })\n}\n\nexport { ASSIGNMENT_FIELDS }\n"]}
1
+ {"version":3,"file":"applied-when-dialog.js","sourceRoot":"","sources":["../../../client/pages/master/applied-when-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\n\nimport { CLAIMED_FIELDS } from './assignment-fields.js'\n\n/**\n * **언제부터 그러한가를 묻는 자리.**\n *\n * ── 왜 날짜 칸 하나가 아닌가 ───────────────────────────────────────────────\n * 그리드에 날짜 열을 하나 더하면 사람은 그것을 **또 하나의 값**으로 읽는다. 이 셋(자리 배정 · 근무조\n * 선언 · 부모 관계)은 값이 아니라 **언제부터 그렇게 볼 것인가**를 함께 정하는 일이고, 과거를 적으면\n * **지연 기록인지 정정인지**까지 갈라야 한다. 그래서 저장 직전에 한 번 묻는다.\n *\n * ```\n * 바뀌기 전 → 바뀐 뒤\n * 적용 시작 기본값 「지금부터」\n * 과거 시각이면 지연 기록인가 이미 적은 것의 정정인가\n * 미래 시각이면 지금 값은 그대로이고 변경이 예약된다고 알린다\n * ```\n *\n * ⚠ **기록 시각은 안 묻는다.** 사람이 넣으면 「언제 알았나」를 사람이 정하게 된다 — 서버가 붙인다.\n */\nexport interface AppliedAnswer {\n at?: string\n kind?: 'change' | 'correction'\n until?: string\n}\n\nexport interface AssignmentChange {\n rowName: string\n field: string\n before: string\n after: string\n}\n\n@customElement('applied-when-dialog')\nexport class AppliedWhenDialog extends LitElement {\n static styles = css`\n :host {\n display: block;\n padding: var(--spacing-medium, 12px);\n }\n .what {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: var(--spacing-tiny, 4px) var(--spacing-small, 8px);\n margin-bottom: var(--spacing-medium, 12px);\n }\n .what span {\n color: var(--md-sys-color-on-surface-variant);\n }\n .note {\n color: var(--md-sys-color-on-surface-variant);\n margin-top: var(--spacing-small, 8px);\n }\n .buttons {\n display: flex;\n justify-content: flex-end;\n gap: var(--spacing-small, 8px);\n margin-top: var(--spacing-medium, 12px);\n }\n `\n\n @property({ type: Array }) changes: AssignmentChange[] = []\n @property({ type: Object }) onAnswer?: (answer: AppliedAnswer) => void\n @property({ type: Object }) onCancel?: () => void\n\n @state() private at = ''\n @state() private kind: 'change' | 'correction' = 'change'\n @state() private until = ''\n\n /** 안 적으면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */\n private get when(): number {\n return this.at ? new Date(this.at).getTime() : Date.now()\n }\n\n private get past(): boolean {\n return this.when < Date.now() - 60000\n }\n\n private get future(): boolean {\n return this.when > Date.now() + 60000\n }\n\n render() {\n return html`\n <div class=\"what\">\n ${this.changes.map(\n one => html`\n <span>${one.rowName} · ${labelOf(one.field)}</span>\n <div>${one.before || i18next.t('text.none')} → <b>${one.after || i18next.t('text.none')}</b></div>\n `\n )}\n </div>\n\n <label>\n ${i18next.t('label.applied-from')}\n <input\n type=\"datetime-local\"\n .value=${this.at}\n @change=${(e: Event) => (this.at = (e.target as HTMLInputElement).value)}\n placeholder=${i18next.t('text.from-now')}\n />\n </label>\n\n ${this.past\n ? html`\n <div>\n <label>\n <input type=\"radio\" name=\"kind\" ?checked=${this.kind === 'change'} @change=${() => (this.kind = 'change')} />\n ${i18next.t('label.late-record')}\n </label>\n <label>\n <input type=\"radio\" name=\"kind\" ?checked=${this.kind === 'correction'} @change=${() => (this.kind = 'correction')} />\n ${i18next.t('label.correct-what-was-recorded')}\n </label>\n ${this.kind === 'correction'\n ? html`\n <label>\n ${i18next.t('label.applied-until')}\n <input type=\"datetime-local\" .value=${this.until} @change=${(e: Event) => (this.until = (e.target as HTMLInputElement).value)} />\n </label>\n `\n : ''}\n </div>\n `\n : ''}\n ${this.future ? html`<div class=\"note\">${i18next.t('text.change-is-scheduled')}</div>` : ''}\n\n <div class=\"buttons\">\n <button @click=${() => this.onCancel?.()}>${i18next.t('button.cancel')}</button>\n <button\n @click=${() =>\n this.onAnswer?.({\n ...(this.at ? { at: new Date(this.at).toISOString() } : {}),\n ...(this.past ? { kind: this.kind } : {}),\n ...(this.past && this.kind === 'correction' && this.until ? { until: new Date(this.until).toISOString() } : {})\n })}\n >\n ${i18next.t('button.save')}\n </button>\n </div>\n `\n }\n}\n\nfunction labelOf(field: string): string {\n return i18next.t(`field.${field}`, { defaultValue: field })\n}\n\nexport { CLAIMED_FIELDS }\n"]}
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.
3
3
  *
4
- * 서버의 `ASSIGNMENT_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가
4
+ * 서버의 `CLAIMED_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가
5
5
  * 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**
6
6
  */
7
- export declare const ASSIGNMENT_FIELDS: {
7
+ export declare const CLAIMED_FIELDS: {
8
8
  axis: string;
9
9
  field: string;
10
10
  }[];
11
11
  /** 이 축에서 이력을 받는 칸들. */
12
- export declare function assignmentFieldsOf(axis: string): string[];
12
+ export declare function claimedFieldsOf(axis: string): string[];
13
13
  export interface AssignmentChange {
14
14
  rowName: string;
15
15
  field: string;
@@ -17,12 +17,11 @@ export interface AssignmentChange {
17
17
  after: string;
18
18
  }
19
19
  /**
20
- * 보내려는 변경 중 **이력을 받는 칸을 건드린 것**만 고른다.
20
+ * 바뀐 중 **이력을 받는 칸을 건드린 것**만 고른다.
21
21
  *
22
- * ⚠ **새로 만드는 행은 안 묻는다.** 그 행은 그 순간 생긴 것이고 「언제부터」는 그 순간이다.
23
- * 물으면 존재하기 전의 자리를 적을 수 있게 된다.
22
+ * ⚠ **새로 만드는 행은 안 묻는다**(`__dirty__` 가 `+` 다). 그 행은 그 순간 생긴 것이고 「언제부터」는
23
+ * 그 순간이다. 물으면 존재하기 전의 자리를 적을 수 있게 된다.
24
24
  *
25
- * ⚠ **같은 값을 다시 보낸 것도 묻는다.** 그리드는 칸만 고쳐도 행 전체를 보낸다 — 안 바뀐
26
- * 칸까지 물으면 사람이 매번 날짜를 보게 되고, 그러면 아무도 안 읽는다.
25
+ * ⚠ **안 바뀐 칸은 애초에 `__dirtyfields__` 없다.** 그리드가 이미 가려 준다.
27
26
  */
28
- export declare function assignmentChangesIn(axis: string, patches: readonly Record<string, unknown>[], before: (id: string) => Record<string, unknown> | undefined): AssignmentChange[];
27
+ export declare function assignmentChangesIn(axis: string, patches: readonly Record<string, unknown>[]): AssignmentChange[];
@@ -1,46 +1,53 @@
1
1
  /**
2
2
  * **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.
3
3
  *
4
- * 서버의 `ASSIGNMENT_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가
4
+ * 서버의 `CLAIMED_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가
5
5
  * 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**
6
6
  */
7
- export const ASSIGNMENT_FIELDS = [
7
+ export const CLAIMED_FIELDS = [
8
8
  { axis: 'equipment', field: 'homeLocation' },
9
9
  { axis: 'operationalLocation', field: 'workShiftNames' },
10
10
  { axis: 'operationalLocation', field: 'parentId' }
11
11
  ];
12
12
  /** 이 축에서 이력을 받는 칸들. */
13
- export function assignmentFieldsOf(axis) {
14
- return ASSIGNMENT_FIELDS.filter(one => one.axis === axis).map(one => one.field);
13
+ export function claimedFieldsOf(axis) {
14
+ return CLAIMED_FIELDS.filter(one => one.axis === axis).map(one => one.field);
15
15
  }
16
16
  /**
17
- * 보내려는 변경 중 **이력을 받는 칸을 건드린 것**만 고른다.
17
+ * 바뀐 중 **이력을 받는 칸을 건드린 것**만 고른다.
18
18
  *
19
- * ⚠ **새로 만드는 행은 안 묻는다.** 그 행은 그 순간 생긴 것이고 「언제부터」는 그 순간이다.
20
- * 물으면 존재하기 전의 자리를 적을 수 있게 된다.
19
+ * ⚠ **새로 만드는 행은 안 묻는다**(`__dirty__` 가 `+` 다). 그 행은 그 순간 생긴 것이고 「언제부터」는
20
+ * 그 순간이다. 물으면 존재하기 전의 자리를 적을 수 있게 된다.
21
21
  *
22
- * ⚠ **같은 값을 다시 보낸 것도 묻는다.** 그리드는 칸만 고쳐도 행 전체를 보낸다 — 안 바뀐
23
- * 칸까지 물으면 사람이 매번 날짜를 보게 되고, 그러면 아무도 안 읽는다.
22
+ * ⚠ **안 바뀐 칸은 애초에 `__dirtyfields__` 없다.** 그리드가 이미 가려 준다.
24
23
  */
25
- export function assignmentChangesIn(axis, patches, before) {
26
- const fields = assignmentFieldsOf(axis);
24
+ export function assignmentChangesIn(axis, patches) {
25
+ const fields = claimedFieldsOf(axis);
27
26
  if (!fields.length) {
28
27
  return [];
29
28
  }
30
29
  const changes = [];
31
30
  for (const patch of patches) {
32
- if (String(patch.cuFlag ?? '').toUpperCase() !== 'M') {
31
+ /*
32
+ * **그리드가 무엇이 바뀌었는지 이미 들고 있다.** `__dirtyfields__` 에 칸마다 before·after 가
33
+ * 있고, 바뀐 행은 `__dirty__` 가 `M` 이다.
34
+ *
35
+ * ⚠ 처음에 `cuFlag` 를 봤다 — 그것은 서버로 보낼 때 붙는 이름이고 **이 시점에는 없다.** 그래서
36
+ * 창이 영영 안 떴다. 화면을 열어 보기 전에는 안 보였다(2026-09-22).
37
+ */
38
+ if (String(patch.__dirty__ ?? '').toUpperCase() !== 'M') {
33
39
  continue;
34
40
  }
35
- const was = before(String(patch.id)) ?? {};
41
+ const dirty = patch.__dirtyfields__;
36
42
  for (const field of fields) {
37
- if (!(field in patch)) {
43
+ const moved = dirty?.[field];
44
+ if (!moved) {
38
45
  continue;
39
46
  }
40
- const after = show(patch[field]);
41
- const had = show(was[field]);
42
- if (after !== had) {
43
- changes.push({ rowName: String(patch.name ?? was.name ?? patch.id), field, before: had, after });
47
+ const before = show(moved.before);
48
+ const after = show(moved.after);
49
+ if (before !== after) {
50
+ changes.push({ rowName: String(patch.name ?? patch.id), field, before, after });
44
51
  }
45
52
  }
46
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"assignment-fields.js","sourceRoot":"","sources":["../../../client/pages/master/assignment-fields.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsC;IAClE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE;IAC5C,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACxD,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,UAAU,EAAE;CACnD,CAAA;AAED,uBAAuB;AACvB,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AACjF,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,OAA2C,EAC3C,MAA2D;IAE3D,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAA;IAEvC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,OAAO,GAAuB,EAAE,CAAA;IAEtC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC;YACrD,SAAQ;QACV,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAE1C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,CAAC;gBACtB,SAAQ;YACV,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;YAE5B,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;YAClG,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.\n *\n * 서버의 `ASSIGNMENT_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가\n * 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**\n */\nexport const ASSIGNMENT_FIELDS: { axis: string; field: string }[] = [\n { axis: 'equipment', field: 'homeLocation' },\n { axis: 'operationalLocation', field: 'workShiftNames' },\n { axis: 'operationalLocation', field: 'parentId' }\n]\n\n/** 이 축에서 이력을 받는 칸들. */\nexport function assignmentFieldsOf(axis: string): string[] {\n return ASSIGNMENT_FIELDS.filter(one => one.axis === axis).map(one => one.field)\n}\n\nexport interface AssignmentChange {\n rowName: string\n field: string\n before: string\n after: string\n}\n\n/**\n * 보내려는 변경 중 **이력을 받는 칸을 건드린 것**만 고른다.\n *\n * ⚠ **새로 만드는 행은 안 묻는다.** 그 행은 그 순간 생긴 것이고 「언제부터」는순간이다.\n * 물으면 존재하기 전의 자리를 적을 수 있게 된다.\n *\n * ⚠ **같은 값을 다시 보낸 것도 묻는다.** 그리드는 칸만 고쳐도 행 전체를 보낸다 — 안 바뀐\n * 칸까지 물으면 사람이 매번 날짜를 보게 되고, 그러면 아무도 안 읽는다.\n */\nexport function assignmentChangesIn(\n axis: string,\n patches: readonly Record<string, unknown>[],\n before: (id: string) => Record<string, unknown> | undefined\n): AssignmentChange[] {\n const fields = assignmentFieldsOf(axis)\n\n if (!fields.length) {\n return []\n }\n\n const changes: AssignmentChange[] = []\n\n for (const patch of patches) {\n if (String(patch.cuFlag ?? '').toUpperCase() !== 'M') {\n continue\n }\n\n const was = before(String(patch.id)) ?? {}\n\n for (const field of fields) {\n if (!(field in patch)) {\n continue\n }\n\n const after = show(patch[field])\n const had = show(was[field])\n\n if (after !== had) {\n changes.push({ rowName: String(patch.name ?? was.name ?? patch.id), field, before: had, after })\n }\n }\n }\n\n return changes\n}\n\nfunction show(value: unknown): string {\n if (value === null || value === undefined || value === '') {\n return ''\n }\n\n return Array.isArray(value) ? value.map(String).join(', ') : String(value)\n}\n"]}
1
+ {"version":3,"file":"assignment-fields.js","sourceRoot":"","sources":["../../../client/pages/master/assignment-fields.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsC;IAC/D,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE;IAC5C,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACxD,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,UAAU,EAAE;CACnD,CAAA;AAED,uBAAuB;AACvB,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AAC9E,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,OAA2C;IAC3F,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IAEpC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,OAAO,GAAuB,EAAE,CAAA;IAEtC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B;;;;;;WAMG;QACH,IAAI,MAAM,CAAE,KAAa,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC;YACjE,SAAQ;QACV,CAAC;QAED,MAAM,KAAK,GAAI,KAAa,CAAC,eAAoF,CAAA;QAEjH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;YAE5B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,SAAQ;YACV,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAE/B,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;YACjF,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.\n *\n * 서버의 `CLAIMED_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가\n * 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**\n */\nexport const CLAIMED_FIELDS: { axis: string; field: string }[] = [\n { axis: 'equipment', field: 'homeLocation' },\n { axis: 'operationalLocation', field: 'workShiftNames' },\n { axis: 'operationalLocation', field: 'parentId' }\n]\n\n/** 이 축에서 이력을 받는 칸들. */\nexport function claimedFieldsOf(axis: string): string[] {\n return CLAIMED_FIELDS.filter(one => one.axis === axis).map(one => one.field)\n}\n\nexport interface AssignmentChange {\n rowName: string\n field: string\n before: string\n after: string\n}\n\n/**\n * 바뀐 중 **이력을 받는 칸을 건드린 것**만 고른다.\n *\n * ⚠ **새로 만드는 행은 안 묻는다**(`__dirty__` 가 `+` 다). 그 행은 그 순간 생긴 것이고 「언제부터」는\n * 순간이다. 물으면 존재하기 전의 자리를 적을 수 있게 된다.\n *\n * ⚠ **안 바뀐 칸은 애초에 `__dirtyfields__` 없다.** 그리드가 이미 가려 준다.\n */\nexport function assignmentChangesIn(axis: string, patches: readonly Record<string, unknown>[]): AssignmentChange[] {\n const fields = claimedFieldsOf(axis)\n\n if (!fields.length) {\n return []\n }\n\n const changes: AssignmentChange[] = []\n\n for (const patch of patches) {\n /*\n * **그리드가 무엇이 바뀌었는지 이미 들고 있다.** `__dirtyfields__` 에 칸마다 before·after 가\n * 있고, 바뀐 행은 `__dirty__` 가 `M` 이다.\n *\n * ⚠ 처음에 `cuFlag` 를 봤다 — 그것은 서버로 보낼 때 붙는 이름이고 **이 시점에는 없다.** 그래서\n * 창이 영영 안 떴다. 화면을 열어 보기 전에는 안 보였다(2026-09-22).\n */\n if (String((patch as any).__dirty__ ?? '').toUpperCase() !== 'M') {\n continue\n }\n\n const dirty = (patch as any).__dirtyfields__ as Record<string, { before?: unknown; after?: unknown }> | undefined\n\n for (const field of fields) {\n const moved = dirty?.[field]\n\n if (!moved) {\n continue\n }\n\n const before = show(moved.before)\n const after = show(moved.after)\n\n if (before !== after) {\n changes.push({ rowName: String(patch.name ?? patch.id), field, before, after })\n }\n }\n }\n\n return changes\n}\n\nfunction show(value: unknown): string {\n if (value === null || value === undefined || value === '') {\n return ''\n }\n\n return Array.isArray(value) ? value.map(String).join(', ') : String(value)\n}\n"]}
@@ -362,8 +362,6 @@ export declare abstract class MasterPageBase extends MasterPageBase_base {
362
362
  private headerIndex;
363
363
  /** 이 이름들이 이미 있나 — 있으면 그 `id`. 없는 이름은 지도에 없다. */
364
364
  private idsByName;
365
- /** 마지막으로 읽은 줄들. 「바뀌기 전 → 바뀐 뒤」를 보여 주는 데 쓴다. */
366
- protected lastRead?: Map<string, any>;
367
365
  /** 「언제부터」를 묻는다. 사람이 그만두면 `undefined` 이고, 그때는 **아무것도 안 보낸다.** */
368
366
  protected askAppliedWhen(changes: AssignmentChange[]): Promise<AppliedAnswer | undefined>;
369
367
  protected save(): Promise<void>;
@@ -382,8 +382,6 @@ export class MasterPageBase extends p13n(localize(i18next)(PageView)) {
382
382
  });
383
383
  const payload = response.data?.[this.spec.query];
384
384
  const records = (payload?.items ?? []).map((item) => this.spreadJson(item));
385
- /* 바뀌기 전 값을 보여 주려면 읽은 것을 들고 있어야 한다 — 그리드는 바뀐 것만 돌려준다. */
386
- this.lastRead = new Map(records.map((one) => [String(one.id), one]));
387
385
  return { total: payload?.total ?? 0, records };
388
386
  }
389
387
  /**
@@ -596,7 +594,7 @@ export class MasterPageBase extends p13n(localize(i18next)(PageView)) {
596
594
  * 안 물어도 저장은 된다 — 그때는 「지금부터」라는 주장이 된다. 다만 **묻지 않으면 사람이 그것을
597
595
  * 주장한 줄 모른다.**
598
596
  */
599
- const assignments = assignmentChangesIn(this.spec.axis ?? '', packed.patches, id => this.lastRead?.get(id));
597
+ const assignments = assignmentChangesIn(this.spec.axis ?? '', patches);
600
598
  const applied = assignments.length ? await this.askAppliedWhen(assignments) : undefined;
601
599
  /* 그만두었으면 아무것도 안 보낸다 — 절반만 저장되면 화면과 저장된 것이 달라진다. */
602
600
  if (assignments.length && !applied) {
@@ -1 +1 @@
1
- {"version":3,"file":"master-page-base.js","sourceRoot":"","sources":["../../../client/pages/master/master-page-base.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,iCAAiC,CAAA;AAExC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE1D,OAAO,EAAE,SAAS,EAAe,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAGpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAiB,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EAAoB,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE9E,OAAO,0BAA0B,CAAA;AA8HjC,oCAAoC;AACpC,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE;IAC9E,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE;CAC3E,CAAA;AAED,2CAA2C;AAC3C,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACxE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;CAC1E,CAAA;AAED,MAAM,OAAgB,cAAe,SAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;IAA9E;;QAiC+B,WAAM,GAAY,KAAK,CAAA;QAExB,SAAI,GAA6B,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;QAE5E,UAAK,GAAG,KAAK,CAAA;IAwoBlC,CAAC;aA5qBQ,WAAM,GAAG;QACd,qEAAqE;QACrE,qBAAqB;QACrB,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;KAoBF;KACF,AA3BY,CA2BZ;IAaD,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5C;;;;;;;;;;;;eAYG;YACH,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACtD,wCAAwC;YACxC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;YACpB;;;;;;;;;eASG;YACH,MAAM,EAAE;gBACN,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE;oBAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAA;gBAChC,CAAC;gBACD,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE;aACpC;YACD,OAAO,EAAE;gBACP;oBACE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;oBAC/B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;oBAC5B,IAAI,EAAE,MAAM;oBACZ,8CAA8C;oBAC9C,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;iBAC3B;aACF;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC3C,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE;aACvC;SACF,CAAA;IACH,CAAC;IAED;;;;;OAKG;IACO,WAAW;QACnB,OAAO,IAAI,CAAA,EAAE,CAAA;IACf,CAAC;IAED;;;;;OAKG;IACH,MAAM;QACJ,OAAO,IAAI,CAAA;;gBAEC,IAAI,CAAC,IAAI;kBACP,IAAI,CAAC,WAAW;wBACV,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;kCAClB,IAAI,CAAC,yBAAyB,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAE;;;;;;;;;;;;;;8CAchD,IAAI,CAAC,WAAW,EAAE;;;;;KAK3D,CAAA;IACH,CAAC;IAED;;;;;;;;;OASG;IACH,IAAc,sBAAsB;QAClC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;QAClC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,QAAc,EAAE,MAAY,EAAE,OAAa;QAC3D,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;QACrB,CAAC;IACH,CAAC;IAED;;;OAGG;IACO,QAAQ;QAChB,OAAO;YACL;;;;;;;;;eASG;YACH,SAAS,EAAE;gBACT,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,qBAAqB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACtD,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,qBAAqB,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,IAAI,SAAS;aACrG;YACD,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;YACtD,OAAO,EAAE;gBACP,0DAA0D;gBAC1D,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE;gBACvD,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;gBAC3E,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;aAC1D;YACD,2CAA2C;YAC3C,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC1D,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;SACzC,CAAA;IACH,CAAC;IAES,QAAQ,CAAC,MAAoB;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QACjD,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAA;QACjC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;QAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;QAE5B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,QAAQ;gBACX,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YAC/H,KAAK,SAAS;gBACZ,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YACjH,KAAK,MAAM;gBACT,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YAClI;;;;;eAKG;YACH,KAAK,QAAQ;gBACX;;;mBAGG;gBACH,OAAO;oBACL,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM;oBACN,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE;oBACnD,MAAM,EAAE,QAAQ;oBAChB,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,KAAK;oBACL,MAAM;iBACP,CAAA;YACH,KAAK,WAAW;gBACd;;;;;mBAKG;gBACH,OAAO;oBACL,IAAI,EAAE,WAAW;oBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM;oBACN;;;;;;;uBAOG;oBACH,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,EAAE;oBACvE,MAAM,EAAE,QAAQ;oBAChB,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,KAAK;oBACL,MAAM;iBACP,CAAA;YACH,KAAK,cAAc;gBACjB;;;;;mBAKG;gBACH,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YACtH;;;eAGG;YACH,KAAK,MAAM;gBACT,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YAC/G;;;eAGG;YACH;;;;;;;;;eASG;YACH,KAAK,gBAAgB;gBACnB,OAAO;oBACL,IAAI,EAAE,UAAU;oBAChB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM;oBACN,MAAM,EAAE;wBACN,QAAQ;wBACR;;;;;2BAKG;wBACH,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC;wBAC9B;;;;2BAIG;wBACH,UAAU,EAAE,IAAI;wBAChB,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE;qBAClE;oBACD,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,KAAK;oBACL,MAAM;iBACP,CAAA;YACH,KAAK,MAAM;gBACT;;;mBAGG;gBACH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YAClH;gBACE,OAAO;oBACL,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM;oBACN,MAAM,EAAE,EAAE,QAAQ,EAAE;oBACpB,MAAM,EAAE,QAAQ;oBAChB,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,KAAK;oBACL,MAAM;iBACP,CAAA;QACL,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAe;QACnF,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEtE;;;;;;;;;;;;;WAaG;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAE,IAAY,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAA;QAE7F,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,CAAC,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC9F,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;YAEJ,IAAI,CAAC,IAAI,CAAC,KAAK;;;gBAGX,MAAM;;;;;OAKf;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE;YAC7D,WAAW,EAAE,UAAU;SACxB,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChD,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;QAEhF,wDAAwD;QACxD,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;QAEzE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;IAChD,CAAC;IAED;;;;;;;;;OASG;IACO,UAAU,CAAC,IAAS;QAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;QAE9E,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YACxB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAA;QAE1B,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAEjC,iEAAiE;YACjE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACvE,SAAQ;YACV,CAAC;YAED,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACtD,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC;IAED;;;;;OAKG;IACO,QAAQ,CAAC,OAAc;QAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;QAE9E,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;QAClC,CAAC;QAED,MAAM,QAAQ,GAAa,EAAE,CAAA;QAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC1C,MAAM,GAAG,GAAG,EAAE,GAAG,KAAK,EAAE,CAAA;YAExB,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;gBACjC,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBAE9B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC9B,uCAAuC;oBACvC,SAAQ;gBACV,CAAC;gBAED,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;oBACxB,6CAA6C;oBAC7C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;oBACvB,SAAQ;gBACV,CAAC;gBAED,IAAI,CAAC;oBACH,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;gBACtC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,QAAQ,CAAC,IAAI,CACX,OAAO,CAAC,CAAC,CAAC,6BAA6B,EAAE;wBACvC,GAAG,EAAE,KAAK,GAAG,CAAC;wBACd,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,KAAK,EAAE,CAAC;wBAC1C,MAAM,EAAG,KAAe,CAAC,OAAO;qBACjC,CAAC,CACH,CAAA;gBACH,CAAC;YACH,CAAC;YAED,OAAO,GAAG,CAAA;QACZ,CAAC,CAAC,CAAA;QAEF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;IACtC,CAAC;IAED;;;;;;;;;;;;;OAaG;IACO,KAAK,CAAC,aAAa,CAAC,OAAc;QAC1C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YACvE,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QACnC,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;QAEvE,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC5B,8CAA8C;YAC9C,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;YACtH,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAChF,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;QAEpE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAErC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;YAC5E,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;YAEP,IAAI,CAAC,IAAI,CAAC,QAAQ;;;;OAIvB;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE;SACvC,CAAC,CAAA;QAEF;;;;WAIG;QACH,IAAI,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAC7B,OAAM;QACR,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAA;QAEjE,MAAM,CAAC;YACL,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;YAChF,KAAK,EAAE,MAAM;SACd,CAAC,CAAA;QAEF,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;IAED;;;;;;OAMG;IACH,wDAAwD;IAChD,UAAU,CAAC,OAAsB;QACvC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,gBAAgB;gBACnB,OAAO,OAAO,CAAC,CAAC,CAAC,6BAA6B,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YACjG,KAAK,SAAS;gBACZ,OAAO,OAAO,CAAC,CAAC,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;YAClE,KAAK,gBAAgB;gBACnB,OAAO,OAAO,CAAC,CAAC,CAAC,6BAA6B,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;QAC/F,CAAC;IACH,CAAC;IAEO,WAAW;QACjB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAA;QAEvC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;YACnC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QAC3D,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC;IAED,gDAAgD;IACxC,KAAK,CAAC,SAAS,CAAC,KAAe;QACrC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;YAEJ,IAAI,CAAC,IAAI,CAAC,KAAK;;;;;;;OAOpB;YACD,SAAS,EAAE;gBACT,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;gBACzD,0DAA0D;gBAC1D,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE;aAC7C;YACD,WAAW,EAAE,UAAU;SACxB,CAAC,CAAA;QAEF,OAAO,IAAI,GAAG,CACZ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CACzF,CAAA;IACH,CAAC;IAKD,gEAAgE;IACtD,cAAc,CAAC,OAA2B;QAClD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC3B,MAAM,KAAK,GAAQ,SAAS,CAC1B,IAAI,CAAA;qBACS,OAAO;sBACN,CAAC,MAAqB,EAAE,EAAE;gBACpC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAA;gBAChB,OAAO,CAAC,MAAM,CAAC,CAAA;YACjB,CAAC;sBACW,GAAG,EAAE;gBACf,KAAK,EAAE,KAAK,EAAE,EAAE,CAAA;gBAChB,OAAO,CAAC,SAAS,CAAC,CAAA;YACpB,CAAC;gCACqB,EACxB,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC,EAAE,CACnF,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAES,KAAK,CAAC,IAAI;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAA;QAEvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YACrE,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAErC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC3B,4CAA4C;YAC5C,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;YAC/D,OAAM;QACR,CAAC;QAED;;;;;;;;;WASG;QACH,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QAC3G,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAEvF,mDAAmD;QACnD,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACnC,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC;;;;;;;eAOG;YACH,QAAQ,EAAE,GAAG,CAAA;;YAEP,IAAI,CAAC,IAAI,CAAC,QAAQ;;;;;OAKvB;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE;SAChD,CAAC,CAAA;QAEF;;;;WAIG;QACH,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YAC5B,OAAM;QACR,CAAC;QAED,8DAA8D;QAC9D,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC;YACpF,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC3E,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;;AA3oB4B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;8CAAwB;AACxB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;mDAAiB;AAChB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;4CAAoE;AAE5E;IAAlB,KAAK,EAAE;;6CAAwB;AAEH;IAA5B,KAAK,CAAC,UAAU,CAAC;8BAAmB,SAAS;6CAAA","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { property, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption, getEditor } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify, openPopup } from '@operato/layout'\nimport { p13n } from '@operato/p13n'\n\nimport type { ReferenceOptions } from './grist-reference.js'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport { ImportProblem, patchesFor, readImportRows } from './import-rows.js'\nimport { AssignmentChange, assignmentChangesIn } from './assignment-fields.js'\nimport { AppliedAnswer } from './applied-when-dialog.js'\nimport './applied-when-dialog.js'\n\n/**\n * 마스터 화면이 함께 쓰는 것 — **코드 재사용은 여기서, 화면은 각자.**\n *\n * ── 왜 화면을 갈랐나 (2026-09-01) ──────────────────────────────────────────\n * 전에는 화면 하나가 `?axis=` 로 열두 축을 다 맡았다. 근거는 「열두 축이 하는 일이 같다」였는데, 그\n * 말은 **지금 자리표시자라서 같아 보이는 것**이었다. 선언을 읽으면 이미 갈라져 있다.\n *\n * 정지 사유 · 위치 계층(parentName · parentId) — 표가 아니라 나무여야 한다\n * 등급 넷 baseIds 다중 상속 — 쉼표 문자열로 편집 중이다\n * 인원 · 설비 · 자산 근무 캘린더 · 검사 결과를 JSON 원문으로 보인다\n * 자재 GTIN · 단위 · 보관처의 칸이 없어 properties JSON 이다\n * 공정 duration 이 ISO 기간, bizStep 이 EPCIS 어휘다\n * 검사 기준 컬럼이 0개다 — 항목 · 상하한 · 단위 · 방법이 들어와야 한다\n *\n * 그 자리표시자를 걷으면 열둘이 서로 다른 화면이 된다. 공용 화면에 「이 축만」 분기를 열둘 넣는 것은\n * 화면 열둘보다 나쁘다.\n *\n * ── UX 일관성과 일괄 개발 편의는 다르다 ────────────────────────────────────\n * 일관성은 **쓰는 사람이 겪는 것**이다. 같은 뜻이 같은 자리에, 같은 용어로, 같은 조작으로. 그것은\n * 어휘와 배치와 부품을 공유해서 얻는다.\n *\n * 일괄 개발의 편의는 **만드는 쪽이 겪는 것**이고, 기반 클래스로 얻으면 된다. 그것을 얻으려고 서로 다른\n * 대상을 한 화면에 묶으면 편의는 만드는 쪽이 가져가고 값은 쓰는 쪽이 치른다.\n *\n * ── MDI 에서도 갈려야 한다 ─────────────────────────────────────────────────\n * MDI 탭은 페이지 **요소**로 나뉜다(`page-mdibar.ts`). 한 라우트면 인원과 설비를 나란히 열 수 없는데,\n * 마스터 작업의 대부분이 두 항목을 비교하며 옮겨 적는 일이다. 품목을 보면서 품목 등급을 채운다.\n *\n * ── 이 클래스가 갖는 것과 갖지 않는 것 ─────────────────────────────────────\n * 갖는 것: 화면 골격 · 컬럼 설정 · 가져오기 · 저장 · 컬럼 하나를 그리는 방식.\n * 갖지 않는 것: **무엇을 보이나.** 그것은 각 화면이 `spec` 으로 말한다.\n */\n\nexport type MasterColumnType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'date'\n | 'json'\n | 'list'\n /** 정해진 값 중에서 고른다. `options` 를 함께 준다. */\n | 'select'\n /**\n * ISO 8601 기간 — `PT30S`.\n *\n * 이름이 `duration` 이 아니다. 그 이름은 초 숫자용으로 이미 임자가 있다(`worklist`). 등록소를\n * 앱 전체가 나눠 쓰므로, 이름이 겹치면 나중에 부팅한 쪽이 오류보고없이 이긴다.\n */\n | 'iso-duration'\n /** 다른 표의 이름 하나. `reference` 를 함께 준다. */\n | 'reference'\n | 'reference-list'\n\nexport interface MasterColumn {\n name: string\n /** 화면 문구 키. `field.<이것>`. */\n label: string\n type: MasterColumnType\n /**\n * `select` 가 고를 값들.\n *\n * **여기에 손으로 적지 않는다.** `master-options.ts` 가 계약에서 가져오거나, 계약이 타입으로만\n * 주는 것은 컴파일러가 계약에 묶은 거울에서 가져온다. 화면에 값을 베껴 적으면 커널이 값을 늘렸을\n * 때 화면만 옛 목록을 보인다.\n */\n options?: readonly string[]\n /**\n * `reference` 가 어느 표에서 고르나.\n *\n * 저장되는 것은 **이름**이지 id 가 아니다 — 커널이 이름으로 주소를 잡는다.\n */\n reference?: ReferenceOptions\n width?: number\n /** 고칠 수 없는 칸. 자동으로 붙는 값이 여기 온다. */\n readonly?: boolean\n /**\n * 가로로 스크롤해도 화면에 남는 컬럼.\n *\n * 컬럼이 열 개를 넘고 폭 합이 1000px 을 넘는 축이 있다. 오른쪽 끝을 고치려고 스크롤하면 이름이 화면\n * 밖으로 나가서 **어느 행을 고치는지 알 수 없다.**\n */\n fixed?: boolean\n /**\n * 표에 그리지 않는다 — **검색에는 남는다.**\n *\n * 같은 사실을 두 번 보이지 않으려고 감추는 자리가 있다. 로케이션의 상위는 트리의 들여쓰기가 말하므로\n * 컬럼으로 또 보일 필요가 없지만, 「LINE-1 아래 있는 것」을 찾는 일은 있다.\n *\n * `searchText` 는 `filter: 'search'` 를 선언한 컬럼마다 필터를 만들고 hidden 을 보지 않는다\n * (`data-grist.ts` 의 `set searchText`). 그래서 감춘 컬럼도 검색에는 참여한다.\n *\n * 다만 컬럼 설정 목록에서는 **빠진다** — personalizer 가 페이지 설정의 hidden 을 걸러낸다\n * (`ox-grist-personalizer.ts:51`). 사용자가 켜야 할 컬럼에는 쓰지 않는다.\n */\n hidden?: boolean\n}\n\n/** 각 마스터 화면이 자기에 대해 말하는 것. */\nexport interface MasterSpec {\n /** 컬럼 설정을 사람마다 따로 저장하는 키. 화면마다 달라야 한다. */\n key: string\n /**\n * 이 화면이 다루는 마스터 축.\n *\n * **배정·선언 칸이 있는 축만 「언제부터」를 묻는다**(§`assignment-fields`). 안 적으면 안 묻는다 —\n * 그 축에는 이력을 받는 칸이 없다는 뜻이다.\n */\n axis?: string\n /** 제목 문구 키. `title.<이것>`. */\n title: string\n /**\n * 도움말 문서 키 — `helps/<이것>.md`.\n *\n * `ox-page-title-bar` 가 이 값이 있으면 제목 옆에 도움말 아이콘을 그린다. 상세한 설명은 그 문서가\n * 갖는다 — 화면에 형식적인 소개 문장을 붙이는 것은 자리만 차지하고 도움이 안 된다.\n */\n help: string\n /** GraphQL 조회 이름 — 서버가 낸 그대로. */\n query: string\n /** GraphQL 저장 이름. */\n mutation: string\n columns: MasterColumn[]\n}\n\n/** 열두 축이 공통으로 갖는 유효 기간 — ISA-95. */\nexport const EFFECTIVE: MasterColumn[] = [\n { name: 'effectiveStart', label: 'effective-start', type: 'date', width: 150 },\n { name: 'effectiveEnd', label: 'effective-end', type: 'date', width: 150 }\n]\n\n/** 이름과 설명은 열두 축이 모두 갖는다. 이름은 스크롤해도 남는다. */\nexport const IDENTITY: MasterColumn[] = [\n { name: 'name', label: 'name', type: 'string', width: 160, fixed: true },\n { name: 'description', label: 'description', type: 'string', width: 200 }\n]\n\nexport abstract class MasterPageBase extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n\n ox-grist {\n overflow-y: auto;\n flex: 1;\n }\n\n /*\n * 헤드룸에는 「어디를 보고 있나」만 남는다. 검색은 타이틀 줄로 올라갔다(configOf 의 search).\n *\n * 범위가 없는 화면은 이 칸이 비고, 아래 규칙이 그 줄을 아예 감춘다. 빈 띠를 남기지 않는다.\n */\n .header {\n grid-template-areas: 'scope';\n grid-template-columns: 1fr;\n }\n\n .header:empty {\n display: none;\n }\n `\n ]\n\n /** 이 화면이 자기에 대해 말하는 것. 화면마다 자기 것을 준다. */\n protected abstract get spec(): MasterSpec\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) gristConfig: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n @state() protected ready = false\n\n @query('ox-grist') protected grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t(`title.${this.spec.title}`),\n /*\n * **파일로 한 번에 올린다.**\n *\n * 새 공장을 받으면 기준정보를 통째로 넣어야 한다. 자리 · 인원 · 설비가 각각 수백에서 수천\n * 줄이고, 화면에서 한 줄씩 넣으면 인원 1000명에 다섯 시간을 타이핑만 한다. 그런데 고객은\n * 그 자료를 이미 엑셀로 갖고 있다.\n *\n * 틀은 플랫폼이 갖고 있다(`@things-factory/import-ui`). 도구막대에 버튼을 달고 파일을\n * 읽어 줄의 배열로 준다. 우리는 그 줄을 이 화면의 열에 맞추고 저장하기만 한다.\n *\n * **이 자리에 두면 마스터 화면 전부가 한꺼번에 된다.** 화면마다 따로 만들면 열넷이 조금씩\n * 다르게 동작한다.\n */\n importable: { handler: this.importHandler.bind(this) },\n /* 제목 옆의 도움말 아이콘 — 상세한 설명은 그 문서가 갖는다. */\n help: this.spec.help,\n /*\n * 검색은 **페이지 타이틀 줄의 오른쪽 끝**에 둔다.\n *\n * 타이틀은 페이지 안의 제목 줄에 선다(ADR-0060 결정 9, 모든 앱이 같다). 그래서 그\n * 줄은 상단 앱 바가 아니라 페이지의 첫 줄이고, 거기 검색을 두면 거르는 곳과 걸러진 결과가 붙어\n * 있다 — 전에 상단 바를 마다한 이유가 여기서는 생기지 않는다.\n *\n * `ox-page-title-bar` 가 이 값을 받아 타이틀 오른쪽에 그린다. 그래서 헤드룸에는 검색을 두지\n * 않는다 — 두 벌이 되고, 두 벌은 어긋난다.\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.save'),\n action: this.save.bind(this),\n icon: 'save',\n /* 이 화면의 주 동작 — 채운 버튼으로 그려진다. 한 화면에 하나만 둔다. */\n emphasis: { raised: true }\n }\n ],\n exportable: {\n name: i18next.t(`title.${this.spec.title}`),\n data: () => this.grist.exportRecords()\n }\n }\n }\n\n /**\n * 지금 무엇을 보고 있는가 — 표 바로 위.\n *\n * 계층을 드릴다운으로 걷는 화면은 「어디를 보고 있나」를 말해야 한다. 대부분의 화면은 표 하나가 전부라\n * 범위가 없다 — 그런 화면은 아무것도 그리지 않고, 그 줄이 접힌다.\n */\n protected renderScope() {\n return html``\n }\n\n /**\n * 화면 골격 — 표 하나와 컬럼 설정.\n *\n * 자기 인터페이스가 필요한 화면은 이것을 덮어쓴다. 위치와 정지 사유는 계층이라 표가 아니라 나무여야\n * 하고, 그때 이 render 를 쓰지 않는다.\n */\n render() {\n return html`\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 >\n <!--\n 자리를 뜻으로 구분한다.\n\n 어디를 보고 있나 빵가루 헤드룸 — 표 바로 위\n 좁히는 것 검색 타이틀 줄 오른쪽 끝\n 저지르는 것 저장 · 삭제 화면 바닥 — 셸이 모든 화면에 같은 자리로 둔다\n 고른 것에 하는 것 선택 후 동작 고른 것 옆\n\n 바닥인 이유는 목록을 위에서 아래로 훑고 줄을 고르고 저지르기 때문이다. 손과 눈이 아래에서\n 끝난다. 전에 동작 바를 헤드룸에도 두었다가 「저장」이 위아래에 하나씩 생겼다(실측: top 150 과\n top 720).\n -->\n <div slot=\"headroom\" class=\"header\">${this.renderScope()}</div>\n\n <!-- 컬럼 설정 버튼. provider 만 넘겨도 버튼은 안 생긴다. 이 요소를 화면이 놓아야 한다. -->\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n /**\n * 주소 끝의 이름 조각을 **목록 좁히기**에 쓰나.\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003` 이\n * 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰고 있다(그 이름 아래를 보여 준다).\n * 그 둘은 여기서 빠진다 — 두 뜻이 한 조각에 겹치면 어느 쪽도 제대로 안 된다.\n */\n protected get pathSegmentNarrowsList(): boolean {\n return true\n }\n\n async pageInitialized() {\n this.gristConfig = this.configOf()\n this.ready = true\n }\n\n /**\n * 셸은 `(changes, after, before)` 를 넘긴다 — 인수 없는 모양으로 좁히면 주소를 읽어야 하는 화면이\n * 이것을 덮어쓸 수 없다(`after.resourceId` 와 `before.resourceId` 를 비교하는 자리다).\n */\n async pageUpdated(_changes?: any, _after?: any, _before?: any) {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n\n /*\n * 반환 타입을 밝힌다. 적지 않으면 이 자리의 리터럴이 그대로 타입이 되고, 컬럼 구성을 바꾸는 화면이\n * 이것을 재정의할 수 없다(로케이션이 실제로 막혔다).\n */\n protected configOf(): any {\n return {\n /*\n * 빈 목록이 하는 말 — **축마다 다르다.**\n *\n * 「아직 아무것도 없습니다」 하나로는 이 화면들이 할 말을 못 한다. 기준정보는 처음 오는\n * 사람이 가장 먼저 만나는 자리이고, 비어 있는 사람이 묻는 것은 「이게 뭔데 채워야 하나」다.\n * 그 답은 축마다 다르다 — 인원이 없으면 작업 스테이션에서 사람을 고를 수 없고, 근무 캘린더가\n * 없으면 가동률이 「모름」이 된다.\n *\n * 항목 파일을 고치지 않는다. spec.key 로 용어를 끌어오므로 용어만 더하면 새 축도 따라온다.\n */\n emptyNote: {\n title: i18next.t(`text.master-empty-${this.spec.key}`),\n description: i18next.t(`text.master-empty-${this.spec.key}-hint`, { defaultValue: '' }) || undefined\n },\n list: { fields: ['name', 'description'], details: [] },\n columns: [\n /* 이름을 고정하면 그 왼쪽의 거터도 함께 남아야 한다 — 아니면 체크박스가 이름 아래로 밀린다. */\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n { type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },\n ...this.spec.columns.map(column => this.columnOf(column))\n ],\n /* 새 행을 만들 수 있다 — 마스터를 채우는 것이 이 화면의 일이다. */\n rows: { appendable: true, selectable: { multiple: true } },\n sorters: [{ name: 'name', desc: false }]\n }\n }\n\n protected columnOf(column: MasterColumn) {\n const header = i18next.t(`field.${column.label}`)\n const editable = !column.readonly\n const fixed = column.fixed\n const hidden = column.hidden\n\n switch (column.type) {\n case 'number':\n return { type: 'float', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n case 'boolean':\n return { type: 'boolean', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n case 'date':\n return { type: 'datetime', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n /*\n * 목록과 JSON 은 **글자로 고친다.** 전용 편집기를 만들면 축마다 다른 편집기가 생기고, 그 편집기가\n * 모르는 칸이 들어오면 값이 오류 보고 없이 사라진다. 지금은 있는 그대로 보이고 그대로 고친다.\n *\n * 이것은 **자리표시자다.** 화면이 갈라진 지금은 그 축의 화면이 자기 편집기를 가질 수 있다.\n */\n case 'select':\n /*\n * **빈 줄이 없으면 되돌릴 수 없다.** 한 번 고르면 「아직 안 정했다」로 못 돌아간다. 비워 둘\n * 수 없는 칸이면 화면 쪽에서 `options` 에 빈 값을 안 넣으면 된다.\n */\n return {\n type: 'select',\n name: column.name,\n header,\n record: { editable, options: column.options ?? [] },\n filter: 'select',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'reference':\n /*\n * 다른 표에서 골라 온다. 자유 텍스트로 두면 `GUNSAN` 을 `GUNSAM` 이라고 쳐도 저장되고,\n * 그 뒤로 그 참조는 어디에서도 안 걸린다 — 화면에는 값이 있는데.\n *\n * 고르기는 팝업이 하고, 검색은 그 팝업이 갖는다. 그래서 열 자체에는 검색 필터를 안 붙인다.\n */\n return {\n type: 'reference',\n name: column.name,\n header,\n /*\n * **제목을 여기서 채운다.** 팝업 머리에 글자가 없으면 회색 띠에 검색창만 뜨고, 무엇을\n * 고르는 중인지 화면이 말하지 않는다. 편집기 자리에서는 못 채운다 — 그리드가 머리말을\n * `{ renderer }` 객체로 바꿔서 넘기기 때문에 거기서는 글자를 볼 수 없다.\n *\n * 열의 머리말을 그대로 쓴다. 「주 근무 로케이션」을 고르는 팝업의 제목이 「주 근무\n * 로케이션」이면 둘이 어긋날 수가 없다.\n */\n record: { editable, reference: { title: header, ...column.reference } },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'iso-duration':\n /*\n * ISO 8601 기간(`PT30S`)이다. 글자로 고치면 `30s` 나 `PT30` 처럼 커널이 못 읽는 것이\n * 오류 보고 없이 저장된다. 전용 편집기가 초로 받아 표기를 만든다.\n *\n * 이 편집기는 기본 등록에 없다 — `bootstrap.ts` 가 등록한다.\n */\n return { type: 'iso-duration', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * JSON 은 **구조를 아는 편집기로 고친다.** 글자로 고치면 괄호 하나가 빠져도 저장 순간까지\n * 모르고, 저장되면 그 칸을 읽는 쪽이 통째로 못 읽는다. `json5` 는 팝업에서 문법을 보아 준다.\n */\n case 'json':\n return { type: 'json5', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * 목록은 아직 **글자로 고친다.** 이 칸들은 대개 다른 표의 이름을 담는데(등급 · 공정 · 상위\n * 등급), 그 표에서 골라 오는 편집기가 아직 없다. 자리표시자다.\n */\n /*\n * ── 여럿을 고르는 참조 ──────────────────────────────────────────────────\n *\n * 상위 등급 · 요구 검사 · 공정의 차례처럼 **관계를 여럿 다는 칸**이다. 그리는 것은 목록과\n * 같고(ops-list), 고치는 것은 참조 팝업이 여럿 고르기로 연다.\n *\n * 전에는 이런 칸이 전부 `list` 였다 — 즉 자유 텍스트였다. 그래서 GUNSAN 을 GUNSAM 이라고\n * 쳐도 저장되고, 그 뒤로 그 참조는 어디에서도 안 걸린다. 열 곳이 그 상태였다\n * (2026-09-05 · 사용자 지시로 전부 바꿈).\n */\n case 'reference-list':\n return {\n type: 'ops-list',\n name: column.name,\n header,\n record: {\n editable,\n /*\n * 편집기는 참조 팝업이 맡는다 — 그리기만 목록 렌더러를 쓴다.\n *\n * 열 빌더는 `column.type` 으로 편집기를 고르므로(column-builder), 여기서 함수를\n * 직접 준다. 문자열을 주면 무시되고 글자 편집기가 붙는다.\n */\n editor: getEditor('reference'),\n /*\n * 팝업으로 연다고 밝힌다. 표식(edit_square)의 판단이 열 타입을 보는데, 이 열은\n * 그리기를 목록 렌더러로 하려고 타입이 `ops-list` 다 — 그대로 두면 칸 안에서\n * 고치는 것으로 표시된다.\n */\n opensPopup: true,\n reference: { title: header, multiple: true, ...column.reference }\n },\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'list':\n /*\n * 그리기만 우리 것을 쓰고 고치기는 글자 그대로다(`ops-list` 는 렌더러만 등록한다).\n * 기본 글자 렌더러는 배열을 구분 없이 이어 붙인다.\n */\n return { type: 'ops-list', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n default:\n return {\n type: 'string',\n name: column.name,\n header,\n record: { editable },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [] }: FetchOption) {\n const fields = this.spec.columns.map(column => column.name).join('\\n')\n\n /*\n * ── 주소 끝의 이름 조각으로 좁힌다 ──────────────────────────────────────\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003`\n * 이 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * **그리드의 검색어를 세우는 방식은 안 된다.** 그렇게 해 봤더니 값은 실제로 세워지는데(기억에\n * 남았다) 그 뒤 그리드가 설정을 복원하면서 지웠다. 훅 자리를 앞뒤로 옮겨도 이기지 못하는\n * 경합이다. 질의를 만드는 이 자리에서 조건을 얹으면 경합이 없다 — 언제 불려도 같은 답이다.\n * [확인함] 2026-09-04\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰므로 여기서 빠진다.\n */\n const segment = this.pathSegmentNarrowsList ? (this as any).lifecycle?.resourceId : undefined\n\n if (segment) {\n filters = [...filters, { name: 'name', operator: 'eq', value: decodeURIComponent(segment) }]\n }\n\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n ${this.spec.query}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n ${fields}\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n fetchPolicy: 'no-cache'\n })\n\n const payload = response.data?.[this.spec.query]\n const records = (payload?.items ?? []).map((item: any) => this.spreadJson(item))\n\n /* 바뀌기 전 값을 보여 주려면 읽은 것을 들고 있어야 한다 — 그리드는 바뀐 것만 돌려준다. */\n this.lastRead = new Map(records.map((one: any) => [String(one.id), one]))\n\n return { total: payload?.total ?? 0, records }\n }\n\n /**\n * JSON 칸을 **사람이 고칠 수 있는 글자로 편다.**\n *\n * 서버는 구조를 구조로 낸다(`ScalarObject` 로 선언하지만 **스키마에 나가는 이름은 `Object`** 다) — 그것이\n * 사실이고, 곧 붙을 전용 편집기가 원하는 모양이다.\n * 그런데 지금 이 그리드의 JSON 칸은 글자 편집기라 객체를 그대로 주면 `[object Object]` 가 된다.\n * 그래서 **화면에 들어올 때만** 편다. 스키마를 화면 사정에 맞추지 않고, 화면이 자기 몫을 한다.\n *\n * 이 옮김은 `packJson` 과 짝이다. 한쪽만 고치면 저장이 오류보고없이 망가진다.\n */\n protected spreadJson(item: any): any {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return item\n }\n\n const spread = { ...item }\n\n for (const column of jsonColumns) {\n const value = spread[column.name]\n\n /* 비어 있는 것은 비어 있는 채로 둔다 — `null` 을 `\"null\"` 로 만들면 지운 것이 값이 된다. */\n if (value === undefined || value === null || typeof value === 'string') {\n continue\n }\n\n spread[column.name] = JSON.stringify(value, null, 0)\n }\n\n return spread\n }\n\n /**\n * 저장하기 전에 JSON 칸의 글자를 **구조로 되읽는다.**\n *\n * 읽지 못하면 **보내지 않는다.** 그냥 보내면 글자가 통째로 값이 되어, 사람이 적은 구조가 사라진 채로\n * 저장이 성공했다고 나온다. 어느 행 어느 칸인지 말해 주어야 고칠 수 있다.\n */\n protected packJson(patches: any[]): { patches: any[]; problems: string[] } {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return { patches, problems: [] }\n }\n\n const problems: string[] = []\n\n const packed = patches.map((patch, index) => {\n const one = { ...patch }\n\n for (const column of jsonColumns) {\n const value = one[column.name]\n\n if (typeof value !== 'string') {\n /* 손대지 않은 칸은 이미 구조다. 비운 칸은 비운 채로 간다. */\n continue\n }\n\n if (value.trim() === '') {\n /* 비운 것은 「없앤다」는 뜻이다. 빈 글자로 보내면 칸의 뜻이 흐려진다. */\n one[column.name] = null\n continue\n }\n\n try {\n one[column.name] = JSON.parse(value)\n } catch (error) {\n problems.push(\n i18next.t('text.json-column-unreadable', {\n row: index + 1,\n column: i18next.t(`field.${column.label}`),\n detail: (error as Error).message\n })\n )\n }\n }\n\n return one\n })\n\n return { patches: packed, problems }\n }\n\n /**\n * 파일로 올린 줄들을 이 화면의 마스터로 넣는다.\n *\n * ── 오류보고없이 버리지 않는다 ─────────────────────────────────────────────────\n * 모르는 열 이름, 이름이 없는 줄, 중복된 이름을 **알린다.** 오류보고없이 빼면 사람은 다 올라간 줄 알고,\n * 빠진 것은 나중에 「그 설비가 왜 없지」로 나타난다.\n *\n * ── 하나라도 틀리면 아무것도 안 보낸다 ───────────────────────────────────\n * 절반만 들어가면 무엇이 들어갔는지 사람이 모른다. 파일을 고쳐 다시 올리는 편이 낫다.\n *\n * ── 새로 만들 것과 고칠 것을 이름으로 구분한다 ─────────────────────────────\n * 서버는 `+`(새로)와 `M`(고침, id 필요)을 나눠 받는다. 그러니 **올리기 전에 그 이름들이 이미\n * 있는지 물어야 한다.** 안 물으면 같은 이름이 두 벌 생긴다.\n */\n protected async importHandler(records: any[]) {\n if (!records?.length) {\n notify({ message: i18next.t('text.nothing-to-import'), level: 'info' })\n return\n }\n\n const byHeader = this.headerIndex()\n const reading = readImportRows(records, header => byHeader.get(header))\n\n if (reading.problems.length) {\n /* 열 줄까지만 보인다 — 파일이 통째로 어긋나면 문제도 줄 수만큼 나온다. */\n notify({ message: reading.problems.slice(0, 10).map(problem => this.sayProblem(problem)).join('\\n'), level: 'error' })\n return\n }\n\n const existing = await this.idsByName(reading.rows.map(row => String(row.name)))\n const patches = patchesFor(reading.rows, name => existing.get(name))\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n notify({ message: packed.problems.slice(0, 10).join('\\n'), level: 'error' })\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [Object!]!) {\n ${this.spec.mutation}(patches: $patches) {\n id\n }\n }\n `,\n variables: { patches: packed.patches }\n })\n\n /*\n * 이 앱의 클라이언트는 서버가 거절해도 던지지 않고 `errors` 에 담아 준다. 여기서 보지 않으면\n * 아래의 「올렸다」가 실패한 뒤에도 그대로 뜬다. 사유는 앱의 공통 오류 처리가 이미 띄우므로\n * 여기서 다시 띄우지 않는다 — 같은 말이 두 번 뜬다.\n */\n if (response?.errors?.length) {\n return\n }\n\n const made = patches.filter(patch => patch.cuFlag === '+').length\n\n notify({\n message: i18next.t('text.import-done', { made, changed: patches.length - made }),\n level: 'info'\n })\n\n this.grist.fetch()\n }\n\n /**\n * 파일의 머리글을 이 화면의 열 이름으로 바꾼다.\n *\n * **두 가지를 다 받는다** — 열 이름 그대로(`homeLocation`)와 화면에 보이는 말(`홈 자리`).\n * 사람이 이 화면에서 내려받아 고쳐 올리면 머리글이 화면의 말이고, 다른 시스템에서 뽑아 오면\n * 열 이름이다. 한쪽만 받으면 다른 쪽이 통째로 「모르는 열」이 된다.\n */\n /** 무엇이 잘못됐는지를 사람에게 보일 문장으로 바꾼다. 줄 번호는 엑셀에서 보는 그 번호다. */\n private sayProblem(problem: ImportProblem): string {\n switch (problem.kind) {\n case 'unknown-column':\n return i18next.t('error.import-unknown-column', { line: problem.line, column: problem.column })\n case 'no-name':\n return i18next.t('error.import-no-name', { line: problem.line })\n case 'duplicate-name':\n return i18next.t('error.import-duplicate-name', { line: problem.line, name: problem.name })\n }\n }\n\n private headerIndex(): Map<string, string> {\n const found = new Map<string, string>()\n\n for (const column of this.spec.columns) {\n found.set(column.name, column.name)\n found.set(i18next.t(`field.${column.name}`), column.name)\n }\n\n return found\n }\n\n /** 이 이름들이 이미 있나 — 있으면 그 `id`. 없는 이름은 지도에 없다. */\n private async idsByName(names: string[]): Promise<Map<string, string>> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination) {\n ${this.spec.query}(filters: $filters, pagination: $pagination) {\n items {\n id\n name\n }\n }\n }\n `,\n variables: {\n filters: [{ name: 'name', operator: 'in', value: names }],\n /* 올린 줄 수만큼만 돌아오면 된다. 기본 50으로 두면 51번째부터 새로 만들어 두 벌이 된다. */\n pagination: { page: 1, limit: names.length }\n },\n fetchPolicy: 'no-cache'\n })\n\n return new Map(\n (response.data?.[this.spec.query]?.items ?? []).map((item: any) => [item.name, item.id])\n )\n }\n\n /** 마지막으로 읽은 줄들. 「바뀌기 전 → 바뀐 뒤」를 보여 주는 데 쓴다. */\n protected lastRead?: Map<string, any>\n\n /** 「언제부터」를 묻는다. 사람이 그만두면 `undefined` 이고, 그때는 **아무것도 안 보낸다.** */\n protected askAppliedWhen(changes: AssignmentChange[]): Promise<AppliedAnswer | undefined> {\n return new Promise(resolve => {\n const popup: any = openPopup(\n html`<applied-when-dialog\n .changes=${changes}\n .onAnswer=${(answer: AppliedAnswer) => {\n popup?.close?.()\n resolve(answer)\n }}\n .onCancel=${() => {\n popup?.close?.()\n resolve(undefined)\n }}\n ></applied-when-dialog>`,\n { backdrop: true, size: 'medium', title: i18next.t('title.when-does-this-apply') }\n )\n })\n }\n\n protected async save() {\n const patches = this.grist.dirtyRecords\n\n if (!patches?.length) {\n notify({ message: i18next.t('text.nothing-changed'), level: 'info' })\n return\n }\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n /* 하나도 보내지 않는다. 절반만 저장되면 화면과 저장된 것이 달라진다. */\n notify({ message: packed.problems.join('\\n'), level: 'error' })\n return\n }\n\n /*\n * ── 배정과 선언은 「언제부터」를 묻고 보낸다 ─────────────────────────────\n *\n * 자리 배정 · 근무조 선언 · 부모 관계는 가동률 분모를 정한다. 그래서 값만 바꾸는 것이 아니라\n * **언제부터 그렇게 볼 것인가**를 함께 정해야 하고, 과거를 적으면 지연 기록인지 정정인지까지\n * 갈라야 한다(§`applied-when-dialog`).\n *\n * 안 물어도 저장은 된다 — 그때는 「지금부터」라는 주장이 된다. 다만 **묻지 않으면 사람이 그것을\n * 주장한 줄 모른다.**\n */\n const assignments = assignmentChangesIn(this.spec.axis ?? '', packed.patches, id => this.lastRead?.get(id))\n const applied = assignments.length ? await this.askAppliedWhen(assignments) : undefined\n\n /* 그만두었으면 아무것도 안 보낸다 — 절반만 저장되면 화면과 저장된 것이 달라진다. */\n if (assignments.length && !applied) {\n return\n }\n\n const response = await client.mutate({\n /*\n * **타입 이름은 `Object` 다.**\n *\n * 서버가 `ScalarObject` 라는 이름으로 선언하지만 스키마에 나가는 이름은 `Object` 이고,\n * `ScalarObject` 로 적으면 「Unknown type」으로 거절된다. 2026-09-01부터 이 자리가 그렇게\n * 적혀 있어 **모든 기준정보 화면의 저장이 안 됐다.** 화면은 오류를 그대로 보여 주지도\n * 않았다 — 이 앱의 클라이언트는 서버가 거절해도 던지지 않는다.\n */\n mutation: gql`\n mutation ($patches: [Object!]!, $applied: Object) {\n ${this.spec.mutation}(patches: $patches, applied: $applied) {\n id\n name\n }\n }\n `,\n variables: { patches: packed.patches, applied }\n })\n\n /*\n * 이 클라이언트는 서버가 거절해도 던지지 않는다 — 오류는 `response.errors` 에 담겨 온다.\n * 보지 않으면 저장에 실패해도 화면이 목록을 다시 읽어 「저장된 것처럼」 보인다.\n * 사유 자체는 앱의 공통 오류 처리가 띄운다.\n */\n if (response.errors?.length) {\n return\n }\n\n /* 지운 행은 사라지지 않고 유효 기간의 끝이 채워진다. 말해 주지 않으면 저장이 안 된 것으로 읽는다. */\n if (packed.patches.some((patch: any) => String(patch.cuFlag).toUpperCase() === '-')) {\n notify({ message: i18next.t('text.retired-not-deleted'), level: 'info' })\n }\n\n this.grist.fetch()\n }\n}\n"]}
1
+ {"version":3,"file":"master-page-base.js","sourceRoot":"","sources":["../../../client/pages/master/master-page-base.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { property, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption, getEditor } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify, openPopup } from '@operato/layout'\nimport { p13n } from '@operato/p13n'\n\nimport type { ReferenceOptions } from './grist-reference.js'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport { ImportProblem, patchesFor, readImportRows } from './import-rows.js'\nimport { AssignmentChange, assignmentChangesIn } from './assignment-fields.js'\nimport { AppliedAnswer } from './applied-when-dialog.js'\nimport './applied-when-dialog.js'\n\n/**\n * 마스터 화면이 함께 쓰는 것 — **코드 재사용은 여기서, 화면은 각자.**\n *\n * ── 왜 화면을 갈랐나 (2026-09-01) ──────────────────────────────────────────\n * 전에는 화면 하나가 `?axis=` 로 열두 축을 다 맡았다. 근거는 「열두 축이 하는 일이 같다」였는데, 그\n * 말은 **지금 자리표시자라서 같아 보이는 것**이었다. 선언을 읽으면 이미 갈라져 있다.\n *\n * 정지 사유 · 위치 계층(parentName · parentId) — 표가 아니라 나무여야 한다\n * 등급 넷 baseIds 다중 상속 — 쉼표 문자열로 편집 중이다\n * 인원 · 설비 · 자산 근무 캘린더 · 검사 결과를 JSON 원문으로 보인다\n * 자재 GTIN · 단위 · 보관처의 칸이 없어 properties JSON 이다\n * 공정 duration 이 ISO 기간, bizStep 이 EPCIS 어휘다\n * 검사 기준 컬럼이 0개다 — 항목 · 상하한 · 단위 · 방법이 들어와야 한다\n *\n * 그 자리표시자를 걷으면 열둘이 서로 다른 화면이 된다. 공용 화면에 「이 축만」 분기를 열둘 넣는 것은\n * 화면 열둘보다 나쁘다.\n *\n * ── UX 일관성과 일괄 개발 편의는 다르다 ────────────────────────────────────\n * 일관성은 **쓰는 사람이 겪는 것**이다. 같은 뜻이 같은 자리에, 같은 용어로, 같은 조작으로. 그것은\n * 어휘와 배치와 부품을 공유해서 얻는다.\n *\n * 일괄 개발의 편의는 **만드는 쪽이 겪는 것**이고, 기반 클래스로 얻으면 된다. 그것을 얻으려고 서로 다른\n * 대상을 한 화면에 묶으면 편의는 만드는 쪽이 가져가고 값은 쓰는 쪽이 치른다.\n *\n * ── MDI 에서도 갈려야 한다 ─────────────────────────────────────────────────\n * MDI 탭은 페이지 **요소**로 나뉜다(`page-mdibar.ts`). 한 라우트면 인원과 설비를 나란히 열 수 없는데,\n * 마스터 작업의 대부분이 두 항목을 비교하며 옮겨 적는 일이다. 품목을 보면서 품목 등급을 채운다.\n *\n * ── 이 클래스가 갖는 것과 갖지 않는 것 ─────────────────────────────────────\n * 갖는 것: 화면 골격 · 컬럼 설정 · 가져오기 · 저장 · 컬럼 하나를 그리는 방식.\n * 갖지 않는 것: **무엇을 보이나.** 그것은 각 화면이 `spec` 으로 말한다.\n */\n\nexport type MasterColumnType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'date'\n | 'json'\n | 'list'\n /** 정해진 값 중에서 고른다. `options` 를 함께 준다. */\n | 'select'\n /**\n * ISO 8601 기간 — `PT30S`.\n *\n * 이름이 `duration` 이 아니다. 그 이름은 초 숫자용으로 이미 임자가 있다(`worklist`). 등록소를\n * 앱 전체가 나눠 쓰므로, 이름이 겹치면 나중에 부팅한 쪽이 오류보고없이 이긴다.\n */\n | 'iso-duration'\n /** 다른 표의 이름 하나. `reference` 를 함께 준다. */\n | 'reference'\n | 'reference-list'\n\nexport interface MasterColumn {\n name: string\n /** 화면 문구 키. `field.<이것>`. */\n label: string\n type: MasterColumnType\n /**\n * `select` 가 고를 값들.\n *\n * **여기에 손으로 적지 않는다.** `master-options.ts` 가 계약에서 가져오거나, 계약이 타입으로만\n * 주는 것은 컴파일러가 계약에 묶은 거울에서 가져온다. 화면에 값을 베껴 적으면 커널이 값을 늘렸을\n * 때 화면만 옛 목록을 보인다.\n */\n options?: readonly string[]\n /**\n * `reference` 가 어느 표에서 고르나.\n *\n * 저장되는 것은 **이름**이지 id 가 아니다 — 커널이 이름으로 주소를 잡는다.\n */\n reference?: ReferenceOptions\n width?: number\n /** 고칠 수 없는 칸. 자동으로 붙는 값이 여기 온다. */\n readonly?: boolean\n /**\n * 가로로 스크롤해도 화면에 남는 컬럼.\n *\n * 컬럼이 열 개를 넘고 폭 합이 1000px 을 넘는 축이 있다. 오른쪽 끝을 고치려고 스크롤하면 이름이 화면\n * 밖으로 나가서 **어느 행을 고치는지 알 수 없다.**\n */\n fixed?: boolean\n /**\n * 표에 그리지 않는다 — **검색에는 남는다.**\n *\n * 같은 사실을 두 번 보이지 않으려고 감추는 자리가 있다. 로케이션의 상위는 트리의 들여쓰기가 말하므로\n * 컬럼으로 또 보일 필요가 없지만, 「LINE-1 아래 있는 것」을 찾는 일은 있다.\n *\n * `searchText` 는 `filter: 'search'` 를 선언한 컬럼마다 필터를 만들고 hidden 을 보지 않는다\n * (`data-grist.ts` 의 `set searchText`). 그래서 감춘 컬럼도 검색에는 참여한다.\n *\n * 다만 컬럼 설정 목록에서는 **빠진다** — personalizer 가 페이지 설정의 hidden 을 걸러낸다\n * (`ox-grist-personalizer.ts:51`). 사용자가 켜야 할 컬럼에는 쓰지 않는다.\n */\n hidden?: boolean\n}\n\n/** 각 마스터 화면이 자기에 대해 말하는 것. */\nexport interface MasterSpec {\n /** 컬럼 설정을 사람마다 따로 저장하는 키. 화면마다 달라야 한다. */\n key: string\n /**\n * 이 화면이 다루는 마스터 축.\n *\n * **배정·선언 칸이 있는 축만 「언제부터」를 묻는다**(§`assignment-fields`). 안 적으면 안 묻는다 —\n * 그 축에는 이력을 받는 칸이 없다는 뜻이다.\n */\n axis?: string\n /** 제목 문구 키. `title.<이것>`. */\n title: string\n /**\n * 도움말 문서 키 — `helps/<이것>.md`.\n *\n * `ox-page-title-bar` 가 이 값이 있으면 제목 옆에 도움말 아이콘을 그린다. 상세한 설명은 그 문서가\n * 갖는다 — 화면에 형식적인 소개 문장을 붙이는 것은 자리만 차지하고 도움이 안 된다.\n */\n help: string\n /** GraphQL 조회 이름 — 서버가 낸 그대로. */\n query: string\n /** GraphQL 저장 이름. */\n mutation: string\n columns: MasterColumn[]\n}\n\n/** 열두 축이 공통으로 갖는 유효 기간 — ISA-95. */\nexport const EFFECTIVE: MasterColumn[] = [\n { name: 'effectiveStart', label: 'effective-start', type: 'date', width: 150 },\n { name: 'effectiveEnd', label: 'effective-end', type: 'date', width: 150 }\n]\n\n/** 이름과 설명은 열두 축이 모두 갖는다. 이름은 스크롤해도 남는다. */\nexport const IDENTITY: MasterColumn[] = [\n { name: 'name', label: 'name', type: 'string', width: 160, fixed: true },\n { name: 'description', label: 'description', type: 'string', width: 200 }\n]\n\nexport abstract class MasterPageBase extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n\n ox-grist {\n overflow-y: auto;\n flex: 1;\n }\n\n /*\n * 헤드룸에는 「어디를 보고 있나」만 남는다. 검색은 타이틀 줄로 올라갔다(configOf 의 search).\n *\n * 범위가 없는 화면은 이 칸이 비고, 아래 규칙이 그 줄을 아예 감춘다. 빈 띠를 남기지 않는다.\n */\n .header {\n grid-template-areas: 'scope';\n grid-template-columns: 1fr;\n }\n\n .header:empty {\n display: none;\n }\n `\n ]\n\n /** 이 화면이 자기에 대해 말하는 것. 화면마다 자기 것을 준다. */\n protected abstract get spec(): MasterSpec\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) gristConfig: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n @state() protected ready = false\n\n @query('ox-grist') protected grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t(`title.${this.spec.title}`),\n /*\n * **파일로 한 번에 올린다.**\n *\n * 새 공장을 받으면 기준정보를 통째로 넣어야 한다. 자리 · 인원 · 설비가 각각 수백에서 수천\n * 줄이고, 화면에서 한 줄씩 넣으면 인원 1000명에 다섯 시간을 타이핑만 한다. 그런데 고객은\n * 그 자료를 이미 엑셀로 갖고 있다.\n *\n * 틀은 플랫폼이 갖고 있다(`@things-factory/import-ui`). 도구막대에 버튼을 달고 파일을\n * 읽어 줄의 배열로 준다. 우리는 그 줄을 이 화면의 열에 맞추고 저장하기만 한다.\n *\n * **이 자리에 두면 마스터 화면 전부가 한꺼번에 된다.** 화면마다 따로 만들면 열넷이 조금씩\n * 다르게 동작한다.\n */\n importable: { handler: this.importHandler.bind(this) },\n /* 제목 옆의 도움말 아이콘 — 상세한 설명은 그 문서가 갖는다. */\n help: this.spec.help,\n /*\n * 검색은 **페이지 타이틀 줄의 오른쪽 끝**에 둔다.\n *\n * 타이틀은 페이지 안의 제목 줄에 선다(ADR-0060 결정 9, 모든 앱이 같다). 그래서 그\n * 줄은 상단 앱 바가 아니라 페이지의 첫 줄이고, 거기 검색을 두면 거르는 곳과 걸러진 결과가 붙어\n * 있다 — 전에 상단 바를 마다한 이유가 여기서는 생기지 않는다.\n *\n * `ox-page-title-bar` 가 이 값을 받아 타이틀 오른쪽에 그린다. 그래서 헤드룸에는 검색을 두지\n * 않는다 — 두 벌이 되고, 두 벌은 어긋난다.\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.save'),\n action: this.save.bind(this),\n icon: 'save',\n /* 이 화면의 주 동작 — 채운 버튼으로 그려진다. 한 화면에 하나만 둔다. */\n emphasis: { raised: true }\n }\n ],\n exportable: {\n name: i18next.t(`title.${this.spec.title}`),\n data: () => this.grist.exportRecords()\n }\n }\n }\n\n /**\n * 지금 무엇을 보고 있는가 — 표 바로 위.\n *\n * 계층을 드릴다운으로 걷는 화면은 「어디를 보고 있나」를 말해야 한다. 대부분의 화면은 표 하나가 전부라\n * 범위가 없다 — 그런 화면은 아무것도 그리지 않고, 그 줄이 접힌다.\n */\n protected renderScope() {\n return html``\n }\n\n /**\n * 화면 골격 — 표 하나와 컬럼 설정.\n *\n * 자기 인터페이스가 필요한 화면은 이것을 덮어쓴다. 위치와 정지 사유는 계층이라 표가 아니라 나무여야\n * 하고, 그때 이 render 를 쓰지 않는다.\n */\n render() {\n return html`\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 >\n <!--\n 자리를 뜻으로 구분한다.\n\n 어디를 보고 있나 빵가루 헤드룸 — 표 바로 위\n 좁히는 것 검색 타이틀 줄 오른쪽 끝\n 저지르는 것 저장 · 삭제 화면 바닥 — 셸이 모든 화면에 같은 자리로 둔다\n 고른 것에 하는 것 선택 후 동작 고른 것 옆\n\n 바닥인 이유는 목록을 위에서 아래로 훑고 줄을 고르고 저지르기 때문이다. 손과 눈이 아래에서\n 끝난다. 전에 동작 바를 헤드룸에도 두었다가 「저장」이 위아래에 하나씩 생겼다(실측: top 150 과\n top 720).\n -->\n <div slot=\"headroom\" class=\"header\">${this.renderScope()}</div>\n\n <!-- 컬럼 설정 버튼. provider 만 넘겨도 버튼은 안 생긴다. 이 요소를 화면이 놓아야 한다. -->\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n /**\n * 주소 끝의 이름 조각을 **목록 좁히기**에 쓰나.\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003` 이\n * 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰고 있다(그 이름 아래를 보여 준다).\n * 그 둘은 여기서 빠진다 — 두 뜻이 한 조각에 겹치면 어느 쪽도 제대로 안 된다.\n */\n protected get pathSegmentNarrowsList(): boolean {\n return true\n }\n\n async pageInitialized() {\n this.gristConfig = this.configOf()\n this.ready = true\n }\n\n /**\n * 셸은 `(changes, after, before)` 를 넘긴다 — 인수 없는 모양으로 좁히면 주소를 읽어야 하는 화면이\n * 이것을 덮어쓸 수 없다(`after.resourceId` 와 `before.resourceId` 를 비교하는 자리다).\n */\n async pageUpdated(_changes?: any, _after?: any, _before?: any) {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n\n /*\n * 반환 타입을 밝힌다. 적지 않으면 이 자리의 리터럴이 그대로 타입이 되고, 컬럼 구성을 바꾸는 화면이\n * 이것을 재정의할 수 없다(로케이션이 실제로 막혔다).\n */\n protected configOf(): any {\n return {\n /*\n * 빈 목록이 하는 말 — **축마다 다르다.**\n *\n * 「아직 아무것도 없습니다」 하나로는 이 화면들이 할 말을 못 한다. 기준정보는 처음 오는\n * 사람이 가장 먼저 만나는 자리이고, 비어 있는 사람이 묻는 것은 「이게 뭔데 채워야 하나」다.\n * 그 답은 축마다 다르다 — 인원이 없으면 작업 스테이션에서 사람을 고를 수 없고, 근무 캘린더가\n * 없으면 가동률이 「모름」이 된다.\n *\n * 항목 파일을 고치지 않는다. spec.key 로 용어를 끌어오므로 용어만 더하면 새 축도 따라온다.\n */\n emptyNote: {\n title: i18next.t(`text.master-empty-${this.spec.key}`),\n description: i18next.t(`text.master-empty-${this.spec.key}-hint`, { defaultValue: '' }) || undefined\n },\n list: { fields: ['name', 'description'], details: [] },\n columns: [\n /* 이름을 고정하면 그 왼쪽의 거터도 함께 남아야 한다 — 아니면 체크박스가 이름 아래로 밀린다. */\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n { type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },\n ...this.spec.columns.map(column => this.columnOf(column))\n ],\n /* 새 행을 만들 수 있다 — 마스터를 채우는 것이 이 화면의 일이다. */\n rows: { appendable: true, selectable: { multiple: true } },\n sorters: [{ name: 'name', desc: false }]\n }\n }\n\n protected columnOf(column: MasterColumn) {\n const header = i18next.t(`field.${column.label}`)\n const editable = !column.readonly\n const fixed = column.fixed\n const hidden = column.hidden\n\n switch (column.type) {\n case 'number':\n return { type: 'float', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n case 'boolean':\n return { type: 'boolean', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n case 'date':\n return { type: 'datetime', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n /*\n * 목록과 JSON 은 **글자로 고친다.** 전용 편집기를 만들면 축마다 다른 편집기가 생기고, 그 편집기가\n * 모르는 칸이 들어오면 값이 오류 보고 없이 사라진다. 지금은 있는 그대로 보이고 그대로 고친다.\n *\n * 이것은 **자리표시자다.** 화면이 갈라진 지금은 그 축의 화면이 자기 편집기를 가질 수 있다.\n */\n case 'select':\n /*\n * **빈 줄이 없으면 되돌릴 수 없다.** 한 번 고르면 「아직 안 정했다」로 못 돌아간다. 비워 둘\n * 수 없는 칸이면 화면 쪽에서 `options` 에 빈 값을 안 넣으면 된다.\n */\n return {\n type: 'select',\n name: column.name,\n header,\n record: { editable, options: column.options ?? [] },\n filter: 'select',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'reference':\n /*\n * 다른 표에서 골라 온다. 자유 텍스트로 두면 `GUNSAN` 을 `GUNSAM` 이라고 쳐도 저장되고,\n * 그 뒤로 그 참조는 어디에서도 안 걸린다 — 화면에는 값이 있는데.\n *\n * 고르기는 팝업이 하고, 검색은 그 팝업이 갖는다. 그래서 열 자체에는 검색 필터를 안 붙인다.\n */\n return {\n type: 'reference',\n name: column.name,\n header,\n /*\n * **제목을 여기서 채운다.** 팝업 머리에 글자가 없으면 회색 띠에 검색창만 뜨고, 무엇을\n * 고르는 중인지 화면이 말하지 않는다. 편집기 자리에서는 못 채운다 — 그리드가 머리말을\n * `{ renderer }` 객체로 바꿔서 넘기기 때문에 거기서는 글자를 볼 수 없다.\n *\n * 열의 머리말을 그대로 쓴다. 「주 근무 로케이션」을 고르는 팝업의 제목이 「주 근무\n * 로케이션」이면 둘이 어긋날 수가 없다.\n */\n record: { editable, reference: { title: header, ...column.reference } },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'iso-duration':\n /*\n * ISO 8601 기간(`PT30S`)이다. 글자로 고치면 `30s` 나 `PT30` 처럼 커널이 못 읽는 것이\n * 오류 보고 없이 저장된다. 전용 편집기가 초로 받아 표기를 만든다.\n *\n * 이 편집기는 기본 등록에 없다 — `bootstrap.ts` 가 등록한다.\n */\n return { type: 'iso-duration', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * JSON 은 **구조를 아는 편집기로 고친다.** 글자로 고치면 괄호 하나가 빠져도 저장 순간까지\n * 모르고, 저장되면 그 칸을 읽는 쪽이 통째로 못 읽는다. `json5` 는 팝업에서 문법을 보아 준다.\n */\n case 'json':\n return { type: 'json5', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * 목록은 아직 **글자로 고친다.** 이 칸들은 대개 다른 표의 이름을 담는데(등급 · 공정 · 상위\n * 등급), 그 표에서 골라 오는 편집기가 아직 없다. 자리표시자다.\n */\n /*\n * ── 여럿을 고르는 참조 ──────────────────────────────────────────────────\n *\n * 상위 등급 · 요구 검사 · 공정의 차례처럼 **관계를 여럿 다는 칸**이다. 그리는 것은 목록과\n * 같고(ops-list), 고치는 것은 참조 팝업이 여럿 고르기로 연다.\n *\n * 전에는 이런 칸이 전부 `list` 였다 — 즉 자유 텍스트였다. 그래서 GUNSAN 을 GUNSAM 이라고\n * 쳐도 저장되고, 그 뒤로 그 참조는 어디에서도 안 걸린다. 열 곳이 그 상태였다\n * (2026-09-05 · 사용자 지시로 전부 바꿈).\n */\n case 'reference-list':\n return {\n type: 'ops-list',\n name: column.name,\n header,\n record: {\n editable,\n /*\n * 편집기는 참조 팝업이 맡는다 — 그리기만 목록 렌더러를 쓴다.\n *\n * 열 빌더는 `column.type` 으로 편집기를 고르므로(column-builder), 여기서 함수를\n * 직접 준다. 문자열을 주면 무시되고 글자 편집기가 붙는다.\n */\n editor: getEditor('reference'),\n /*\n * 팝업으로 연다고 밝힌다. 표식(edit_square)의 판단이 열 타입을 보는데, 이 열은\n * 그리기를 목록 렌더러로 하려고 타입이 `ops-list` 다 — 그대로 두면 칸 안에서\n * 고치는 것으로 표시된다.\n */\n opensPopup: true,\n reference: { title: header, multiple: true, ...column.reference }\n },\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'list':\n /*\n * 그리기만 우리 것을 쓰고 고치기는 글자 그대로다(`ops-list` 는 렌더러만 등록한다).\n * 기본 글자 렌더러는 배열을 구분 없이 이어 붙인다.\n */\n return { type: 'ops-list', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n default:\n return {\n type: 'string',\n name: column.name,\n header,\n record: { editable },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [] }: FetchOption) {\n const fields = this.spec.columns.map(column => column.name).join('\\n')\n\n /*\n * ── 주소 끝의 이름 조각으로 좁힌다 ──────────────────────────────────────\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003`\n * 이 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * **그리드의 검색어를 세우는 방식은 안 된다.** 그렇게 해 봤더니 값은 실제로 세워지는데(기억에\n * 남았다) 그 뒤 그리드가 설정을 복원하면서 지웠다. 훅 자리를 앞뒤로 옮겨도 이기지 못하는\n * 경합이다. 질의를 만드는 이 자리에서 조건을 얹으면 경합이 없다 — 언제 불려도 같은 답이다.\n * [확인함] 2026-09-04\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰므로 여기서 빠진다.\n */\n const segment = this.pathSegmentNarrowsList ? (this as any).lifecycle?.resourceId : undefined\n\n if (segment) {\n filters = [...filters, { name: 'name', operator: 'eq', value: decodeURIComponent(segment) }]\n }\n\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n ${this.spec.query}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n ${fields}\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n fetchPolicy: 'no-cache'\n })\n\n const payload = response.data?.[this.spec.query]\n const records = (payload?.items ?? []).map((item: any) => this.spreadJson(item))\n\n return { total: payload?.total ?? 0, records }\n }\n\n /**\n * JSON 칸을 **사람이 고칠 수 있는 글자로 편다.**\n *\n * 서버는 구조를 구조로 낸다(`ScalarObject` 로 선언하지만 **스키마에 나가는 이름은 `Object`** 다) — 그것이\n * 사실이고, 곧 붙을 전용 편집기가 원하는 모양이다.\n * 그런데 지금 이 그리드의 JSON 칸은 글자 편집기라 객체를 그대로 주면 `[object Object]` 가 된다.\n * 그래서 **화면에 들어올 때만** 편다. 스키마를 화면 사정에 맞추지 않고, 화면이 자기 몫을 한다.\n *\n * 이 옮김은 `packJson` 과 짝이다. 한쪽만 고치면 저장이 오류보고없이 망가진다.\n */\n protected spreadJson(item: any): any {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return item\n }\n\n const spread = { ...item }\n\n for (const column of jsonColumns) {\n const value = spread[column.name]\n\n /* 비어 있는 것은 비어 있는 채로 둔다 — `null` 을 `\"null\"` 로 만들면 지운 것이 값이 된다. */\n if (value === undefined || value === null || typeof value === 'string') {\n continue\n }\n\n spread[column.name] = JSON.stringify(value, null, 0)\n }\n\n return spread\n }\n\n /**\n * 저장하기 전에 JSON 칸의 글자를 **구조로 되읽는다.**\n *\n * 읽지 못하면 **보내지 않는다.** 그냥 보내면 글자가 통째로 값이 되어, 사람이 적은 구조가 사라진 채로\n * 저장이 성공했다고 나온다. 어느 행 어느 칸인지 말해 주어야 고칠 수 있다.\n */\n protected packJson(patches: any[]): { patches: any[]; problems: string[] } {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return { patches, problems: [] }\n }\n\n const problems: string[] = []\n\n const packed = patches.map((patch, index) => {\n const one = { ...patch }\n\n for (const column of jsonColumns) {\n const value = one[column.name]\n\n if (typeof value !== 'string') {\n /* 손대지 않은 칸은 이미 구조다. 비운 칸은 비운 채로 간다. */\n continue\n }\n\n if (value.trim() === '') {\n /* 비운 것은 「없앤다」는 뜻이다. 빈 글자로 보내면 칸의 뜻이 흐려진다. */\n one[column.name] = null\n continue\n }\n\n try {\n one[column.name] = JSON.parse(value)\n } catch (error) {\n problems.push(\n i18next.t('text.json-column-unreadable', {\n row: index + 1,\n column: i18next.t(`field.${column.label}`),\n detail: (error as Error).message\n })\n )\n }\n }\n\n return one\n })\n\n return { patches: packed, problems }\n }\n\n /**\n * 파일로 올린 줄들을 이 화면의 마스터로 넣는다.\n *\n * ── 오류보고없이 버리지 않는다 ─────────────────────────────────────────────────\n * 모르는 열 이름, 이름이 없는 줄, 중복된 이름을 **알린다.** 오류보고없이 빼면 사람은 다 올라간 줄 알고,\n * 빠진 것은 나중에 「그 설비가 왜 없지」로 나타난다.\n *\n * ── 하나라도 틀리면 아무것도 안 보낸다 ───────────────────────────────────\n * 절반만 들어가면 무엇이 들어갔는지 사람이 모른다. 파일을 고쳐 다시 올리는 편이 낫다.\n *\n * ── 새로 만들 것과 고칠 것을 이름으로 구분한다 ─────────────────────────────\n * 서버는 `+`(새로)와 `M`(고침, id 필요)을 나눠 받는다. 그러니 **올리기 전에 그 이름들이 이미\n * 있는지 물어야 한다.** 안 물으면 같은 이름이 두 벌 생긴다.\n */\n protected async importHandler(records: any[]) {\n if (!records?.length) {\n notify({ message: i18next.t('text.nothing-to-import'), level: 'info' })\n return\n }\n\n const byHeader = this.headerIndex()\n const reading = readImportRows(records, header => byHeader.get(header))\n\n if (reading.problems.length) {\n /* 열 줄까지만 보인다 — 파일이 통째로 어긋나면 문제도 줄 수만큼 나온다. */\n notify({ message: reading.problems.slice(0, 10).map(problem => this.sayProblem(problem)).join('\\n'), level: 'error' })\n return\n }\n\n const existing = await this.idsByName(reading.rows.map(row => String(row.name)))\n const patches = patchesFor(reading.rows, name => existing.get(name))\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n notify({ message: packed.problems.slice(0, 10).join('\\n'), level: 'error' })\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [Object!]!) {\n ${this.spec.mutation}(patches: $patches) {\n id\n }\n }\n `,\n variables: { patches: packed.patches }\n })\n\n /*\n * 이 앱의 클라이언트는 서버가 거절해도 던지지 않고 `errors` 에 담아 준다. 여기서 보지 않으면\n * 아래의 「올렸다」가 실패한 뒤에도 그대로 뜬다. 사유는 앱의 공통 오류 처리가 이미 띄우므로\n * 여기서 다시 띄우지 않는다 — 같은 말이 두 번 뜬다.\n */\n if (response?.errors?.length) {\n return\n }\n\n const made = patches.filter(patch => patch.cuFlag === '+').length\n\n notify({\n message: i18next.t('text.import-done', { made, changed: patches.length - made }),\n level: 'info'\n })\n\n this.grist.fetch()\n }\n\n /**\n * 파일의 머리글을 이 화면의 열 이름으로 바꾼다.\n *\n * **두 가지를 다 받는다** — 열 이름 그대로(`homeLocation`)와 화면에 보이는 말(`홈 자리`).\n * 사람이 이 화면에서 내려받아 고쳐 올리면 머리글이 화면의 말이고, 다른 시스템에서 뽑아 오면\n * 열 이름이다. 한쪽만 받으면 다른 쪽이 통째로 「모르는 열」이 된다.\n */\n /** 무엇이 잘못됐는지를 사람에게 보일 문장으로 바꾼다. 줄 번호는 엑셀에서 보는 그 번호다. */\n private sayProblem(problem: ImportProblem): string {\n switch (problem.kind) {\n case 'unknown-column':\n return i18next.t('error.import-unknown-column', { line: problem.line, column: problem.column })\n case 'no-name':\n return i18next.t('error.import-no-name', { line: problem.line })\n case 'duplicate-name':\n return i18next.t('error.import-duplicate-name', { line: problem.line, name: problem.name })\n }\n }\n\n private headerIndex(): Map<string, string> {\n const found = new Map<string, string>()\n\n for (const column of this.spec.columns) {\n found.set(column.name, column.name)\n found.set(i18next.t(`field.${column.name}`), column.name)\n }\n\n return found\n }\n\n /** 이 이름들이 이미 있나 — 있으면 그 `id`. 없는 이름은 지도에 없다. */\n private async idsByName(names: string[]): Promise<Map<string, string>> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination) {\n ${this.spec.query}(filters: $filters, pagination: $pagination) {\n items {\n id\n name\n }\n }\n }\n `,\n variables: {\n filters: [{ name: 'name', operator: 'in', value: names }],\n /* 올린 줄 수만큼만 돌아오면 된다. 기본 50으로 두면 51번째부터 새로 만들어 두 벌이 된다. */\n pagination: { page: 1, limit: names.length }\n },\n fetchPolicy: 'no-cache'\n })\n\n return new Map(\n (response.data?.[this.spec.query]?.items ?? []).map((item: any) => [item.name, item.id])\n )\n }\n\n /** 「언제부터」를 묻는다. 사람이 그만두면 `undefined` 이고, 그때는 **아무것도 안 보낸다.** */\n protected askAppliedWhen(changes: AssignmentChange[]): Promise<AppliedAnswer | undefined> {\n return new Promise(resolve => {\n const popup: any = openPopup(\n html`<applied-when-dialog\n .changes=${changes}\n .onAnswer=${(answer: AppliedAnswer) => {\n popup?.close?.()\n resolve(answer)\n }}\n .onCancel=${() => {\n popup?.close?.()\n resolve(undefined)\n }}\n ></applied-when-dialog>`,\n { backdrop: true, size: 'medium', title: i18next.t('title.when-does-this-apply') }\n )\n })\n }\n\n protected async save() {\n const patches = this.grist.dirtyRecords\n\n if (!patches?.length) {\n notify({ message: i18next.t('text.nothing-changed'), level: 'info' })\n return\n }\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n /* 하나도 보내지 않는다. 절반만 저장되면 화면과 저장된 것이 달라진다. */\n notify({ message: packed.problems.join('\\n'), level: 'error' })\n return\n }\n\n /*\n * ── 배정과 선언은 「언제부터」를 묻고 보낸다 ─────────────────────────────\n *\n * 자리 배정 · 근무조 선언 · 부모 관계는 가동률 분모를 정한다. 그래서 값만 바꾸는 것이 아니라\n * **언제부터 그렇게 볼 것인가**를 함께 정해야 하고, 과거를 적으면 지연 기록인지 정정인지까지\n * 갈라야 한다(§`applied-when-dialog`).\n *\n * 안 물어도 저장은 된다 — 그때는 「지금부터」라는 주장이 된다. 다만 **묻지 않으면 사람이 그것을\n * 주장한 줄 모른다.**\n */\n const assignments = assignmentChangesIn(this.spec.axis ?? '', patches)\n const applied = assignments.length ? await this.askAppliedWhen(assignments) : undefined\n\n /* 그만두었으면 아무것도 안 보낸다 — 절반만 저장되면 화면과 저장된 것이 달라진다. */\n if (assignments.length && !applied) {\n return\n }\n\n const response = await client.mutate({\n /*\n * **타입 이름은 `Object` 다.**\n *\n * 서버가 `ScalarObject` 라는 이름으로 선언하지만 스키마에 나가는 이름은 `Object` 이고,\n * `ScalarObject` 로 적으면 「Unknown type」으로 거절된다. 2026-09-01부터 이 자리가 그렇게\n * 적혀 있어 **모든 기준정보 화면의 저장이 안 됐다.** 화면은 오류를 그대로 보여 주지도\n * 않았다 — 이 앱의 클라이언트는 서버가 거절해도 던지지 않는다.\n */\n mutation: gql`\n mutation ($patches: [Object!]!, $applied: Object) {\n ${this.spec.mutation}(patches: $patches, applied: $applied) {\n id\n name\n }\n }\n `,\n variables: { patches: packed.patches, applied }\n })\n\n /*\n * 이 클라이언트는 서버가 거절해도 던지지 않는다 — 오류는 `response.errors` 에 담겨 온다.\n * 보지 않으면 저장에 실패해도 화면이 목록을 다시 읽어 「저장된 것처럼」 보인다.\n * 사유 자체는 앱의 공통 오류 처리가 띄운다.\n */\n if (response.errors?.length) {\n return\n }\n\n /* 지운 행은 사라지지 않고 유효 기간의 끝이 채워진다. 말해 주지 않으면 저장이 안 된 것으로 읽는다. */\n if (packed.patches.some((patch: any) => String(patch.cuFlag).toUpperCase() === '-')) {\n notify({ message: i18next.t('text.retired-not-deleted'), level: 'info' })\n }\n\n this.grist.fetch()\n }\n}\n"]}