@things-factory/worklist 10.1.40 → 10.1.42
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/components/activity-starter-form.js +1 -1
- package/dist-client/components/activity-starter-form.js.map +1 -1
- package/dist-client/pages/work-door/door-sentence.d.ts +63 -0
- package/dist-client/pages/work-door/door-sentence.js +109 -0
- package/dist-client/pages/work-door/door-sentence.js.map +1 -0
- package/dist-client/pages/work-door/door-words.d.ts +7 -0
- package/dist-client/pages/work-door/door-words.js +30 -0
- package/dist-client/pages/work-door/door-words.js.map +1 -1
- package/dist-client/pages/work-door/work-doors-page.d.ts +1 -1
- package/dist-client/pages/work-door/work-doors-page.js +49 -17
- package/dist-client/pages/work-door/work-doors-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/index.d.ts +3 -3
- package/dist-server/service/work-door/work-door-declaration.d.ts +11 -5
- package/dist-server/service/work-door/work-door-declaration.js +5 -3
- package/dist-server/service/work-door/work-door-declaration.js.map +1 -1
- package/dist-server/service/work-door/work-door-resolver.js +1 -1
- package/dist-server/service/work-door/work-door-resolver.js.map +1 -1
- package/dist-server/service/work-door/work-door-service.js +3 -14
- package/dist-server/service/work-door/work-door-service.js.map +1 -1
- package/dist-server/service/work-door/work-door-type.d.ts +43 -6
- package/dist-server/service/work-door/work-door-type.js +102 -26
- package/dist-server/service/work-door/work-door-type.js.map +1 -1
- package/dist-server/service/work-door/work-door.d.ts +65 -16
- package/dist-server/service/work-door/work-door.js +53 -90
- package/dist-server/service/work-door/work-door.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/tests/work-door-door-words.test.ts +35 -1
- package/tests/work-door-not-used-refuse.test.ts +6 -6
- package/tests/work-door-sentence.test.ts +199 -0
- package/tests/work-door.test.ts +63 -56
- package/translations/en.json +26 -1
- package/translations/ja.json +26 -1
- package/translations/ko.json +26 -1
- package/translations/ms.json +26 -1
- package/translations/zh.json +26 -1
|
@@ -27,7 +27,7 @@ let ActivityStarterForm = class ActivityStarterForm extends localize(i18next)(Li
|
|
|
27
27
|
display: flex;
|
|
28
28
|
flex-direction: column;
|
|
29
29
|
|
|
30
|
-
color: var(--md-sys-color-on-
|
|
30
|
+
color: var(--md-sys-color-on-surface);
|
|
31
31
|
background-color: var(--md-sys-color-surface);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-starter-form.js","sourceRoot":"","sources":["../../client/components/activity-starter-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/textfield/outlined-text-field.js'\nimport '@material/web/textfield/filled-text-field.js'\nimport '@operato/property-editor/ox-properties-dynamic-view.js'\nimport '@things-factory/organization'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { navigate } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { ranMutation } from './say-outcome.js'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('activity-starter-form')\nexport class ActivityStarterForm extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--md-sys-color-on-s);\n background-color: var(--md-sys-color-surface);\n }\n\n content {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: var(--spacing-medium);\n gap: var(--spacing-medium);\n overflow: auto;\n }\n\n content > div {\n display: flex;\n flex-direction: column;\n }\n\n content label {\n font: var(--label-font);\n }\n content input,\n content textarea {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n padding: var(--input-padding);\n margin: var(--input-margin);\n font: var(--input-font);\n }\n\n content [desc] {\n font-size: 0.9em;\n }\n\n #description {\n flex: 1;\n }\n `\n ]\n\n @property({ type: String }) activityId?: string\n\n @state() activity: any = {}\n @state() activityInstance: any = {}\n\n render() {\n var { thumbnail, startingType } = this.activity || {}\n var { name, description } = this.activityInstance\n\n return html`\n <content>\n <div desc>\n <span>${i18next.t('text.activity-draft-how')}</span>\n <span\n >${i18next.t('text.activity-draft-where', {\n action: i18next.t(startingType == 'issue' ? 'button.issue' : 'button.post')\n })}</span\n >\n </div>\n\n <md-outlined-text-field\n id=\"name\"\n type=\"text\"\n label=${String(i18next.t('label.title'))}\n .value=${name || this.activity?.name || ''}\n @change=${e => (this.activityInstance.name = e.currentTarget.value)}\n >\n </md-outlined-text-field>\n\n <md-filled-text-field\n id=\"description\"\n type=\"textarea\"\n label=${String(i18next.t('label.description'))}\n resize=\"none\"\n .value=${description || this.activity?.description || ''}\n @change=${e => {\n this.activityInstance.description = e.currentTarget.value\n }}\n >\n </md-filled-text-field>\n </content>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => history.back()}><md-icon>close</md-icon>${i18next.t('button.close')}</button>\n <button @click=${this.draftActivityInstance.bind(this)} done>\n <md-icon>save</md-icon>${i18next.t('button.draft')}\n </button>\n </div>\n `\n }\n\n firstUpdated() {\n this.fetchActivity(this.activityId)\n }\n\n async fetchActivity(id) {\n const response = await client.query({\n query: gql`\n query activity($id: String!) {\n activity(id: $id) {\n id\n name\n description\n state\n multiple\n startingType\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n participation\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n thumbnail\n model {\n name\n description\n active\n tag\n inout\n type\n unit\n options\n quantifier\n spec\n }\n }\n }\n `,\n variables: {\n id\n }\n })\n\n this.activity = response.data.activity\n }\n\n async draftActivityInstance() {\n var { id: activityId } = this.activity\n var { id, name, description, priority, threadsMin, threadsMax, dueAt, input, assignees, approvalLine } =\n this.activityInstance\n\n name ||= this.activity.name\n description ||= this.activity.description\n assignees ||= this.activity.assignees\n approvalLine ||= this.activity.approvalLine\n\n var activityInstance = {\n id,\n activityId,\n name,\n description,\n priority,\n threadsMax,\n threadsMin,\n dueAt,\n input,\n assignees,\n approvalLine\n }\n\n const outcome = await ranMutation(() => client.mutate({\n mutation: gql`\n mutation ($activityInstance: ActivityInstanceDraft!) {\n draftActivityInstance(activityInstance: $activityInstance) {\n id\n name\n description\n input\n output\n state\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n participation\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n threadsMin\n threadsMax\n }\n }\n `,\n variables: {\n activityInstance\n }\n }))\n\n if (!outcome.ok) { return\n }\n\n this.activityInstance = outcome.data.draftActivityInstance\n notify({\n message: i18next.t('text.info_x_successfully', {\n x: `${i18next.t('label.activity-instance')} ${i18next.t('text.draft')}`\n })\n })\n\n this.openActivityInstanceStart(this.activityInstance)\n }\n\n openActivityInstanceStart(activityInstance) {\n navigate(`activity-instance-start/${activityInstance.id}?title=${activityInstance.name}`)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"activity-starter-form.js","sourceRoot":"","sources":["../../client/components/activity-starter-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/textfield/outlined-text-field.js'\nimport '@material/web/textfield/filled-text-field.js'\nimport '@operato/property-editor/ox-properties-dynamic-view.js'\nimport '@things-factory/organization'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { navigate } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { ranMutation } from './say-outcome.js'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('activity-starter-form')\nexport class ActivityStarterForm extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface);\n }\n\n content {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: var(--spacing-medium);\n gap: var(--spacing-medium);\n overflow: auto;\n }\n\n content > div {\n display: flex;\n flex-direction: column;\n }\n\n content label {\n font: var(--label-font);\n }\n content input,\n content textarea {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n padding: var(--input-padding);\n margin: var(--input-margin);\n font: var(--input-font);\n }\n\n content [desc] {\n font-size: 0.9em;\n }\n\n #description {\n flex: 1;\n }\n `\n ]\n\n @property({ type: String }) activityId?: string\n\n @state() activity: any = {}\n @state() activityInstance: any = {}\n\n render() {\n var { thumbnail, startingType } = this.activity || {}\n var { name, description } = this.activityInstance\n\n return html`\n <content>\n <div desc>\n <span>${i18next.t('text.activity-draft-how')}</span>\n <span\n >${i18next.t('text.activity-draft-where', {\n action: i18next.t(startingType == 'issue' ? 'button.issue' : 'button.post')\n })}</span\n >\n </div>\n\n <md-outlined-text-field\n id=\"name\"\n type=\"text\"\n label=${String(i18next.t('label.title'))}\n .value=${name || this.activity?.name || ''}\n @change=${e => (this.activityInstance.name = e.currentTarget.value)}\n >\n </md-outlined-text-field>\n\n <md-filled-text-field\n id=\"description\"\n type=\"textarea\"\n label=${String(i18next.t('label.description'))}\n resize=\"none\"\n .value=${description || this.activity?.description || ''}\n @change=${e => {\n this.activityInstance.description = e.currentTarget.value\n }}\n >\n </md-filled-text-field>\n </content>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => history.back()}><md-icon>close</md-icon>${i18next.t('button.close')}</button>\n <button @click=${this.draftActivityInstance.bind(this)} done>\n <md-icon>save</md-icon>${i18next.t('button.draft')}\n </button>\n </div>\n `\n }\n\n firstUpdated() {\n this.fetchActivity(this.activityId)\n }\n\n async fetchActivity(id) {\n const response = await client.query({\n query: gql`\n query activity($id: String!) {\n activity(id: $id) {\n id\n name\n description\n state\n multiple\n startingType\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n participation\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n thumbnail\n model {\n name\n description\n active\n tag\n inout\n type\n unit\n options\n quantifier\n spec\n }\n }\n }\n `,\n variables: {\n id\n }\n })\n\n this.activity = response.data.activity\n }\n\n async draftActivityInstance() {\n var { id: activityId } = this.activity\n var { id, name, description, priority, threadsMin, threadsMax, dueAt, input, assignees, approvalLine } =\n this.activityInstance\n\n name ||= this.activity.name\n description ||= this.activity.description\n assignees ||= this.activity.assignees\n approvalLine ||= this.activity.approvalLine\n\n var activityInstance = {\n id,\n activityId,\n name,\n description,\n priority,\n threadsMax,\n threadsMin,\n dueAt,\n input,\n assignees,\n approvalLine\n }\n\n const outcome = await ranMutation(() => client.mutate({\n mutation: gql`\n mutation ($activityInstance: ActivityInstanceDraft!) {\n draftActivityInstance(activityInstance: $activityInstance) {\n id\n name\n description\n input\n output\n state\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n participation\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n threadsMin\n threadsMax\n }\n }\n `,\n variables: {\n activityInstance\n }\n }))\n\n if (!outcome.ok) { return\n }\n\n this.activityInstance = outcome.data.draftActivityInstance\n notify({\n message: i18next.t('text.info_x_successfully', {\n x: `${i18next.t('label.activity-instance')} ${i18next.t('text.draft')}`\n })\n })\n\n this.openActivityInstanceStart(this.activityInstance)\n }\n\n openActivityInstanceStart(activityInstance) {\n navigate(`activity-instance-start/${activityInstance.id}?title=${activityInstance.name}`)\n }\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sentences of the work-doors screen, **composed here from facts and translations.**
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ── Why the screen writes them ────────────────────────────────────────────
|
|
6
|
+
* The server used to send each row's sentence and the summary already written, in Korean. An English,
|
|
7
|
+
* Japanese, Chinese or Malay reader got Korean, and the screen had no way to draw a row differently
|
|
8
|
+
* without reading the sentence back apart. The server now sends facts (`WorkDoorType`) and every
|
|
9
|
+
* sentence is a translation key here.
|
|
10
|
+
*
|
|
11
|
+
* ── The empty line is the door's declaration, not one sentence ────────────
|
|
12
|
+
* What an empty approval line does is declared per door (ADR-0077 decision 1). `run` lets the raiser
|
|
13
|
+
* approve their own; `refuse` refuses the raise; `as-established` turns on how the row stood. The
|
|
14
|
+
* old sentence said "the raiser approves their own" for every door, and four refusing doors on :4000
|
|
15
|
+
* carried it (2026-09-24). A door with no declaration says nothing about the consequence rather than
|
|
16
|
+
* guessing one.
|
|
17
|
+
*
|
|
18
|
+
* Pure: `t` is passed in, so a test can load the real translation files and read the sentences.
|
|
19
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
*/
|
|
21
|
+
/** The row as this screen holds it, after `door-words` has moved the wire spellings. */
|
|
22
|
+
export type DoorFacts = {
|
|
23
|
+
name: string;
|
|
24
|
+
labelKey: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
standing: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
approvers: number;
|
|
29
|
+
whenNoLine?: string;
|
|
30
|
+
whenNoLineBecause?: string;
|
|
31
|
+
followsLabelKey?: string;
|
|
32
|
+
assigneeRole?: string;
|
|
33
|
+
notUsedBecause?: string;
|
|
34
|
+
};
|
|
35
|
+
export type DoorCounts = {
|
|
36
|
+
on: number;
|
|
37
|
+
off: number;
|
|
38
|
+
missing: number;
|
|
39
|
+
notUsed: number;
|
|
40
|
+
todo: number;
|
|
41
|
+
};
|
|
42
|
+
type T = (key: string, options?: Record<string, unknown>) => string;
|
|
43
|
+
/**
|
|
44
|
+
* A door's name. A label key without a translation falls back to the form name - a name a person can
|
|
45
|
+
* search for - rather than printing the key.
|
|
46
|
+
*/
|
|
47
|
+
export declare function doorLabel(t: T, door: {
|
|
48
|
+
labelKey: string;
|
|
49
|
+
name: string;
|
|
50
|
+
}): string;
|
|
51
|
+
/** Why an approval door will not run on an empty line, as a short phrase. Empty when not declared. */
|
|
52
|
+
export declare function whyItRefuses(t: T, door: Pick<DoorFacts, 'whenNoLineBecause'>): string;
|
|
53
|
+
/** What this row says about its standing. */
|
|
54
|
+
export declare function doorSentence(t: T, door: DoorFacts): string;
|
|
55
|
+
/**
|
|
56
|
+
* What the door does when its line is empty, as a short tag - shown on rows whose sentence does not
|
|
57
|
+
* already say it (ADR-0077 decision 3). An empty-line row already says it; a not-used or missing row
|
|
58
|
+
* does not go through the line at all.
|
|
59
|
+
*/
|
|
60
|
+
export declare function whenNoLineTag(t: T, door: DoorFacts): string;
|
|
61
|
+
/** The whole plant in one sentence - a list alone makes the reader count rows. */
|
|
62
|
+
export declare function doorsSummary(t: T, counts: DoorCounts): string;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sentences of the work-doors screen, **composed here from facts and translations.**
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ── Why the screen writes them ────────────────────────────────────────────
|
|
6
|
+
* The server used to send each row's sentence and the summary already written, in Korean. An English,
|
|
7
|
+
* Japanese, Chinese or Malay reader got Korean, and the screen had no way to draw a row differently
|
|
8
|
+
* without reading the sentence back apart. The server now sends facts (`WorkDoorType`) and every
|
|
9
|
+
* sentence is a translation key here.
|
|
10
|
+
*
|
|
11
|
+
* ── The empty line is the door's declaration, not one sentence ────────────
|
|
12
|
+
* What an empty approval line does is declared per door (ADR-0077 decision 1). `run` lets the raiser
|
|
13
|
+
* approve their own; `refuse` refuses the raise; `as-established` turns on how the row stood. The
|
|
14
|
+
* old sentence said "the raiser approves their own" for every door, and four refusing doors on :4000
|
|
15
|
+
* carried it (2026-09-24). A door with no declaration says nothing about the consequence rather than
|
|
16
|
+
* guessing one.
|
|
17
|
+
*
|
|
18
|
+
* Pure: `t` is passed in, so a test can load the real translation files and read the sentences.
|
|
19
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
*/
|
|
21
|
+
/*
|
|
22
|
+
* Values reach the sentence through lit, which escapes them. i18next escaping on top would print
|
|
23
|
+
* `&` for a plant's own "we do not use this" note.
|
|
24
|
+
*/
|
|
25
|
+
const RAW = { interpolation: { escapeValue: false } };
|
|
26
|
+
/**
|
|
27
|
+
* A door's name. A label key without a translation falls back to the form name - a name a person can
|
|
28
|
+
* search for - rather than printing the key.
|
|
29
|
+
*/
|
|
30
|
+
export function doorLabel(t, door) {
|
|
31
|
+
return String(t(door.labelKey, { defaultValue: door.name }));
|
|
32
|
+
}
|
|
33
|
+
/** Why an approval door will not run on an empty line, as a short phrase. Empty when not declared. */
|
|
34
|
+
export function whyItRefuses(t, door) {
|
|
35
|
+
return door.whenNoLineBecause ? String(t(`text.door-refuses-because-${door.whenNoLineBecause}`)) : '';
|
|
36
|
+
}
|
|
37
|
+
/** What this row says about its standing. */
|
|
38
|
+
export function doorSentence(t, door) {
|
|
39
|
+
const label = doorLabel(t, door);
|
|
40
|
+
const said = [String(t(sentenceKey(door), { ...RAW, label, ...sentenceParams(t, door) }))];
|
|
41
|
+
if (door.followsLabelKey && door.standing !== 'not-used' && door.standing !== 'missing') {
|
|
42
|
+
said.push(String(t('text.door-because-follows', { ...RAW, label: doorLabel(t, { labelKey: door.followsLabelKey, name: '' }) })));
|
|
43
|
+
}
|
|
44
|
+
return said.join(' ');
|
|
45
|
+
}
|
|
46
|
+
function sentenceKey(door) {
|
|
47
|
+
if (door.reason === 'line-empty') {
|
|
48
|
+
return door.whenNoLine ? `text.door-because-line-empty-${door.whenNoLine}` : 'text.door-because-line-empty';
|
|
49
|
+
}
|
|
50
|
+
return `text.door-because-${door.reason}`;
|
|
51
|
+
}
|
|
52
|
+
function sentenceParams(t, door) {
|
|
53
|
+
switch (door.reason) {
|
|
54
|
+
case 'not-used':
|
|
55
|
+
return { reason: door.notUsedBecause ?? '' };
|
|
56
|
+
case 'line-filled':
|
|
57
|
+
return { count: door.approvers };
|
|
58
|
+
case 'assignee-bound':
|
|
59
|
+
return { role: door.assigneeRole ?? '' };
|
|
60
|
+
case 'line-empty':
|
|
61
|
+
return { why: whyItRefuses(t, door) };
|
|
62
|
+
default:
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What the door does when its line is empty, as a short tag - shown on rows whose sentence does not
|
|
68
|
+
* already say it (ADR-0077 decision 3). An empty-line row already says it; a not-used or missing row
|
|
69
|
+
* does not go through the line at all.
|
|
70
|
+
*/
|
|
71
|
+
export function whenNoLineTag(t, door) {
|
|
72
|
+
if (door.kind !== 'approval' || !door.whenNoLine || door.reason === 'line-empty') {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
if (door.standing !== 'on' && door.standing !== 'off') {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
const why = whyItRefuses(t, door);
|
|
79
|
+
const tag = String(t(`text.door-when-no-line-${door.whenNoLine}`));
|
|
80
|
+
return door.whenNoLine === 'refuse' && why ? `${tag} (${why})` : tag;
|
|
81
|
+
}
|
|
82
|
+
/** The whole plant in one sentence - a list alone makes the reader count rows. */
|
|
83
|
+
export function doorsSummary(t, counts) {
|
|
84
|
+
const total = counts.on + counts.off + counts.missing + counts.notUsed;
|
|
85
|
+
if (!total) {
|
|
86
|
+
return String(t('text.work-doors-summary-none'));
|
|
87
|
+
}
|
|
88
|
+
/*
|
|
89
|
+
* Only `off + missing` is counted, so that zero means done. Counting the doors a plant opted out of
|
|
90
|
+
* would leave the number standing in a plant that filled everything, and the screen would nag.
|
|
91
|
+
*/
|
|
92
|
+
if (!counts.todo) {
|
|
93
|
+
return counts.notUsed
|
|
94
|
+
? String(t('text.work-doors-summary-done-not-used', { on: counts.on, notUsed: counts.notUsed }))
|
|
95
|
+
: String(t('text.work-doors-summary-all-on', { count: total }));
|
|
96
|
+
}
|
|
97
|
+
const said = [String(t('text.work-doors-summary-todo', { count: counts.todo }))];
|
|
98
|
+
if (counts.off) {
|
|
99
|
+
said.push(String(t('text.work-doors-summary-off', { count: counts.off })));
|
|
100
|
+
}
|
|
101
|
+
if (counts.missing) {
|
|
102
|
+
said.push(String(t('text.work-doors-summary-missing', { count: counts.missing })));
|
|
103
|
+
}
|
|
104
|
+
if (counts.notUsed) {
|
|
105
|
+
said.push(String(t('text.work-doors-summary-not-used', { count: counts.notUsed })));
|
|
106
|
+
}
|
|
107
|
+
return said.join(' ');
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=door-sentence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"door-sentence.js","sourceRoot":"","sources":["../../../client/pages/work-door/door-sentence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The sentences of the work-doors screen, **composed here from facts and translations.**\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── Why the screen writes them ────────────────────────────────────────────\n * The server used to send each row's sentence and the summary already written, in Korean. An English,\n * Japanese, Chinese or Malay reader got Korean, and the screen had no way to draw a row differently\n * without reading the sentence back apart. The server now sends facts (`WorkDoorType`) and every\n * sentence is a translation key here.\n *\n * ── The empty line is the door's declaration, not one sentence ────────────\n * What an empty approval line does is declared per door (ADR-0077 decision 1). `run` lets the raiser\n * approve their own; `refuse` refuses the raise; `as-established` turns on how the row stood. The\n * old sentence said \"the raiser approves their own\" for every door, and four refusing doors on :4000\n * carried it (2026-09-24). A door with no declaration says nothing about the consequence rather than\n * guessing one.\n *\n * Pure: `t` is passed in, so a test can load the real translation files and read the sentences.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** The row as this screen holds it, after `door-words` has moved the wire spellings. */\nexport type DoorFacts = {\n name: string\n labelKey: string\n kind: string\n standing: string\n reason: string\n approvers: number\n whenNoLine?: string\n whenNoLineBecause?: string\n followsLabelKey?: string\n assigneeRole?: string\n notUsedBecause?: string\n}\n\nexport type DoorCounts = { on: number; off: number; missing: number; notUsed: number; todo: number }\n\ntype T = (key: string, options?: Record<string, unknown>) => string\n\n/*\n * Values reach the sentence through lit, which escapes them. i18next escaping on top would print\n * `&` for a plant's own \"we do not use this\" note.\n */\nconst RAW = { interpolation: { escapeValue: false } }\n\n/**\n * A door's name. A label key without a translation falls back to the form name - a name a person can\n * search for - rather than printing the key.\n */\nexport function doorLabel(t: T, door: { labelKey: string; name: string }): string {\n return String(t(door.labelKey, { defaultValue: door.name }))\n}\n\n/** Why an approval door will not run on an empty line, as a short phrase. Empty when not declared. */\nexport function whyItRefuses(t: T, door: Pick<DoorFacts, 'whenNoLineBecause'>): string {\n return door.whenNoLineBecause ? String(t(`text.door-refuses-because-${door.whenNoLineBecause}`)) : ''\n}\n\n/** What this row says about its standing. */\nexport function doorSentence(t: T, door: DoorFacts): string {\n const label = doorLabel(t, door)\n const said = [String(t(sentenceKey(door), { ...RAW, label, ...sentenceParams(t, door) }))]\n\n if (door.followsLabelKey && door.standing !== 'not-used' && door.standing !== 'missing') {\n said.push(String(t('text.door-because-follows', { ...RAW, label: doorLabel(t, { labelKey: door.followsLabelKey, name: '' }) })))\n }\n\n return said.join(' ')\n}\n\nfunction sentenceKey(door: DoorFacts): string {\n if (door.reason === 'line-empty') {\n return door.whenNoLine ? `text.door-because-line-empty-${door.whenNoLine}` : 'text.door-because-line-empty'\n }\n\n return `text.door-because-${door.reason}`\n}\n\nfunction sentenceParams(t: T, door: DoorFacts): Record<string, unknown> {\n switch (door.reason) {\n case 'not-used':\n return { reason: door.notUsedBecause ?? '' }\n case 'line-filled':\n return { count: door.approvers }\n case 'assignee-bound':\n return { role: door.assigneeRole ?? '' }\n case 'line-empty':\n return { why: whyItRefuses(t, door) }\n default:\n return {}\n }\n}\n\n/**\n * What the door does when its line is empty, as a short tag - shown on rows whose sentence does not\n * already say it (ADR-0077 decision 3). An empty-line row already says it; a not-used or missing row\n * does not go through the line at all.\n */\nexport function whenNoLineTag(t: T, door: DoorFacts): string {\n if (door.kind !== 'approval' || !door.whenNoLine || door.reason === 'line-empty') {\n return ''\n }\n\n if (door.standing !== 'on' && door.standing !== 'off') {\n return ''\n }\n\n const why = whyItRefuses(t, door)\n const tag = String(t(`text.door-when-no-line-${door.whenNoLine}`))\n\n return door.whenNoLine === 'refuse' && why ? `${tag} (${why})` : tag\n}\n\n/** The whole plant in one sentence - a list alone makes the reader count rows. */\nexport function doorsSummary(t: T, counts: DoorCounts): string {\n const total = counts.on + counts.off + counts.missing + counts.notUsed\n\n if (!total) {\n return String(t('text.work-doors-summary-none'))\n }\n\n /*\n * Only `off + missing` is counted, so that zero means done. Counting the doors a plant opted out of\n * would leave the number standing in a plant that filled everything, and the screen would nag.\n */\n if (!counts.todo) {\n return counts.notUsed\n ? String(t('text.work-doors-summary-done-not-used', { on: counts.on, notUsed: counts.notUsed }))\n : String(t('text.work-doors-summary-all-on', { count: total }))\n }\n\n const said = [String(t('text.work-doors-summary-todo', { count: counts.todo }))]\n\n if (counts.off) {\n said.push(String(t('text.work-doors-summary-off', { count: counts.off })))\n }\n\n if (counts.missing) {\n said.push(String(t('text.work-doors-summary-missing', { count: counts.missing })))\n }\n\n if (counts.notUsed) {\n said.push(String(t('text.work-doors-summary-not-used', { count: counts.notUsed })))\n }\n\n return said.join(' ')\n}\n"]}
|
|
@@ -36,6 +36,13 @@
|
|
|
36
36
|
/** 전선의 철자 → 판단부의 낱말. 키가 전선의 것이라 대문자다 — 일부러다. */
|
|
37
37
|
export declare const DOOR_STANDING_WORD: Record<string, string>;
|
|
38
38
|
export declare const DOOR_KIND_WORD: Record<string, string>;
|
|
39
|
+
export declare const DOOR_REASON_WORD: Record<string, string>;
|
|
40
|
+
export declare const WHEN_NO_LINE_WORD: Record<string, string>;
|
|
41
|
+
export declare const WHEN_NO_LINE_BECAUSE_WORD: Record<string, string>;
|
|
39
42
|
/** 모르는 철자면 `undefined` — 부르는 쪽이 「모른다」를 그리게 한다. */
|
|
40
43
|
export declare function standingWord(wire: string): string | undefined;
|
|
41
44
|
export declare function kindWord(wire: string): string | undefined;
|
|
45
|
+
export declare function reasonWord(wire: string): string | undefined;
|
|
46
|
+
/** 비어 있으면(배정 문) 비어 있는 채로 — 선언이 없는 것을 `run` 으로 기울이지 않는다. */
|
|
47
|
+
export declare function whenNoLineWord(wire?: string | null): string | undefined;
|
|
48
|
+
export declare function whenNoLineBecauseWord(wire?: string | null): string | undefined;
|
|
@@ -44,6 +44,26 @@ export const DOOR_KIND_WORD = {
|
|
|
44
44
|
Approval: 'approval',
|
|
45
45
|
Assignment: 'assignment'
|
|
46
46
|
};
|
|
47
|
+
export const DOOR_REASON_WORD = {
|
|
48
|
+
NotUsed: 'not-used',
|
|
49
|
+
Missing: 'missing',
|
|
50
|
+
LineEmpty: 'line-empty',
|
|
51
|
+
RoleEmpty: 'role-empty',
|
|
52
|
+
PersonAbsent: 'person-absent',
|
|
53
|
+
NoAssignee: 'no-assignee',
|
|
54
|
+
LineFilled: 'line-filled',
|
|
55
|
+
AssigneeBound: 'assignee-bound'
|
|
56
|
+
};
|
|
57
|
+
export const WHEN_NO_LINE_WORD = {
|
|
58
|
+
Run: 'run',
|
|
59
|
+
Refuse: 'refuse',
|
|
60
|
+
AsEstablished: 'as-established'
|
|
61
|
+
};
|
|
62
|
+
export const WHEN_NO_LINE_BECAUSE_WORD = {
|
|
63
|
+
Irreversible: 'irreversible',
|
|
64
|
+
ApprovalIsTheAct: 'approval-is-the-act',
|
|
65
|
+
LowersAControl: 'lowers-a-control'
|
|
66
|
+
};
|
|
47
67
|
/** 모르는 철자면 `undefined` — 부르는 쪽이 「모른다」를 그리게 한다. */
|
|
48
68
|
export function standingWord(wire) {
|
|
49
69
|
return DOOR_STANDING_WORD[wire];
|
|
@@ -51,4 +71,14 @@ export function standingWord(wire) {
|
|
|
51
71
|
export function kindWord(wire) {
|
|
52
72
|
return DOOR_KIND_WORD[wire];
|
|
53
73
|
}
|
|
74
|
+
export function reasonWord(wire) {
|
|
75
|
+
return DOOR_REASON_WORD[wire];
|
|
76
|
+
}
|
|
77
|
+
/** 비어 있으면(배정 문) 비어 있는 채로 — 선언이 없는 것을 `run` 으로 기울이지 않는다. */
|
|
78
|
+
export function whenNoLineWord(wire) {
|
|
79
|
+
return wire ? WHEN_NO_LINE_WORD[wire] : undefined;
|
|
80
|
+
}
|
|
81
|
+
export function whenNoLineBecauseWord(wire) {
|
|
82
|
+
return wire ? WHEN_NO_LINE_BECAUSE_WORD[wire] : undefined;
|
|
83
|
+
}
|
|
54
84
|
//# sourceMappingURL=door-words.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"door-words.js","sourceRoot":"","sources":["../../../client/pages/work-door/door-words.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,KAAK;IACV,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,UAAU;CACpB,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;CACzB,CAAA;AAED,kDAAkD;AAClD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,cAAc,CAAC,IAAI,CAAC,CAAA;AAC7B,CAAC","sourcesContent":["/**\n * 전선으로 오는 문 상태를 **이 화면의 낱말로** 옮긴다 — 경계 한 곳에서.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 왜 옮겨야 하나 ────────────────────────────────────────────────────────\n * `type-graphql` 의 `registerEnumType` 은 **멤버 이름**을 전선에 싣는다. 값이 아니다.\n *\n * ```ts\n * export enum DoorStandingValue {\n * Off = 'off' // ← 전선으로 가는 것은 'Off' 다\n * }\n * ```\n *\n * 판단부(`work-door.ts`)의 낱말은 `off` 이고, i18n 키도 CSS 클래스도 그 낱말로 적혀 있다. 그래서\n * 옮기지 않으면 **둘 다 조용히 어긋난다.**\n *\n * ```\n * [확인함] 2026-09-12 실행 중인 화면 (MIRATEK)\n * 전선 \"Off\" · \"Approval\"\n * i18n text.door-standing-Off — 없는 키라 화면에 키 글자가 그대로\n * CSS .mark.Off 안 맞음 → 상태 색이 안 걸리고 물려받은 글자색으로 그려짐\n * ```\n *\n * 상태 색이 한 번도 안 나왔고, 열두 줄에 한국어 대신 키가 적혀 있었다. 서버도 시험도 전부 초록이다 —\n * **클라이언트만 조용히 틀리는** 부류다.\n *\n * ── 왜 서버 쪽 철자를 안 고치나 ───────────────────────────────────────────\n * `not-used` 는 enum 멤버 이름이 될 수 없다(붙임표). 그래서 어떤 철자를 골라도 한쪽은 옮겨야 하고,\n * 옮기는 자리를 **하나**로 두는 것이 답이다. 화면이 받는 자리가 그 하나다.\n *\n * ── 모르는 값을 지어내지 않는다 ───────────────────────────────────────────\n * 새 상태가 생겼는데 여기 안 적히면 `undefined` 다. 화면은 그때 받은 글자를 그대로 보이고 색을 안\n * 칠한다 — 「모른다」가 보여야 이 표를 고칠 수 있다. `off` 로 기울이면 새 상태가 조용히 묻힌다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** 전선의 철자 → 판단부의 낱말. 키가 전선의 것이라 대문자다 — 일부러다. */\nexport const DOOR_STANDING_WORD: Record<string, string> = {\n On: 'on',\n Off: 'off',\n Missing: 'missing',\n NotUsed: 'not-used'\n}\n\nexport const DOOR_KIND_WORD: Record<string, string> = {\n Approval: 'approval',\n Assignment: 'assignment'\n}\n\n/** 모르는 철자면 `undefined` — 부르는 쪽이 「모른다」를 그리게 한다. */\nexport function standingWord(wire: string): string | undefined {\n return DOOR_STANDING_WORD[wire]\n}\n\nexport function kindWord(wire: string): string | undefined {\n return DOOR_KIND_WORD[wire]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"door-words.js","sourceRoot":"","sources":["../../../client/pages/work-door/door-words.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,KAAK;IACV,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,UAAU;CACpB,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;CACzB,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,YAAY;IACvB,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE,aAAa;IACzB,UAAU,EAAE,aAAa;IACzB,aAAa,EAAE,gBAAgB;CAChC,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAA2B;IACvD,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,gBAAgB;CAChC,CAAA;AAED,MAAM,CAAC,MAAM,yBAAyB,GAA2B;IAC/D,YAAY,EAAE,cAAc;IAC5B,gBAAgB,EAAE,qBAAqB;IACvC,cAAc,EAAE,kBAAkB;CACnC,CAAA;AAED,kDAAkD;AAClD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,cAAc,CAAC,IAAI,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,OAAO,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAoB;IACxD,OAAO,IAAI,CAAC,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AAC3D,CAAC","sourcesContent":["/**\n * 전선으로 오는 문 상태를 **이 화면의 낱말로** 옮긴다 — 경계 한 곳에서.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 왜 옮겨야 하나 ────────────────────────────────────────────────────────\n * `type-graphql` 의 `registerEnumType` 은 **멤버 이름**을 전선에 싣는다. 값이 아니다.\n *\n * ```ts\n * export enum DoorStandingValue {\n * Off = 'off' // ← 전선으로 가는 것은 'Off' 다\n * }\n * ```\n *\n * 판단부(`work-door.ts`)의 낱말은 `off` 이고, i18n 키도 CSS 클래스도 그 낱말로 적혀 있다. 그래서\n * 옮기지 않으면 **둘 다 조용히 어긋난다.**\n *\n * ```\n * [확인함] 2026-09-12 실행 중인 화면 (MIRATEK)\n * 전선 \"Off\" · \"Approval\"\n * i18n text.door-standing-Off — 없는 키라 화면에 키 글자가 그대로\n * CSS .mark.Off 안 맞음 → 상태 색이 안 걸리고 물려받은 글자색으로 그려짐\n * ```\n *\n * 상태 색이 한 번도 안 나왔고, 열두 줄에 한국어 대신 키가 적혀 있었다. 서버도 시험도 전부 초록이다 —\n * **클라이언트만 조용히 틀리는** 부류다.\n *\n * ── 왜 서버 쪽 철자를 안 고치나 ───────────────────────────────────────────\n * `not-used` 는 enum 멤버 이름이 될 수 없다(붙임표). 그래서 어떤 철자를 골라도 한쪽은 옮겨야 하고,\n * 옮기는 자리를 **하나**로 두는 것이 답이다. 화면이 받는 자리가 그 하나다.\n *\n * ── 모르는 값을 지어내지 않는다 ───────────────────────────────────────────\n * 새 상태가 생겼는데 여기 안 적히면 `undefined` 다. 화면은 그때 받은 글자를 그대로 보이고 색을 안\n * 칠한다 — 「모른다」가 보여야 이 표를 고칠 수 있다. `off` 로 기울이면 새 상태가 조용히 묻힌다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** 전선의 철자 → 판단부의 낱말. 키가 전선의 것이라 대문자다 — 일부러다. */\nexport const DOOR_STANDING_WORD: Record<string, string> = {\n On: 'on',\n Off: 'off',\n Missing: 'missing',\n NotUsed: 'not-used'\n}\n\nexport const DOOR_KIND_WORD: Record<string, string> = {\n Approval: 'approval',\n Assignment: 'assignment'\n}\n\nexport const DOOR_REASON_WORD: Record<string, string> = {\n NotUsed: 'not-used',\n Missing: 'missing',\n LineEmpty: 'line-empty',\n RoleEmpty: 'role-empty',\n PersonAbsent: 'person-absent',\n NoAssignee: 'no-assignee',\n LineFilled: 'line-filled',\n AssigneeBound: 'assignee-bound'\n}\n\nexport const WHEN_NO_LINE_WORD: Record<string, string> = {\n Run: 'run',\n Refuse: 'refuse',\n AsEstablished: 'as-established'\n}\n\nexport const WHEN_NO_LINE_BECAUSE_WORD: Record<string, string> = {\n Irreversible: 'irreversible',\n ApprovalIsTheAct: 'approval-is-the-act',\n LowersAControl: 'lowers-a-control'\n}\n\n/** 모르는 철자면 `undefined` — 부르는 쪽이 「모른다」를 그리게 한다. */\nexport function standingWord(wire: string): string | undefined {\n return DOOR_STANDING_WORD[wire]\n}\n\nexport function kindWord(wire: string): string | undefined {\n return DOOR_KIND_WORD[wire]\n}\n\nexport function reasonWord(wire: string): string | undefined {\n return DOOR_REASON_WORD[wire]\n}\n\n/** 비어 있으면(배정 문) 비어 있는 채로 — 선언이 없는 것을 `run` 으로 기울이지 않는다. */\nexport function whenNoLineWord(wire?: string | null): string | undefined {\n return wire ? WHEN_NO_LINE_WORD[wire] : undefined\n}\n\nexport function whenNoLineBecauseWord(wire?: string | null): string | undefined {\n return wire ? WHEN_NO_LINE_BECAUSE_WORD[wire] : undefined\n}\n"]}
|
|
@@ -4,7 +4,7 @@ declare const WorkDoorsPage_base: (new (...args: any[]) => import("lit").LitElem
|
|
|
4
4
|
export declare class WorkDoorsPage extends WorkDoorsPage_base {
|
|
5
5
|
static styles: import("lit").CSSResult[];
|
|
6
6
|
private doors?;
|
|
7
|
-
private
|
|
7
|
+
private counts;
|
|
8
8
|
private todo;
|
|
9
9
|
private seatsStanding;
|
|
10
10
|
private seatsOffered;
|
|
@@ -8,7 +8,8 @@ import { i18next, localize } from '@operato/i18n';
|
|
|
8
8
|
import { PageView } from '@operato/shell';
|
|
9
9
|
import { ProseStyles, SurfaceStyles } from '@things-factory/styles';
|
|
10
10
|
import { detailAddress, doorsToSetHere, opensDetail } from './door-links.js';
|
|
11
|
-
import {
|
|
11
|
+
import { doorLabel, doorSentence, doorsSummary, whenNoLineTag } from './door-sentence.js';
|
|
12
|
+
import { kindWord, reasonWord, standingWord, whenNoLineBecauseWord, whenNoLineWord } from './door-words.js';
|
|
12
13
|
/**
|
|
13
14
|
* 결재 문 현황 — **이 공장에서 어느 승인 단계가 실제로 서 있나.**
|
|
14
15
|
*
|
|
@@ -22,9 +23,10 @@ import { kindWord, standingWord } from './door-words.js';
|
|
|
22
23
|
* 실적 정정도 폐기도 승인 없이 반영되는데, 화면에는 문이 있는 것처럼 보였다.
|
|
23
24
|
*
|
|
24
25
|
* ── 비어 있는 것을 「승인 없음」으로 그리지 않는다 ─────────────────────────
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* 빈 결재선의 결과는 **문마다 선언돼 있다**(ADR-0077). `run` 문은 올린 사람이 자기 건을 승인한 것이
|
|
27
|
+
* 되고, `refuse` 문은 올리는 것 자체가 거절된다. 서버는 그 사실을 값으로 보내고 **문장은 이 화면이
|
|
28
|
+
* 번역으로 짓는다**(§`door-sentence.ts`) — 서버가 한국어로 지어 보내던 때는 다른 언어 사용자도 한국어를
|
|
29
|
+
* 읽었고, 거절하는 문에도 「자기 건을 승인한다」가 적혔다.
|
|
28
30
|
*
|
|
29
31
|
* ── 할 일이 있는 것부터 ────────────────────────────────────────────────────
|
|
30
32
|
* 선언 순서로 늘어놓으면 서 있는 것과 빈 것이 섞이고, 읽는 사람이 열한 줄을 훑어야 한다. **정할
|
|
@@ -34,18 +36,26 @@ import { kindWord, standingWord } from './door-words.js';
|
|
|
34
36
|
const DOORS = gql `
|
|
35
37
|
{
|
|
36
38
|
workDoors {
|
|
37
|
-
summary
|
|
38
39
|
todo
|
|
40
|
+
on
|
|
41
|
+
off
|
|
42
|
+
missing
|
|
43
|
+
notUsed
|
|
39
44
|
seatsStanding
|
|
40
45
|
seatsOffered
|
|
41
46
|
doors {
|
|
42
47
|
name
|
|
43
|
-
|
|
48
|
+
labelKey
|
|
44
49
|
kind
|
|
45
50
|
standing
|
|
51
|
+
reason
|
|
46
52
|
approvers
|
|
47
53
|
from
|
|
48
|
-
|
|
54
|
+
whenNoLine
|
|
55
|
+
whenNoLineBecause
|
|
56
|
+
followsLabelKey
|
|
57
|
+
assigneeRole
|
|
58
|
+
notUsedBecause
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
}
|
|
@@ -60,7 +70,7 @@ const ORDER = { missing: 0, off: 1, on: 2, 'not-used': 3 };
|
|
|
60
70
|
let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
61
71
|
constructor() {
|
|
62
72
|
super(...arguments);
|
|
63
|
-
this.
|
|
73
|
+
this.counts = { on: 0, off: 0, missing: 0, notUsed: 0, todo: 0 };
|
|
64
74
|
this.todo = 0;
|
|
65
75
|
this.seatsStanding = 0;
|
|
66
76
|
this.seatsOffered = 0;
|
|
@@ -181,6 +191,14 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
181
191
|
opacity: 0.85;
|
|
182
192
|
}
|
|
183
193
|
|
|
194
|
+
/* 빈 결재선에서 이 문이 어떻게 하나 — 선언이다(ADR-0077). 문장 아래 한 줄로. */
|
|
195
|
+
.when {
|
|
196
|
+
display: block;
|
|
197
|
+
margin-top: 2px;
|
|
198
|
+
font-size: 0.85em;
|
|
199
|
+
opacity: 0.75;
|
|
200
|
+
}
|
|
201
|
+
|
|
184
202
|
.from {
|
|
185
203
|
flex: 0 0 auto;
|
|
186
204
|
font-size: 0.85em;
|
|
@@ -234,10 +252,20 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
234
252
|
this.doors = (response.data?.workDoors?.doors ?? []).map((one) => ({
|
|
235
253
|
...one,
|
|
236
254
|
standing: standingWord(one.standing) ?? one.standing,
|
|
237
|
-
kind: kindWord(one.kind) ?? one.kind
|
|
255
|
+
kind: kindWord(one.kind) ?? one.kind,
|
|
256
|
+
reason: reasonWord(one.reason) ?? one.reason,
|
|
257
|
+
whenNoLine: whenNoLineWord(one.whenNoLine),
|
|
258
|
+
whenNoLineBecause: whenNoLineBecauseWord(one.whenNoLineBecause)
|
|
238
259
|
}));
|
|
239
|
-
|
|
240
|
-
this.todo =
|
|
260
|
+
const read = response.data?.workDoors ?? {};
|
|
261
|
+
this.todo = read.todo ?? 0;
|
|
262
|
+
this.counts = {
|
|
263
|
+
on: read.on ?? 0,
|
|
264
|
+
off: read.off ?? 0,
|
|
265
|
+
missing: read.missing ?? 0,
|
|
266
|
+
notUsed: read.notUsed ?? 0,
|
|
267
|
+
todo: this.todo
|
|
268
|
+
};
|
|
241
269
|
this.seatsStanding = response.data?.workDoors?.seatsStanding ?? 0;
|
|
242
270
|
this.seatsOffered = response.data?.workDoors?.seatsOffered ?? 0;
|
|
243
271
|
}
|
|
@@ -284,7 +312,7 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
284
312
|
${toSet.length
|
|
285
313
|
? html `<li>
|
|
286
314
|
${i18next.t('text.work-doors-next-line', { count: toSet.length })}
|
|
287
|
-
${toSet.map((door, index) => html `${index ? ' · ' : ''}<a href=${detailAddress(door.name)}>${i18next.t
|
|
315
|
+
${toSet.map((door, index) => html `${index ? ' · ' : ''}<a href=${detailAddress(door.name)}>${doorLabel(i18next.t, door)}</a>`)}
|
|
288
316
|
</li>`
|
|
289
317
|
: ''}
|
|
290
318
|
</ol>
|
|
@@ -312,7 +340,7 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
312
340
|
|
|
313
341
|
<section class="sheet ui-card">
|
|
314
342
|
<span class="ui-label">${i18next.t('title.work-doors')}</span>
|
|
315
|
-
<p class="state">${this.
|
|
343
|
+
<p class="state">${doorsSummary(i18next.t, this.counts)}</p>
|
|
316
344
|
${sorted.map(door => this.renderDoor(door))}
|
|
317
345
|
</section>
|
|
318
346
|
|
|
@@ -320,14 +348,18 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
320
348
|
`;
|
|
321
349
|
}
|
|
322
350
|
renderDoor(door) {
|
|
351
|
+
const label = doorLabel(i18next.t, door);
|
|
352
|
+
const when = whenNoLineTag(i18next.t, door);
|
|
323
353
|
return html `
|
|
324
354
|
<div class="door ${door.standing}">
|
|
325
355
|
${opensDetail(door)
|
|
326
|
-
? html `<a class="name" href=${detailAddress(door.name)}>${
|
|
327
|
-
: html `<span class="name">${
|
|
356
|
+
? html `<a class="name" href=${detailAddress(door.name)}>${label}</a>`
|
|
357
|
+
: html `<span class="name">${label}</span>`}
|
|
328
358
|
<span class="kind">${i18next.t(`text.door-kind-${door.kind}`)}</span>
|
|
329
359
|
<span class="mark ${door.standing}">${i18next.t(`text.door-standing-${door.standing}`)}</span>
|
|
330
|
-
<span class="why"
|
|
360
|
+
<span class="why"
|
|
361
|
+
>${doorSentence(i18next.t, door)}${when ? html `<span class="when">${when}</span>` : ''}</span
|
|
362
|
+
>
|
|
331
363
|
${
|
|
332
364
|
/*
|
|
333
365
|
* 자기 것이 아닌 양식을 쓰는 것은 정상이지만, 안 말하면 「정한 적 없는데」가 된다.
|
|
@@ -355,7 +387,7 @@ __decorate([
|
|
|
355
387
|
__decorate([
|
|
356
388
|
state(),
|
|
357
389
|
__metadata("design:type", Object)
|
|
358
|
-
], WorkDoorsPage.prototype, "
|
|
390
|
+
], WorkDoorsPage.prototype, "counts", void 0);
|
|
359
391
|
__decorate([
|
|
360
392
|
state(),
|
|
361
393
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-doors-page.js","sourceRoot":"","sources":["../../../client/pages/work-door/work-doors-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/text-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { PageView } from '@operato/shell'\nimport { ProseStyles, SurfaceStyles } from '@things-factory/styles'\n\nimport { detailAddress, doorsToSetHere, opensDetail } from './door-links.js'\nimport { kindWord, standingWord } from './door-words.js'\n\n/**\n * 결재 문 현황 — **이 공장에서 어느 승인 단계가 실제로 서 있나.**\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 왜 이 화면이 있나 ──────────────────────────────────────────────────────\n * 양식은 제품이 세우고 **결재선은 그 공장이 채운다.** 채운 것이 「우리는 이 단계를 쓴다」는 선언이라,\n * 비어 있는 것은 결함이 아니라 선택이다. 그런데 **그 선택이 어디에도 안 보였다.** 업무 카탈로그는\n * 「제품이 이 문을 만들었나」를 보여 주고, 이 물음에는 답하지 않는다.\n *\n * 실측(2026-09-11): 미라텍에 양식 11개가 다 서 있고 **11개 전부 결재자가 없었다.** 그 공장에서는\n * 실적 정정도 폐기도 승인 없이 반영되는데, 화면에는 문이 있는 것처럼 보였다.\n *\n * ── 비어 있는 것을 「승인 없음」으로 그리지 않는다 ─────────────────────────\n * 워크리스트는 결재선이 비면 **상신을 곧 완료로 만든다.** 그래서 실제 모습은 「아무도 승인하지\n * 않는다」가 아니라 **「올린 사람이 자기 건을 승인한다」**다. 그 차이를 문장이 말한다 — 문장은\n * 서버가 만든다(§`work-door.ts`). 화면이 상태를 보고 문장을 다시 지으면 두 벌이 된다.\n *\n * ── 할 일이 있는 것부터 ────────────────────────────────────────────────────\n * 선언 순서로 늘어놓으면 서 있는 것과 빈 것이 섞이고, 읽는 사람이 열한 줄을 훑어야 한다. **정할\n * 것이 남은 것을 위로** 올린다 — 이 화면을 여는 이유가 그것이다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\nconst DOORS = gql`\n {\n workDoors {\n summary\n todo\n seatsStanding\n seatsOffered\n doors {\n name\n label\n kind\n standing\n approvers\n from\n because\n }\n }\n }\n`\n\ntype Door = {\n name: string\n label: string\n kind: 'approval' | 'assignment'\n standing: 'on' | 'off' | 'missing' | 'not-used'\n approvers: number\n from?: string\n because: string\n}\n\n/**\n * 조치할 것 · 서 있는 것 · 안 쓰기로 한 것 순. 같은 무리 안에서는 선언 순서를 지킨다.\n *\n * 안 쓰기로 한 것을 맨 아래 두는 이유는 **그것이 할 일이 아니어서**다. 위에 섞으면 이 화면을 여는\n * 이유(무엇을 채워야 하나)가 안 보인다.\n */\nconst ORDER: Record<Door['standing'], number> = { missing: 0, off: 1, on: 2, 'not-used': 3 }\n\n@customElement('work-doors-page')\nexport class WorkDoorsPage extends localize(i18next)(PageView) {\n static styles = [\n ProseStyles,\n SurfaceStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px;\n overflow: auto;\n }\n\n /*\n * These classes were used here but defined only inside disposition-policy-page's shadow root,\n * so this screen drew them unstyled. Shadow roots do not share rules; each page states its own.\n */\n .sheet {\n max-width: var(--ui-measure, min(1080px, 100%));\n }\n\n .lead {\n margin: 0;\n color: var(--ui-muted, #57676f);\n font-size: var(--ui-t-small, 11.5px);\n max-width: 60ch;\n }\n\n .state {\n margin: 0 0 4px;\n color: var(--ui-ink, #17242c);\n font-weight: 600;\n }\n\n .none {\n margin: 0;\n color: var(--ui-muted, #57676f);\n }\n\n .blocked {\n border-left: 3px solid var(--ui-crit, #c93f30);\n padding: 10px 12px;\n background: var(--ui-line-soft, #eef1f3);\n border-radius: var(--ui-r-small, 6px);\n }\n\n .blocked .why {\n margin-top: 4px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n white-space: pre-wrap;\n }\n\n .rule-actions {\n margin-top: var(--spacing-small, 4px);\n }\n\n .door {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 10px 0;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n }\n\n .door:last-child {\n border-bottom: none;\n }\n\n .name {\n flex: 0 0 auto;\n font-weight: 600;\n }\n\n /* 상태는 글자로도 말한다 — 색만으로 가르면 색을 못 보는 사람에게 아무 말도 안 한다. */\n .mark {\n flex: 0 0 auto;\n font-size: 0.85em;\n padding: 1px 8px;\n border-radius: 10px;\n border: 1px solid currentColor;\n }\n\n .mark.on {\n color: var(--md-sys-color-primary, #3f6b3f);\n }\n\n .mark.off {\n color: var(--md-sys-color-tertiary, #7a5b12);\n }\n\n .mark.missing {\n color: var(--md-sys-color-error, #a33);\n }\n\n /* 할 일이 아니므로 눈에 덜 띈다. 지우지는 않는다 — 껐다는 사실 자체가 읽혀야 한다. */\n .mark.not-used {\n color: var(--md-sys-color-outline, #777);\n }\n\n .door.not-used {\n opacity: 0.7;\n }\n\n /* 종류는 낱말로 말한다 — 결재와 배정은 비었을 때 결과가 반대다. */\n .kind {\n flex: 0 0 auto;\n font-size: 0.8em;\n opacity: 0.65;\n }\n\n .why {\n flex: 1 1 auto;\n opacity: 0.85;\n }\n\n .from {\n flex: 0 0 auto;\n font-size: 0.85em;\n opacity: 0.7;\n }\n\n ol.next {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n ol.next li {\n line-height: 1.5;\n }\n\n ol.next a {\n margin-left: 4px;\n color: var(--md-sys-color-primary, #3f6cd4);\n }\n `\n ]\n\n @state() private doors?: Door[]\n @state() private summary = ''\n @state() private todo = 0\n @state() private seatsStanding = 0\n @state() private seatsOffered = 0\n @state() private readFailure = ''\n\n get context() {\n return { title: i18next.t('menu.work-doors') }\n }\n\n async pageInitialized() {\n await this.load()\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (lifecycle.active && !this.doors) {\n await this.load()\n }\n }\n\n private async load() {\n this.readFailure = ''\n\n const response = await client.query({ query: DOORS, fetchPolicy: 'no-cache' })\n\n /*\n * 못 읽은 것을 빈 목록으로 그리지 않는다. 「결재 문이 하나도 없습니다」와 「못 읽었습니다」는\n * 사람이 할 일이 다르다 — 앞은 설치를 보러 갈 일이고 뒤는 다시 눌러 볼 일이다.\n */\n if (response.errors?.length) {\n this.readFailure = response.errors[0].message\n return\n }\n\n /*\n * 전선의 철자를 여기서 **한 번** 옮긴다(§`door-words`). 안 옮기면 i18n 키도 CSS 클래스도\n * 어긋나는데 **오류가 하나도 안 난다** — 키 글자가 화면에 뜨고 상태 색이 투명해질 뿐이다.\n */\n this.doors = (response.data?.workDoors?.doors ?? []).map((one: any) => ({\n ...one,\n standing: standingWord(one.standing) ?? one.standing,\n kind: kindWord(one.kind) ?? one.kind\n }))\n this.summary = response.data?.workDoors?.summary ?? ''\n this.todo = response.data?.workDoors?.todo ?? 0\n this.seatsStanding = response.data?.workDoors?.seatsStanding ?? 0\n this.seatsOffered = response.data?.workDoors?.seatsOffered ?? 0\n }\n\n /**\n * 다음에 할 일 — **순서대로.**\n *\n * ── 문제만 말하는 화면이었다 ──────────────────────────────────────────\n * 열두 줄이 「비었습니다」라고 적혀 있고, 어디로 가야 채우는지는 어디에도 없었다. 이 저장소에서\n * 가장 자주 나는 결함을 내 화면이 하고 있었다 — **자리는 있고 길이 없다.**\n *\n * ── 순서를 지키는 이유 ────────────────────────────────────────────────\n * 결재선은 역할을 가리킨다. 역할이 하나도 없는 공장에서 양식 화면으로 가면 **결재선 칸이 빈\n * 목록**이고, 사람은 헛걸음한 채 무엇이 잘못됐는지 모르고 돌아온다. 그래서 자리가 안 섰으면\n * 자리부터 말한다.\n *\n * 여기서 고치게 하지 않는다. 역할도 업무 양식도 **남의 화면이 임자**이고, 같은 일을 하는 자리를\n * 둘 만들면 한쪽이 낡는다. 이 화면이 하는 일은 **가리키는 것**이다.\n */\n private renderNext() {\n if (!this.todo) {\n return ''\n }\n\n const seatsShort = this.seatsStanding < this.seatsOffered\n const toSet = doorsToSetHere(this.doors || [])\n\n /* 남은 것이 전부 남의 양식이거나 배정 양식이면 여기서 가리킬 곳이 없다 — 빈 칸을 세우지 않는다. */\n if (!seatsShort && !toSet.length) {\n return ''\n }\n\n return html`\n <section class=\"sheet ui-card\">\n <span class=\"ui-label\">${i18next.t('title.work-doors-next')}</span>\n <ol class=\"next\">\n ${seatsShort\n ? html`<li>\n ${i18next.t('text.work-doors-next-seats', {\n standing: this.seatsStanding,\n offered: this.seatsOffered\n })}\n <a href=\"roles\">${i18next.t('field.roles')}</a>\n <!-- field.roles 는 auth-ui 설치본의 키다. 내가 menu.roles 를 썼다가 고쳤다 —\n 그 키는 어디에도 없어서 화면에 키 글자가 그대로 떴을 것이다. -->\n </li>`\n : ''}\n ${toSet.length\n ? html`<li>\n ${i18next.t('text.work-doors-next-line', { count: toSet.length })}\n ${toSet.map(\n (door, index) =>\n html`${index ? ' · ' : ''}<a href=${detailAddress(door.name)}>${i18next.t(door.label)}</a>`\n )}\n </li>`\n : ''}\n </ol>\n </section>\n `\n }\n\n render() {\n if (this.readFailure) {\n return html`<div class=\"sheet blocked\">\n ${i18next.t('text.work-doors-read-failed')}\n <div class=\"why\">${this.readFailure}</div>\n <div class=\"rule-actions\">\n <md-text-button @click=${() => this.load()}>${i18next.t('button.try-again')}</md-text-button>\n </div>\n </div>`\n }\n\n if (!this.doors) {\n return html`<div class=\"none\">${i18next.t('text.loading')}</div>`\n }\n\n const sorted = [...this.doors].sort((a, b) => ORDER[a.standing] - ORDER[b.standing])\n\n return html`\n <div class=\"sheet\">\n <p class=\"lead\">${i18next.t('text.work-doors-lead')}</p>\n </div>\n\n <section class=\"sheet ui-card\">\n <span class=\"ui-label\">${i18next.t('title.work-doors')}</span>\n <p class=\"state\">${this.summary}</p>\n ${sorted.map(door => this.renderDoor(door))}\n </section>\n\n ${this.renderNext()}\n `\n }\n\n private renderDoor(door: Door) {\n return html`\n <div class=\"door ${door.standing}\">\n ${opensDetail(door)\n ? html`<a class=\"name\" href=${detailAddress(door.name)}>${i18next.t(door.label)}</a>`\n : html`<span class=\"name\">${i18next.t(door.label)}</span>`}\n <span class=\"kind\">${i18next.t(`text.door-kind-${door.kind}`)}</span>\n <span class=\"mark ${door.standing}\">${i18next.t(`text.door-standing-${door.standing}`)}</span>\n <span class=\"why\">${door.because}</span>\n ${\n /*\n * 자기 것이 아닌 양식을 쓰는 것은 정상이지만, 안 말하면 「정한 적 없는데」가 된다.\n *\n * ⚠ **차 있는 것과 비어 있는 것은 다른 말이다.** 「본사에서 정했습니다」를 결재자가 없는\n * 줄에 달면 한 줄이 스스로 모순된다 — 정한 것이 없는데 정했다고 적힌다. 비어 있으면\n * 말할 수 있는 것은 **양식의 임자**뿐이고, 그 사실은 「여기서는 못 고칩니다」를 뜻해서\n * 오히려 더 중요하다.\n */\n door.from\n ? html`<span class=\"from\"\n >${door.standing === 'on'\n ? i18next.t('text.door-filled-from', { name: door.from })\n : i18next.t('text.door-form-from', { name: door.from })}</span\n >`\n : ''\n }\n </div>\n `\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"work-doors-page.js","sourceRoot":"","sources":["../../../client/pages/work-door/work-doors-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/text-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { PageView } from '@operato/shell'\nimport { ProseStyles, SurfaceStyles } from '@things-factory/styles'\n\nimport { detailAddress, doorsToSetHere, opensDetail } from './door-links.js'\nimport { DoorCounts, DoorFacts, doorLabel, doorSentence, doorsSummary, whenNoLineTag } from './door-sentence.js'\nimport { kindWord, reasonWord, standingWord, whenNoLineBecauseWord, whenNoLineWord } from './door-words.js'\n\n/**\n * 결재 문 현황 — **이 공장에서 어느 승인 단계가 실제로 서 있나.**\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ── 왜 이 화면이 있나 ──────────────────────────────────────────────────────\n * 양식은 제품이 세우고 **결재선은 그 공장이 채운다.** 채운 것이 「우리는 이 단계를 쓴다」는 선언이라,\n * 비어 있는 것은 결함이 아니라 선택이다. 그런데 **그 선택이 어디에도 안 보였다.** 업무 카탈로그는\n * 「제품이 이 문을 만들었나」를 보여 주고, 이 물음에는 답하지 않는다.\n *\n * 실측(2026-09-11): 미라텍에 양식 11개가 다 서 있고 **11개 전부 결재자가 없었다.** 그 공장에서는\n * 실적 정정도 폐기도 승인 없이 반영되는데, 화면에는 문이 있는 것처럼 보였다.\n *\n * ── 비어 있는 것을 「승인 없음」으로 그리지 않는다 ─────────────────────────\n * 빈 결재선의 결과는 **문마다 선언돼 있다**(ADR-0077). `run` 문은 올린 사람이 자기 건을 승인한 것이\n * 되고, `refuse` 문은 올리는 것 자체가 거절된다. 서버는 그 사실을 값으로 보내고 **문장은 이 화면이\n * 번역으로 짓는다**(§`door-sentence.ts`) — 서버가 한국어로 지어 보내던 때는 다른 언어 사용자도 한국어를\n * 읽었고, 거절하는 문에도 「자기 건을 승인한다」가 적혔다.\n *\n * ── 할 일이 있는 것부터 ────────────────────────────────────────────────────\n * 선언 순서로 늘어놓으면 서 있는 것과 빈 것이 섞이고, 읽는 사람이 열한 줄을 훑어야 한다. **정할\n * 것이 남은 것을 위로** 올린다 — 이 화면을 여는 이유가 그것이다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\nconst DOORS = gql`\n {\n workDoors {\n todo\n on\n off\n missing\n notUsed\n seatsStanding\n seatsOffered\n doors {\n name\n labelKey\n kind\n standing\n reason\n approvers\n from\n whenNoLine\n whenNoLineBecause\n followsLabelKey\n assigneeRole\n notUsedBecause\n }\n }\n }\n`\n\ntype Door = DoorFacts & {\n kind: 'approval' | 'assignment'\n standing: 'on' | 'off' | 'missing' | 'not-used'\n from?: string\n}\n\n/**\n * 조치할 것 · 서 있는 것 · 안 쓰기로 한 것 순. 같은 무리 안에서는 선언 순서를 지킨다.\n *\n * 안 쓰기로 한 것을 맨 아래 두는 이유는 **그것이 할 일이 아니어서**다. 위에 섞으면 이 화면을 여는\n * 이유(무엇을 채워야 하나)가 안 보인다.\n */\nconst ORDER: Record<Door['standing'], number> = { missing: 0, off: 1, on: 2, 'not-used': 3 }\n\n@customElement('work-doors-page')\nexport class WorkDoorsPage extends localize(i18next)(PageView) {\n static styles = [\n ProseStyles,\n SurfaceStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px;\n overflow: auto;\n }\n\n /*\n * These classes were used here but defined only inside disposition-policy-page's shadow root,\n * so this screen drew them unstyled. Shadow roots do not share rules; each page states its own.\n */\n .sheet {\n max-width: var(--ui-measure, min(1080px, 100%));\n }\n\n .lead {\n margin: 0;\n color: var(--ui-muted, #57676f);\n font-size: var(--ui-t-small, 11.5px);\n max-width: 60ch;\n }\n\n .state {\n margin: 0 0 4px;\n color: var(--ui-ink, #17242c);\n font-weight: 600;\n }\n\n .none {\n margin: 0;\n color: var(--ui-muted, #57676f);\n }\n\n .blocked {\n border-left: 3px solid var(--ui-crit, #c93f30);\n padding: 10px 12px;\n background: var(--ui-line-soft, #eef1f3);\n border-radius: var(--ui-r-small, 6px);\n }\n\n .blocked .why {\n margin-top: 4px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n white-space: pre-wrap;\n }\n\n .rule-actions {\n margin-top: var(--spacing-small, 4px);\n }\n\n .door {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 10px 0;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n }\n\n .door:last-child {\n border-bottom: none;\n }\n\n .name {\n flex: 0 0 auto;\n font-weight: 600;\n }\n\n /* 상태는 글자로도 말한다 — 색만으로 가르면 색을 못 보는 사람에게 아무 말도 안 한다. */\n .mark {\n flex: 0 0 auto;\n font-size: 0.85em;\n padding: 1px 8px;\n border-radius: 10px;\n border: 1px solid currentColor;\n }\n\n .mark.on {\n color: var(--md-sys-color-primary, #3f6b3f);\n }\n\n .mark.off {\n color: var(--md-sys-color-tertiary, #7a5b12);\n }\n\n .mark.missing {\n color: var(--md-sys-color-error, #a33);\n }\n\n /* 할 일이 아니므로 눈에 덜 띈다. 지우지는 않는다 — 껐다는 사실 자체가 읽혀야 한다. */\n .mark.not-used {\n color: var(--md-sys-color-outline, #777);\n }\n\n .door.not-used {\n opacity: 0.7;\n }\n\n /* 종류는 낱말로 말한다 — 결재와 배정은 비었을 때 결과가 반대다. */\n .kind {\n flex: 0 0 auto;\n font-size: 0.8em;\n opacity: 0.65;\n }\n\n .why {\n flex: 1 1 auto;\n opacity: 0.85;\n }\n\n /* 빈 결재선에서 이 문이 어떻게 하나 — 선언이다(ADR-0077). 문장 아래 한 줄로. */\n .when {\n display: block;\n margin-top: 2px;\n font-size: 0.85em;\n opacity: 0.75;\n }\n\n .from {\n flex: 0 0 auto;\n font-size: 0.85em;\n opacity: 0.7;\n }\n\n ol.next {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n ol.next li {\n line-height: 1.5;\n }\n\n ol.next a {\n margin-left: 4px;\n color: var(--md-sys-color-primary, #3f6cd4);\n }\n `\n ]\n\n @state() private doors?: Door[]\n @state() private counts: DoorCounts = { on: 0, off: 0, missing: 0, notUsed: 0, todo: 0 }\n @state() private todo = 0\n @state() private seatsStanding = 0\n @state() private seatsOffered = 0\n @state() private readFailure = ''\n\n get context() {\n return { title: i18next.t('menu.work-doors') }\n }\n\n async pageInitialized() {\n await this.load()\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (lifecycle.active && !this.doors) {\n await this.load()\n }\n }\n\n private async load() {\n this.readFailure = ''\n\n const response = await client.query({ query: DOORS, fetchPolicy: 'no-cache' })\n\n /*\n * 못 읽은 것을 빈 목록으로 그리지 않는다. 「결재 문이 하나도 없습니다」와 「못 읽었습니다」는\n * 사람이 할 일이 다르다 — 앞은 설치를 보러 갈 일이고 뒤는 다시 눌러 볼 일이다.\n */\n if (response.errors?.length) {\n this.readFailure = response.errors[0].message\n return\n }\n\n /*\n * 전선의 철자를 여기서 **한 번** 옮긴다(§`door-words`). 안 옮기면 i18n 키도 CSS 클래스도\n * 어긋나는데 **오류가 하나도 안 난다** — 키 글자가 화면에 뜨고 상태 색이 투명해질 뿐이다.\n */\n this.doors = (response.data?.workDoors?.doors ?? []).map((one: any) => ({\n ...one,\n standing: standingWord(one.standing) ?? one.standing,\n kind: kindWord(one.kind) ?? one.kind,\n reason: reasonWord(one.reason) ?? one.reason,\n whenNoLine: whenNoLineWord(one.whenNoLine),\n whenNoLineBecause: whenNoLineBecauseWord(one.whenNoLineBecause)\n }))\n\n const read = response.data?.workDoors ?? {}\n\n this.todo = read.todo ?? 0\n this.counts = {\n on: read.on ?? 0,\n off: read.off ?? 0,\n missing: read.missing ?? 0,\n notUsed: read.notUsed ?? 0,\n todo: this.todo\n }\n this.seatsStanding = response.data?.workDoors?.seatsStanding ?? 0\n this.seatsOffered = response.data?.workDoors?.seatsOffered ?? 0\n }\n\n /**\n * 다음에 할 일 — **순서대로.**\n *\n * ── 문제만 말하는 화면이었다 ──────────────────────────────────────────\n * 열두 줄이 「비었습니다」라고 적혀 있고, 어디로 가야 채우는지는 어디에도 없었다. 이 저장소에서\n * 가장 자주 나는 결함을 내 화면이 하고 있었다 — **자리는 있고 길이 없다.**\n *\n * ── 순서를 지키는 이유 ────────────────────────────────────────────────\n * 결재선은 역할을 가리킨다. 역할이 하나도 없는 공장에서 양식 화면으로 가면 **결재선 칸이 빈\n * 목록**이고, 사람은 헛걸음한 채 무엇이 잘못됐는지 모르고 돌아온다. 그래서 자리가 안 섰으면\n * 자리부터 말한다.\n *\n * 여기서 고치게 하지 않는다. 역할도 업무 양식도 **남의 화면이 임자**이고, 같은 일을 하는 자리를\n * 둘 만들면 한쪽이 낡는다. 이 화면이 하는 일은 **가리키는 것**이다.\n */\n private renderNext() {\n if (!this.todo) {\n return ''\n }\n\n const seatsShort = this.seatsStanding < this.seatsOffered\n const toSet = doorsToSetHere(this.doors || [])\n\n /* 남은 것이 전부 남의 양식이거나 배정 양식이면 여기서 가리킬 곳이 없다 — 빈 칸을 세우지 않는다. */\n if (!seatsShort && !toSet.length) {\n return ''\n }\n\n return html`\n <section class=\"sheet ui-card\">\n <span class=\"ui-label\">${i18next.t('title.work-doors-next')}</span>\n <ol class=\"next\">\n ${seatsShort\n ? html`<li>\n ${i18next.t('text.work-doors-next-seats', {\n standing: this.seatsStanding,\n offered: this.seatsOffered\n })}\n <a href=\"roles\">${i18next.t('field.roles')}</a>\n <!-- field.roles 는 auth-ui 설치본의 키다. 내가 menu.roles 를 썼다가 고쳤다 —\n 그 키는 어디에도 없어서 화면에 키 글자가 그대로 떴을 것이다. -->\n </li>`\n : ''}\n ${toSet.length\n ? html`<li>\n ${i18next.t('text.work-doors-next-line', { count: toSet.length })}\n ${toSet.map(\n (door, index) =>\n html`${index ? ' · ' : ''}<a href=${detailAddress(door.name)}>${doorLabel(i18next.t, door)}</a>`\n )}\n </li>`\n : ''}\n </ol>\n </section>\n `\n }\n\n render() {\n if (this.readFailure) {\n return html`<div class=\"sheet blocked\">\n ${i18next.t('text.work-doors-read-failed')}\n <div class=\"why\">${this.readFailure}</div>\n <div class=\"rule-actions\">\n <md-text-button @click=${() => this.load()}>${i18next.t('button.try-again')}</md-text-button>\n </div>\n </div>`\n }\n\n if (!this.doors) {\n return html`<div class=\"none\">${i18next.t('text.loading')}</div>`\n }\n\n const sorted = [...this.doors].sort((a, b) => ORDER[a.standing] - ORDER[b.standing])\n\n return html`\n <div class=\"sheet\">\n <p class=\"lead\">${i18next.t('text.work-doors-lead')}</p>\n </div>\n\n <section class=\"sheet ui-card\">\n <span class=\"ui-label\">${i18next.t('title.work-doors')}</span>\n <p class=\"state\">${doorsSummary(i18next.t, this.counts)}</p>\n ${sorted.map(door => this.renderDoor(door))}\n </section>\n\n ${this.renderNext()}\n `\n }\n\n private renderDoor(door: Door) {\n const label = doorLabel(i18next.t, door)\n const when = whenNoLineTag(i18next.t, door)\n\n return html`\n <div class=\"door ${door.standing}\">\n ${opensDetail(door)\n ? html`<a class=\"name\" href=${detailAddress(door.name)}>${label}</a>`\n : html`<span class=\"name\">${label}</span>`}\n <span class=\"kind\">${i18next.t(`text.door-kind-${door.kind}`)}</span>\n <span class=\"mark ${door.standing}\">${i18next.t(`text.door-standing-${door.standing}`)}</span>\n <span class=\"why\"\n >${doorSentence(i18next.t, door)}${when ? html`<span class=\"when\">${when}</span>` : ''}</span\n >\n ${\n /*\n * 자기 것이 아닌 양식을 쓰는 것은 정상이지만, 안 말하면 「정한 적 없는데」가 된다.\n *\n * ⚠ **차 있는 것과 비어 있는 것은 다른 말이다.** 「본사에서 정했습니다」를 결재자가 없는\n * 줄에 달면 한 줄이 스스로 모순된다 — 정한 것이 없는데 정했다고 적힌다. 비어 있으면\n * 말할 수 있는 것은 **양식의 임자**뿐이고, 그 사실은 「여기서는 못 고칩니다」를 뜻해서\n * 오히려 더 중요하다.\n */\n door.from\n ? html`<span class=\"from\"\n >${door.standing === 'on'\n ? i18next.t('text.door-filled-from', { name: door.from })\n : i18next.t('text.door-form-from', { name: door.from })}</span\n >`\n : ''\n }\n </div>\n `\n }\n}\n"]}
|
package/dist-client/route.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function route(page: string): "
|
|
1
|
+
export default function route(page: string): "draft-list" | "activity" | "todo-list" | "done-list" | "approval-pending-list" | "done-list-calendar" | "activity-catalog" | "activity-detail" | "work-doors-page" | "approval-done-list" | "pickable-list" | "starter-list" | "reporter-list" | "activity-list" | "activity-instance-start" | "activity-thread" | "activity-thread-view" | "activity-approval" | "activity-instance-list" | "activity-instance-search" | "activity-template-list" | "activity-store" | "activity-thread-list" | "activity-approval-list" | "installable-activity-list" | "activity-stats-list" | undefined;
|