@things-factory/worklist 10.1.29 → 10.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist-client/pages/work-door/door-words.d.ts +41 -0
  2. package/dist-client/pages/work-door/door-words.js +54 -0
  3. package/dist-client/pages/work-door/door-words.js.map +1 -0
  4. package/dist-client/pages/work-door/work-doors-page.d.ts +37 -0
  5. package/dist-client/pages/work-door/work-doors-page.js +369 -0
  6. package/dist-client/pages/work-door/work-doors-page.js.map +1 -0
  7. package/dist-client/route.d.ts +1 -1
  8. package/dist-client/route.js +3 -0
  9. package/dist-client/route.js.map +1 -1
  10. package/dist-client/tsconfig.tsbuildinfo +1 -1
  11. package/dist-server/service/index.d.ts +13 -2
  12. package/dist-server/service/index.js +25 -2
  13. package/dist-server/service/index.js.map +1 -1
  14. package/dist-server/service/work-door/activity-lookup.d.ts +70 -0
  15. package/dist-server/service/work-door/activity-lookup.js +110 -0
  16. package/dist-server/service/work-door/activity-lookup.js.map +1 -0
  17. package/dist-server/service/work-door/approval-line-standing.d.ts +92 -0
  18. package/dist-server/service/work-door/approval-line-standing.js +47 -0
  19. package/dist-server/service/work-door/approval-line-standing.js.map +1 -0
  20. package/dist-server/service/work-door/index.d.ts +15 -0
  21. package/dist-server/service/work-door/index.js +20 -0
  22. package/dist-server/service/work-door/index.js.map +1 -0
  23. package/dist-server/service/work-door/pass-work-door.d.ts +74 -0
  24. package/dist-server/service/work-door/pass-work-door.js +45 -0
  25. package/dist-server/service/work-door/pass-work-door.js.map +1 -0
  26. package/dist-server/service/work-door/work-door-declaration.d.ts +114 -0
  27. package/dist-server/service/work-door/work-door-declaration.js +67 -0
  28. package/dist-server/service/work-door/work-door-declaration.js.map +1 -0
  29. package/dist-server/service/work-door/work-door-event.d.ts +20 -0
  30. package/dist-server/service/work-door/work-door-event.js +25 -0
  31. package/dist-server/service/work-door/work-door-event.js.map +1 -0
  32. package/dist-server/service/work-door/work-door-journal-service.d.ts +47 -0
  33. package/dist-server/service/work-door/work-door-journal-service.js +75 -0
  34. package/dist-server/service/work-door/work-door-journal-service.js.map +1 -0
  35. package/dist-server/service/work-door/work-door-journal.d.ts +30 -0
  36. package/dist-server/service/work-door/work-door-journal.js +94 -0
  37. package/dist-server/service/work-door/work-door-journal.js.map +1 -0
  38. package/dist-server/service/work-door/work-door-not-used.d.ts +39 -0
  39. package/dist-server/service/work-door/work-door-not-used.js +86 -0
  40. package/dist-server/service/work-door/work-door-not-used.js.map +1 -0
  41. package/dist-server/service/work-door/work-door-resolver.d.ts +29 -0
  42. package/dist-server/service/work-door/work-door-resolver.js +127 -0
  43. package/dist-server/service/work-door/work-door-resolver.js.map +1 -0
  44. package/dist-server/service/work-door/work-door-service.d.ts +29 -0
  45. package/dist-server/service/work-door/work-door-service.js +126 -0
  46. package/dist-server/service/work-door/work-door-service.js.map +1 -0
  47. package/dist-server/service/work-door/work-door-type.d.ts +49 -0
  48. package/dist-server/service/work-door/work-door-type.js +122 -0
  49. package/dist-server/service/work-door/work-door-type.js.map +1 -0
  50. package/dist-server/service/work-door/work-door.d.ts +138 -0
  51. package/dist-server/service/work-door/work-door.js +156 -0
  52. package/dist-server/service/work-door/work-door.js.map +1 -0
  53. package/dist-server/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +10 -10
  55. package/tests/pass-work-door.test.ts +133 -0
  56. package/tests/screen-seam.test.ts +50 -0
  57. package/tests/work-door-approval-line-standing.test.ts +117 -0
  58. package/tests/work-door-door-words.test.ts +97 -0
  59. package/tests/work-door.test.ts +222 -0
  60. package/things-factory.config.js +6 -1
  61. package/translations/en.json +18 -1
  62. package/translations/ja.json +18 -1
  63. package/translations/ko.json +18 -1
  64. package/translations/ms.json +18 -1
  65. package/translations/zh.json +18 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * 전선으로 오는 문 상태를 **이 화면의 낱말로** 옮긴다 — 경계 한 곳에서.
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════
5
+ * ── 왜 옮겨야 하나 ────────────────────────────────────────────────────────
6
+ * `type-graphql` 의 `registerEnumType` 은 **멤버 이름**을 전선에 싣는다. 값이 아니다.
7
+ *
8
+ * ```ts
9
+ * export enum DoorStandingValue {
10
+ * Off = 'off' // ← 전선으로 가는 것은 'Off' 다
11
+ * }
12
+ * ```
13
+ *
14
+ * 판단부(`work-door.ts`)의 낱말은 `off` 이고, i18n 키도 CSS 클래스도 그 낱말로 적혀 있다. 그래서
15
+ * 옮기지 않으면 **둘 다 조용히 어긋난다.**
16
+ *
17
+ * ```
18
+ * [확인함] 2026-09-12 실행 중인 화면 (MIRATEK)
19
+ * 전선 "Off" · "Approval"
20
+ * i18n text.door-standing-Off — 없는 키라 화면에 키 글자가 그대로
21
+ * CSS .mark.Off 안 맞음 → 상태 색이 안 걸리고 물려받은 글자색으로 그려짐
22
+ * ```
23
+ *
24
+ * 상태 색이 한 번도 안 나왔고, 열두 줄에 한국어 대신 키가 적혀 있었다. 서버도 시험도 전부 초록이다 —
25
+ * **클라이언트만 조용히 틀리는** 부류다.
26
+ *
27
+ * ── 왜 서버 쪽 철자를 안 고치나 ───────────────────────────────────────────
28
+ * `not-used` 는 enum 멤버 이름이 될 수 없다(붙임표). 그래서 어떤 철자를 골라도 한쪽은 옮겨야 하고,
29
+ * 옮기는 자리를 **하나**로 두는 것이 답이다. 화면이 받는 자리가 그 하나다.
30
+ *
31
+ * ── 모르는 값을 지어내지 않는다 ───────────────────────────────────────────
32
+ * 새 상태가 생겼는데 여기 안 적히면 `undefined` 다. 화면은 그때 받은 글자를 그대로 보이고 색을 안
33
+ * 칠한다 — 「모른다」가 보여야 이 표를 고칠 수 있다. `off` 로 기울이면 새 상태가 조용히 묻힌다.
34
+ * ═══════════════════════════════════════════════════════════════════════════
35
+ */
36
+ /** 전선의 철자 → 판단부의 낱말. 키가 전선의 것이라 대문자다 — 일부러다. */
37
+ export declare const DOOR_STANDING_WORD: Record<string, string>;
38
+ export declare const DOOR_KIND_WORD: Record<string, string>;
39
+ /** 모르는 철자면 `undefined` — 부르는 쪽이 「모른다」를 그리게 한다. */
40
+ export declare function standingWord(wire: string): string | undefined;
41
+ export declare function kindWord(wire: string): string | undefined;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 전선으로 오는 문 상태를 **이 화면의 낱말로** 옮긴다 — 경계 한 곳에서.
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════
5
+ * ── 왜 옮겨야 하나 ────────────────────────────────────────────────────────
6
+ * `type-graphql` 의 `registerEnumType` 은 **멤버 이름**을 전선에 싣는다. 값이 아니다.
7
+ *
8
+ * ```ts
9
+ * export enum DoorStandingValue {
10
+ * Off = 'off' // ← 전선으로 가는 것은 'Off' 다
11
+ * }
12
+ * ```
13
+ *
14
+ * 판단부(`work-door.ts`)의 낱말은 `off` 이고, i18n 키도 CSS 클래스도 그 낱말로 적혀 있다. 그래서
15
+ * 옮기지 않으면 **둘 다 조용히 어긋난다.**
16
+ *
17
+ * ```
18
+ * [확인함] 2026-09-12 실행 중인 화면 (MIRATEK)
19
+ * 전선 "Off" · "Approval"
20
+ * i18n text.door-standing-Off — 없는 키라 화면에 키 글자가 그대로
21
+ * CSS .mark.Off 안 맞음 → 상태 색이 안 걸리고 물려받은 글자색으로 그려짐
22
+ * ```
23
+ *
24
+ * 상태 색이 한 번도 안 나왔고, 열두 줄에 한국어 대신 키가 적혀 있었다. 서버도 시험도 전부 초록이다 —
25
+ * **클라이언트만 조용히 틀리는** 부류다.
26
+ *
27
+ * ── 왜 서버 쪽 철자를 안 고치나 ───────────────────────────────────────────
28
+ * `not-used` 는 enum 멤버 이름이 될 수 없다(붙임표). 그래서 어떤 철자를 골라도 한쪽은 옮겨야 하고,
29
+ * 옮기는 자리를 **하나**로 두는 것이 답이다. 화면이 받는 자리가 그 하나다.
30
+ *
31
+ * ── 모르는 값을 지어내지 않는다 ───────────────────────────────────────────
32
+ * 새 상태가 생겼는데 여기 안 적히면 `undefined` 다. 화면은 그때 받은 글자를 그대로 보이고 색을 안
33
+ * 칠한다 — 「모른다」가 보여야 이 표를 고칠 수 있다. `off` 로 기울이면 새 상태가 조용히 묻힌다.
34
+ * ═══════════════════════════════════════════════════════════════════════════
35
+ */
36
+ /** 전선의 철자 → 판단부의 낱말. 키가 전선의 것이라 대문자다 — 일부러다. */
37
+ export const DOOR_STANDING_WORD = {
38
+ On: 'on',
39
+ Off: 'off',
40
+ Missing: 'missing',
41
+ NotUsed: 'not-used'
42
+ };
43
+ export const DOOR_KIND_WORD = {
44
+ Approval: 'approval',
45
+ Assignment: 'assignment'
46
+ };
47
+ /** 모르는 철자면 `undefined` — 부르는 쪽이 「모른다」를 그리게 한다. */
48
+ export function standingWord(wire) {
49
+ return DOOR_STANDING_WORD[wire];
50
+ }
51
+ export function kindWord(wire) {
52
+ return DOOR_KIND_WORD[wire];
53
+ }
54
+ //# sourceMappingURL=door-words.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,37 @@
1
+ import '@material/web/button/text-button.js';
2
+ import { PageView } from '@operato/shell';
3
+ declare const WorkDoorsPage_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
4
+ export declare class WorkDoorsPage extends WorkDoorsPage_base {
5
+ static styles: import("lit").CSSResult[];
6
+ private doors?;
7
+ private summary;
8
+ private todo;
9
+ private seatsStanding;
10
+ private seatsOffered;
11
+ private readFailure;
12
+ get context(): {
13
+ title: string;
14
+ };
15
+ pageInitialized(): Promise<void>;
16
+ pageUpdated(changes: any, lifecycle: any): Promise<void>;
17
+ private load;
18
+ /**
19
+ * 다음에 할 일 — **순서대로.**
20
+ *
21
+ * ── 문제만 말하는 화면이었다 ──────────────────────────────────────────
22
+ * 열두 줄이 「비었습니다」라고 적혀 있고, 어디로 가야 채우는지는 어디에도 없었다. 이 저장소에서
23
+ * 가장 자주 나는 결함을 내 화면이 하고 있었다 — **자리는 있고 길이 없다.**
24
+ *
25
+ * ── 순서를 지키는 이유 ────────────────────────────────────────────────
26
+ * 결재선은 역할을 가리킨다. 역할이 하나도 없는 공장에서 양식 화면으로 가면 **결재선 칸이 빈
27
+ * 목록**이고, 사람은 헛걸음한 채 무엇이 잘못됐는지 모르고 돌아온다. 그래서 자리가 안 섰으면
28
+ * 자리부터 말한다.
29
+ *
30
+ * 여기서 고치게 하지 않는다. 역할도 업무 양식도 **남의 화면이 임자**이고, 같은 일을 하는 자리를
31
+ * 둘 만들면 한쪽이 낡는다. 이 화면이 하는 일은 **가리키는 것**이다.
32
+ */
33
+ private renderNext;
34
+ render(): import("lit-html").TemplateResult<1>;
35
+ private renderDoor;
36
+ }
37
+ export {};
@@ -0,0 +1,369 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/button/text-button.js';
3
+ import gql from 'graphql-tag';
4
+ import { css, html } from 'lit';
5
+ import { customElement, state } from 'lit/decorators.js';
6
+ import { client } from '@operato/graphql';
7
+ import { i18next, localize } from '@operato/i18n';
8
+ import { PageView } from '@operato/shell';
9
+ import { ProseStyles, SurfaceStyles } from '@things-factory/styles';
10
+ import { kindWord, standingWord } from './door-words.js';
11
+ /**
12
+ * 결재 문 현황 — **이 공장에서 어느 승인 단계가 실제로 서 있나.**
13
+ *
14
+ * ═══════════════════════════════════════════════════════════════════════════
15
+ * ── 왜 이 화면이 있나 ──────────────────────────────────────────────────────
16
+ * 양식은 제품이 세우고 **결재선은 그 공장이 채운다.** 채운 것이 「우리는 이 단계를 쓴다」는 선언이라,
17
+ * 비어 있는 것은 결함이 아니라 선택이다. 그런데 **그 선택이 어디에도 안 보였다.** 업무 카탈로그는
18
+ * 「제품이 이 문을 만들었나」를 보여 주고, 이 물음에는 답하지 않는다.
19
+ *
20
+ * 실측(2026-09-11): 미라텍에 양식 11개가 다 서 있고 **11개 전부 결재자가 없었다.** 그 공장에서는
21
+ * 실적 정정도 폐기도 승인 없이 반영되는데, 화면에는 문이 있는 것처럼 보였다.
22
+ *
23
+ * ── 비어 있는 것을 「승인 없음」으로 그리지 않는다 ─────────────────────────
24
+ * 워크리스트는 결재선이 비면 **상신을 곧 완료로 만든다.** 그래서 실제 모습은 「아무도 승인하지
25
+ * 않는다」가 아니라 **「올린 사람이 자기 건을 승인한다」**다. 그 차이를 문장이 말한다 — 문장은
26
+ * 서버가 만든다(§`work-door.ts`). 화면이 상태를 보고 문장을 다시 지으면 두 벌이 된다.
27
+ *
28
+ * ── 할 일이 있는 것부터 ────────────────────────────────────────────────────
29
+ * 선언 순서로 늘어놓으면 서 있는 것과 빈 것이 섞이고, 읽는 사람이 열한 줄을 훑어야 한다. **정할
30
+ * 것이 남은 것을 위로** 올린다 — 이 화면을 여는 이유가 그것이다.
31
+ * ═══════════════════════════════════════════════════════════════════════════
32
+ */
33
+ const DOORS = gql `
34
+ {
35
+ workDoors {
36
+ summary
37
+ todo
38
+ seatsStanding
39
+ seatsOffered
40
+ doors {
41
+ name
42
+ label
43
+ kind
44
+ standing
45
+ approvers
46
+ from
47
+ because
48
+ }
49
+ }
50
+ }
51
+ `;
52
+ /**
53
+ * 조치할 것 · 서 있는 것 · 안 쓰기로 한 것 순. 같은 무리 안에서는 선언 순서를 지킨다.
54
+ *
55
+ * 안 쓰기로 한 것을 맨 아래 두는 이유는 **그것이 할 일이 아니어서**다. 위에 섞으면 이 화면을 여는
56
+ * 이유(무엇을 채워야 하나)가 안 보인다.
57
+ */
58
+ const ORDER = { missing: 0, off: 1, on: 2, 'not-used': 3 };
59
+ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
60
+ constructor() {
61
+ super(...arguments);
62
+ this.summary = '';
63
+ this.todo = 0;
64
+ this.seatsStanding = 0;
65
+ this.seatsOffered = 0;
66
+ this.readFailure = '';
67
+ }
68
+ static { this.styles = [
69
+ ProseStyles,
70
+ SurfaceStyles,
71
+ css `
72
+ :host {
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 16px;
76
+ padding: 12px;
77
+ overflow: auto;
78
+ }
79
+
80
+ /*
81
+ * These classes were used here but defined only inside disposition-policy-page's shadow root,
82
+ * so this screen drew them unstyled. Shadow roots do not share rules; each page states its own.
83
+ */
84
+ .sheet {
85
+ max-width: var(--ui-measure, min(1080px, 100%));
86
+ }
87
+
88
+ .lead {
89
+ margin: 0;
90
+ color: var(--ui-muted, #57676f);
91
+ font-size: var(--ui-t-small, 11.5px);
92
+ max-width: 60ch;
93
+ }
94
+
95
+ .state {
96
+ margin: 0 0 4px;
97
+ color: var(--ui-ink, #17242c);
98
+ font-weight: 600;
99
+ }
100
+
101
+ .none {
102
+ margin: 0;
103
+ color: var(--ui-muted, #57676f);
104
+ }
105
+
106
+ .blocked {
107
+ border-left: 3px solid var(--ui-crit, #c93f30);
108
+ padding: 10px 12px;
109
+ background: var(--ui-line-soft, #eef1f3);
110
+ border-radius: var(--ui-r-small, 6px);
111
+ }
112
+
113
+ .blocked .why {
114
+ margin-top: 4px;
115
+ font-size: var(--ui-t-small, 11.5px);
116
+ color: var(--ui-muted, #57676f);
117
+ white-space: pre-wrap;
118
+ }
119
+
120
+ .rule-actions {
121
+ margin-top: var(--spacing-small, 4px);
122
+ }
123
+
124
+ .door {
125
+ display: flex;
126
+ align-items: baseline;
127
+ gap: 8px;
128
+ padding: 10px 0;
129
+ border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
130
+ }
131
+
132
+ .door:last-child {
133
+ border-bottom: none;
134
+ }
135
+
136
+ .name {
137
+ flex: 0 0 auto;
138
+ font-weight: 600;
139
+ }
140
+
141
+ /* 상태는 글자로도 말한다 — 색만으로 가르면 색을 못 보는 사람에게 아무 말도 안 한다. */
142
+ .mark {
143
+ flex: 0 0 auto;
144
+ font-size: 0.85em;
145
+ padding: 1px 8px;
146
+ border-radius: 10px;
147
+ border: 1px solid currentColor;
148
+ }
149
+
150
+ .mark.on {
151
+ color: var(--md-sys-color-primary, #3f6b3f);
152
+ }
153
+
154
+ .mark.off {
155
+ color: var(--md-sys-color-tertiary, #7a5b12);
156
+ }
157
+
158
+ .mark.missing {
159
+ color: var(--md-sys-color-error, #a33);
160
+ }
161
+
162
+ /* 할 일이 아니므로 눈에 덜 띈다. 지우지는 않는다 — 껐다는 사실 자체가 읽혀야 한다. */
163
+ .mark.not-used {
164
+ color: var(--md-sys-color-outline, #777);
165
+ }
166
+
167
+ .door.not-used {
168
+ opacity: 0.7;
169
+ }
170
+
171
+ /* 종류는 낱말로 말한다 — 결재와 배정은 비었을 때 결과가 반대다. */
172
+ .kind {
173
+ flex: 0 0 auto;
174
+ font-size: 0.8em;
175
+ opacity: 0.65;
176
+ }
177
+
178
+ .why {
179
+ flex: 1 1 auto;
180
+ opacity: 0.85;
181
+ }
182
+
183
+ .from {
184
+ flex: 0 0 auto;
185
+ font-size: 0.85em;
186
+ opacity: 0.7;
187
+ }
188
+
189
+ ol.next {
190
+ margin: 0;
191
+ padding-left: 20px;
192
+ display: flex;
193
+ flex-direction: column;
194
+ gap: 6px;
195
+ }
196
+
197
+ ol.next li {
198
+ line-height: 1.5;
199
+ }
200
+
201
+ ol.next a {
202
+ margin-left: 4px;
203
+ color: var(--md-sys-color-primary, #3f6cd4);
204
+ }
205
+ `
206
+ ]; }
207
+ get context() {
208
+ return { title: i18next.t('menu.work-doors') };
209
+ }
210
+ async pageInitialized() {
211
+ await this.load();
212
+ }
213
+ async pageUpdated(changes, lifecycle) {
214
+ if (lifecycle.active && !this.doors) {
215
+ await this.load();
216
+ }
217
+ }
218
+ async load() {
219
+ this.readFailure = '';
220
+ const response = await client.query({ query: DOORS, fetchPolicy: 'no-cache' });
221
+ /*
222
+ * 못 읽은 것을 빈 목록으로 그리지 않는다. 「결재 문이 하나도 없습니다」와 「못 읽었습니다」는
223
+ * 사람이 할 일이 다르다 — 앞은 설치를 보러 갈 일이고 뒤는 다시 눌러 볼 일이다.
224
+ */
225
+ if (response.errors?.length) {
226
+ this.readFailure = response.errors[0].message;
227
+ return;
228
+ }
229
+ /*
230
+ * 전선의 철자를 여기서 **한 번** 옮긴다(§`door-words`). 안 옮기면 i18n 키도 CSS 클래스도
231
+ * 어긋나는데 **오류가 하나도 안 난다** — 키 글자가 화면에 뜨고 상태 색이 투명해질 뿐이다.
232
+ */
233
+ this.doors = (response.data?.workDoors?.doors ?? []).map((one) => ({
234
+ ...one,
235
+ standing: standingWord(one.standing) ?? one.standing,
236
+ kind: kindWord(one.kind) ?? one.kind
237
+ }));
238
+ this.summary = response.data?.workDoors?.summary ?? '';
239
+ this.todo = response.data?.workDoors?.todo ?? 0;
240
+ this.seatsStanding = response.data?.workDoors?.seatsStanding ?? 0;
241
+ this.seatsOffered = response.data?.workDoors?.seatsOffered ?? 0;
242
+ }
243
+ /**
244
+ * 다음에 할 일 — **순서대로.**
245
+ *
246
+ * ── 문제만 말하는 화면이었다 ──────────────────────────────────────────
247
+ * 열두 줄이 「비었습니다」라고 적혀 있고, 어디로 가야 채우는지는 어디에도 없었다. 이 저장소에서
248
+ * 가장 자주 나는 결함을 내 화면이 하고 있었다 — **자리는 있고 길이 없다.**
249
+ *
250
+ * ── 순서를 지키는 이유 ────────────────────────────────────────────────
251
+ * 결재선은 역할을 가리킨다. 역할이 하나도 없는 공장에서 양식 화면으로 가면 **결재선 칸이 빈
252
+ * 목록**이고, 사람은 헛걸음한 채 무엇이 잘못됐는지 모르고 돌아온다. 그래서 자리가 안 섰으면
253
+ * 자리부터 말한다.
254
+ *
255
+ * 여기서 고치게 하지 않는다. 역할도 업무 양식도 **남의 화면이 임자**이고, 같은 일을 하는 자리를
256
+ * 둘 만들면 한쪽이 낡는다. 이 화면이 하는 일은 **가리키는 것**이다.
257
+ */
258
+ renderNext() {
259
+ if (!this.todo) {
260
+ return '';
261
+ }
262
+ const seatsShort = this.seatsStanding < this.seatsOffered;
263
+ return html `
264
+ <section class="sheet ui-card">
265
+ <span class="ui-label">${i18next.t('title.work-doors-next')}</span>
266
+ <ol class="next">
267
+ ${seatsShort
268
+ ? html `<li>
269
+ ${i18next.t('text.work-doors-next-seats', {
270
+ standing: this.seatsStanding,
271
+ offered: this.seatsOffered
272
+ })}
273
+ <a href="roles">${i18next.t('field.roles')}</a>
274
+ <!-- field.roles 는 auth-ui 설치본의 키다. 내가 menu.roles 를 썼다가 고쳤다 —
275
+ 그 키는 어디에도 없어서 화면에 키 글자가 그대로 떴을 것이다. -->
276
+ </li>`
277
+ : ''}
278
+ <li>
279
+ ${i18next.t('text.work-doors-next-line', { count: this.todo })}
280
+ <a href="meta-activity-define">${i18next.t('menu.activity-define')}</a>
281
+ </li>
282
+ </ol>
283
+ </section>
284
+ `;
285
+ }
286
+ render() {
287
+ if (this.readFailure) {
288
+ return html `<div class="sheet blocked">
289
+ ${i18next.t('text.work-doors-read-failed')}
290
+ <div class="why">${this.readFailure}</div>
291
+ <div class="rule-actions">
292
+ <md-text-button @click=${() => this.load()}>${i18next.t('button.try-again')}</md-text-button>
293
+ </div>
294
+ </div>`;
295
+ }
296
+ if (!this.doors) {
297
+ return html `<div class="none">${i18next.t('text.loading')}</div>`;
298
+ }
299
+ const sorted = [...this.doors].sort((a, b) => ORDER[a.standing] - ORDER[b.standing]);
300
+ return html `
301
+ <div class="sheet">
302
+ <p class="lead">${i18next.t('text.work-doors-lead')}</p>
303
+ </div>
304
+
305
+ <section class="sheet ui-card">
306
+ <span class="ui-label">${i18next.t('title.work-doors')}</span>
307
+ <p class="state">${this.summary}</p>
308
+ ${sorted.map(door => this.renderDoor(door))}
309
+ </section>
310
+
311
+ ${this.renderNext()}
312
+ `;
313
+ }
314
+ renderDoor(door) {
315
+ return html `
316
+ <div class="door ${door.standing}">
317
+ <span class="name">${i18next.t(door.label)}</span>
318
+ <span class="kind">${i18next.t(`text.door-kind-${door.kind}`)}</span>
319
+ <span class="mark ${door.standing}">${i18next.t(`text.door-standing-${door.standing}`)}</span>
320
+ <span class="why">${door.because}</span>
321
+ ${
322
+ /*
323
+ * 자기 것이 아닌 양식을 쓰는 것은 정상이지만, 안 말하면 「정한 적 없는데」가 된다.
324
+ *
325
+ * ⚠ **차 있는 것과 비어 있는 것은 다른 말이다.** 「본사에서 정했습니다」를 결재자가 없는
326
+ * 줄에 달면 한 줄이 스스로 모순된다 — 정한 것이 없는데 정했다고 적힌다. 비어 있으면
327
+ * 말할 수 있는 것은 **양식의 임자**뿐이고, 그 사실은 「여기서는 못 고칩니다」를 뜻해서
328
+ * 오히려 더 중요하다.
329
+ */
330
+ door.from
331
+ ? html `<span class="from"
332
+ >${door.standing === 'on'
333
+ ? i18next.t('text.door-filled-from', { name: door.from })
334
+ : i18next.t('text.door-form-from', { name: door.from })}</span
335
+ >`
336
+ : ''}
337
+ </div>
338
+ `;
339
+ }
340
+ };
341
+ __decorate([
342
+ state(),
343
+ __metadata("design:type", Array)
344
+ ], WorkDoorsPage.prototype, "doors", void 0);
345
+ __decorate([
346
+ state(),
347
+ __metadata("design:type", Object)
348
+ ], WorkDoorsPage.prototype, "summary", void 0);
349
+ __decorate([
350
+ state(),
351
+ __metadata("design:type", Object)
352
+ ], WorkDoorsPage.prototype, "todo", void 0);
353
+ __decorate([
354
+ state(),
355
+ __metadata("design:type", Object)
356
+ ], WorkDoorsPage.prototype, "seatsStanding", void 0);
357
+ __decorate([
358
+ state(),
359
+ __metadata("design:type", Object)
360
+ ], WorkDoorsPage.prototype, "seatsOffered", void 0);
361
+ __decorate([
362
+ state(),
363
+ __metadata("design:type", Object)
364
+ ], WorkDoorsPage.prototype, "readFailure", void 0);
365
+ WorkDoorsPage = __decorate([
366
+ customElement('work-doors-page')
367
+ ], WorkDoorsPage);
368
+ export { WorkDoorsPage };
369
+ //# sourceMappingURL=work-doors-page.js.map
@@ -0,0 +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 { 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\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 <li>\n ${i18next.t('text.work-doors-next-line', { count: this.todo })}\n <a href=\"meta-activity-define\">${i18next.t('menu.activity-define')}</a>\n </li>\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 <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 +1 @@
1
- export default function route(page: string): "activity" | "activity-thread" | "activity-thread-view" | "activity-approval" | "draft-list" | "todo-list" | "done-list" | "approval-pending-list" | "done-list-calendar" | "activity-catalog" | "approval-done-list" | "pickable-list" | "starter-list" | "reporter-list" | "activity-list" | "activity-instance-start" | "activity-instance-list" | "activity-instance-search" | "activity-template-list" | "activity-store" | "activity-thread-list" | "activity-approval-list" | "installable-activity-list" | "activity-stats-list" | undefined;
1
+ export default function route(page: string): "activity" | "activity-thread" | "activity-thread-view" | "activity-approval" | "work-doors-page" | "draft-list" | "todo-list" | "done-list" | "approval-pending-list" | "done-list-calendar" | "activity-catalog" | "approval-done-list" | "pickable-list" | "starter-list" | "reporter-list" | "activity-list" | "activity-instance-start" | "activity-instance-list" | "activity-instance-search" | "activity-template-list" | "activity-store" | "activity-thread-list" | "activity-approval-list" | "installable-activity-list" | "activity-stats-list" | undefined;
@@ -6,6 +6,9 @@ export default function route(page) {
6
6
  case 'activity-catalog':
7
7
  import('./pages/activity-catalog/activity-catalog-page.js');
8
8
  return page;
9
+ case 'work-doors-page':
10
+ import('./pages/work-door/work-doors-page.js');
11
+ return page;
9
12
  case 'todo-list':
10
13
  import('./pages/todo/todo-list-page.js');
11
14
  return page;
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,oBAAoB;YACvB,MAAM,CAAC,yCAAyC,CAAC,CAAA;YACjD,OAAO,IAAI,CAAA;QAEb,KAAK,kBAAkB;YACrB,MAAM,CAAC,mDAAmD,CAAC,CAAA;YAC3D,OAAO,IAAI,CAAA;QAEb,KAAK,WAAW;YACd,MAAM,CAAC,gCAAgC,CAAC,CAAA;YACxC,OAAO,IAAI,CAAA;QAEb,KAAK,WAAW;YACd,MAAM,CAAC,gCAAgC,CAAC,CAAA;YACxC,OAAO,IAAI,CAAA;QAEb,KAAK,uBAAuB;YAC1B,MAAM,CAAC,4CAA4C,CAAC,CAAA;YACpD,OAAO,IAAI,CAAA;QAEb,KAAK,oBAAoB;YACvB,MAAM,CAAC,yCAAyC,CAAC,CAAA;YACjD,OAAO,IAAI,CAAA;QAEb,KAAK,YAAY;YACf,MAAM,CAAC,iCAAiC,CAAC,CAAA;YACzC,OAAO,IAAI,CAAA;QAEb,KAAK,eAAe;YAClB,MAAM,CAAC,oCAAoC,CAAC,CAAA;YAC5C,OAAO,IAAI,CAAA;QAEb,KAAK,cAAc;YACjB,MAAM,CAAC,uCAAuC,CAAC,CAAA;YAC/C,OAAO,IAAI,CAAA;QAEb,KAAK,eAAe;YAClB,MAAM,CAAC,mDAAmD,CAAC,CAAA;YAC3D,OAAO,IAAI,CAAA;QAEb,KAAK,eAAe;YAClB,MAAM,CAAC,wCAAwC,CAAC,CAAA;YAChD,OAAO,IAAI,CAAA;QAEb,KAAK,UAAU;YACb,MAAM,CAAC,mCAAmC,CAAC,CAAA;YAC3C,OAAO,IAAI,CAAA;QAEb,KAAK,yBAAyB;YAC5B,MAAM,CAAC,2DAA2D,CAAC,CAAA;YACnE,OAAO,IAAI,CAAA;QAEb,KAAK,iBAAiB;YACpB,MAAM,CAAC,iDAAiD,CAAC,CAAA;YACzD,OAAO,IAAI,CAAA;QAEb,KAAK,sBAAsB;YACzB,MAAM,CAAC,sDAAsD,CAAC,CAAA;YAC9D,OAAO,IAAI,CAAA;QAEb,KAAK,mBAAmB;YACtB,MAAM,CAAC,qDAAqD,CAAC,CAAA;YAC7D,OAAO,IAAI,CAAA;QAEb,KAAK,wBAAwB;YAC3B,MAAM,CAAC,0DAA0D,CAAC,CAAA;YAClE,OAAO,IAAI,CAAA;QAEb,KAAK,0BAA0B;YAC7B,MAAM,CAAC,4DAA4D,CAAC,CAAA;YACpE,OAAO,IAAI,CAAA;QAEb,KAAK,wBAAwB;YAC3B,MAAM,CAAC,0DAA0D,CAAC,CAAA;YAClE,OAAO,IAAI,CAAA;QAEb,KAAK,gBAAgB;YACnB,MAAM,CAAC,+CAA+C,CAAC,CAAA;YACvD,OAAO,IAAI,CAAA;QAEb,KAAK,sBAAsB;YACzB,MAAM,CAAC,sDAAsD,CAAC,CAAA;YAC9D,OAAO,IAAI,CAAA;QAEb,KAAK,wBAAwB;YAC3B,MAAM,CAAC,uDAAuD,CAAC,CAAA;YAC/D,OAAO,IAAI,CAAA;QAEb,KAAK,2BAA2B;YAC9B,MAAM,CAAC,6DAA6D,CAAC,CAAA;YACrE,OAAO,IAAI,CAAA;QAEb,KAAK,qBAAqB;YACxB,MAAM,CAAC,iDAAiD,CAAC,CAAA;YACzD,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'done-list-calendar':\n import('./pages/todo/done-list-calendar-page.js')\n return page\n\n case 'activity-catalog':\n import('./pages/activity-catalog/activity-catalog-page.js')\n return page\n\n case 'todo-list':\n import('./pages/todo/todo-list-page.js')\n return page\n\n case 'done-list':\n import('./pages/todo/done-list-page.js')\n return page\n\n case 'approval-pending-list':\n import('./pages/todo/approval-pending-list-page.js')\n return page\n\n case 'approval-done-list':\n import('./pages/todo/approval-done-list-page.js')\n return page\n\n case 'draft-list':\n import('./pages/todo/draft-list-page.js')\n return page\n\n case 'pickable-list':\n import('./pages/todo/pickable-list-page.js')\n return page\n\n case 'starter-list':\n import('./pages/activity/starter-list-page.js')\n return page\n\n case 'reporter-list':\n import('./pages/activity-supervisor/reporter-list-page.js')\n return page\n\n case 'activity-list':\n import('./pages/activity/activity-list-page.js')\n return page\n\n case 'activity':\n import('./pages/activity/activity-page.js')\n return page\n\n case 'activity-instance-start':\n import('./pages/activity-instance/activity-instance-start-page.js')\n return page\n\n case 'activity-thread':\n import('./pages/activity-thread/activity-thread-page.js')\n return page\n\n case 'activity-thread-view':\n import('./pages/activity-thread/activity-thread-view-page.js')\n return page\n\n case 'activity-approval':\n import('./pages/activity-approval/activity-approval-page.js')\n return page\n\n case 'activity-instance-list':\n import('./pages/activity-instance/activity-instance-list-page.js')\n return page\n\n case 'activity-instance-search':\n import('./pages/activity-instance/activity-instance-search-page.js')\n return page\n\n case 'activity-template-list':\n import('./pages/activity-template/activity-template-list-page.js')\n return page\n\n case 'activity-store':\n import('./pages/activity-store/activity-store-page.js')\n return page\n\n case 'activity-thread-list':\n import('./pages/activity-thread/activity-thread-list-page.js')\n return page\n\n case 'activity-approval-list':\n import('./pages/activity-approval/activity-approval-list-page')\n return page\n\n case 'installable-activity-list':\n import('./pages/installable-activity/installable-activity-list-page')\n return page\n\n case 'activity-stats-list':\n import('./pages/activity-stats/activity-stats-list-page')\n return page\n }\n}\n"]}
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,oBAAoB;YACvB,MAAM,CAAC,yCAAyC,CAAC,CAAA;YACjD,OAAO,IAAI,CAAA;QAEb,KAAK,kBAAkB;YACrB,MAAM,CAAC,mDAAmD,CAAC,CAAA;YAC3D,OAAO,IAAI,CAAA;QAEb,KAAK,iBAAiB;YACpB,MAAM,CAAC,sCAAsC,CAAC,CAAA;YAC9C,OAAO,IAAI,CAAA;QAEb,KAAK,WAAW;YACd,MAAM,CAAC,gCAAgC,CAAC,CAAA;YACxC,OAAO,IAAI,CAAA;QAEb,KAAK,WAAW;YACd,MAAM,CAAC,gCAAgC,CAAC,CAAA;YACxC,OAAO,IAAI,CAAA;QAEb,KAAK,uBAAuB;YAC1B,MAAM,CAAC,4CAA4C,CAAC,CAAA;YACpD,OAAO,IAAI,CAAA;QAEb,KAAK,oBAAoB;YACvB,MAAM,CAAC,yCAAyC,CAAC,CAAA;YACjD,OAAO,IAAI,CAAA;QAEb,KAAK,YAAY;YACf,MAAM,CAAC,iCAAiC,CAAC,CAAA;YACzC,OAAO,IAAI,CAAA;QAEb,KAAK,eAAe;YAClB,MAAM,CAAC,oCAAoC,CAAC,CAAA;YAC5C,OAAO,IAAI,CAAA;QAEb,KAAK,cAAc;YACjB,MAAM,CAAC,uCAAuC,CAAC,CAAA;YAC/C,OAAO,IAAI,CAAA;QAEb,KAAK,eAAe;YAClB,MAAM,CAAC,mDAAmD,CAAC,CAAA;YAC3D,OAAO,IAAI,CAAA;QAEb,KAAK,eAAe;YAClB,MAAM,CAAC,wCAAwC,CAAC,CAAA;YAChD,OAAO,IAAI,CAAA;QAEb,KAAK,UAAU;YACb,MAAM,CAAC,mCAAmC,CAAC,CAAA;YAC3C,OAAO,IAAI,CAAA;QAEb,KAAK,yBAAyB;YAC5B,MAAM,CAAC,2DAA2D,CAAC,CAAA;YACnE,OAAO,IAAI,CAAA;QAEb,KAAK,iBAAiB;YACpB,MAAM,CAAC,iDAAiD,CAAC,CAAA;YACzD,OAAO,IAAI,CAAA;QAEb,KAAK,sBAAsB;YACzB,MAAM,CAAC,sDAAsD,CAAC,CAAA;YAC9D,OAAO,IAAI,CAAA;QAEb,KAAK,mBAAmB;YACtB,MAAM,CAAC,qDAAqD,CAAC,CAAA;YAC7D,OAAO,IAAI,CAAA;QAEb,KAAK,wBAAwB;YAC3B,MAAM,CAAC,0DAA0D,CAAC,CAAA;YAClE,OAAO,IAAI,CAAA;QAEb,KAAK,0BAA0B;YAC7B,MAAM,CAAC,4DAA4D,CAAC,CAAA;YACpE,OAAO,IAAI,CAAA;QAEb,KAAK,wBAAwB;YAC3B,MAAM,CAAC,0DAA0D,CAAC,CAAA;YAClE,OAAO,IAAI,CAAA;QAEb,KAAK,gBAAgB;YACnB,MAAM,CAAC,+CAA+C,CAAC,CAAA;YACvD,OAAO,IAAI,CAAA;QAEb,KAAK,sBAAsB;YACzB,MAAM,CAAC,sDAAsD,CAAC,CAAA;YAC9D,OAAO,IAAI,CAAA;QAEb,KAAK,wBAAwB;YAC3B,MAAM,CAAC,uDAAuD,CAAC,CAAA;YAC/D,OAAO,IAAI,CAAA;QAEb,KAAK,2BAA2B;YAC9B,MAAM,CAAC,6DAA6D,CAAC,CAAA;YACrE,OAAO,IAAI,CAAA;QAEb,KAAK,qBAAqB;YACxB,MAAM,CAAC,iDAAiD,CAAC,CAAA;YACzD,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'done-list-calendar':\n import('./pages/todo/done-list-calendar-page.js')\n return page\n\n case 'activity-catalog':\n import('./pages/activity-catalog/activity-catalog-page.js')\n return page\n\n case 'work-doors-page':\n import('./pages/work-door/work-doors-page.js')\n return page\n\n case 'todo-list':\n import('./pages/todo/todo-list-page.js')\n return page\n\n case 'done-list':\n import('./pages/todo/done-list-page.js')\n return page\n\n case 'approval-pending-list':\n import('./pages/todo/approval-pending-list-page.js')\n return page\n\n case 'approval-done-list':\n import('./pages/todo/approval-done-list-page.js')\n return page\n\n case 'draft-list':\n import('./pages/todo/draft-list-page.js')\n return page\n\n case 'pickable-list':\n import('./pages/todo/pickable-list-page.js')\n return page\n\n case 'starter-list':\n import('./pages/activity/starter-list-page.js')\n return page\n\n case 'reporter-list':\n import('./pages/activity-supervisor/reporter-list-page.js')\n return page\n\n case 'activity-list':\n import('./pages/activity/activity-list-page.js')\n return page\n\n case 'activity':\n import('./pages/activity/activity-page.js')\n return page\n\n case 'activity-instance-start':\n import('./pages/activity-instance/activity-instance-start-page.js')\n return page\n\n case 'activity-thread':\n import('./pages/activity-thread/activity-thread-page.js')\n return page\n\n case 'activity-thread-view':\n import('./pages/activity-thread/activity-thread-view-page.js')\n return page\n\n case 'activity-approval':\n import('./pages/activity-approval/activity-approval-page.js')\n return page\n\n case 'activity-instance-list':\n import('./pages/activity-instance/activity-instance-list-page.js')\n return page\n\n case 'activity-instance-search':\n import('./pages/activity-instance/activity-instance-search-page.js')\n return page\n\n case 'activity-template-list':\n import('./pages/activity-template/activity-template-list-page.js')\n return page\n\n case 'activity-store':\n import('./pages/activity-store/activity-store-page.js')\n return page\n\n case 'activity-thread-list':\n import('./pages/activity-thread/activity-thread-list-page.js')\n return page\n\n case 'activity-approval-list':\n import('./pages/activity-approval/activity-approval-list-page')\n return page\n\n case 'installable-activity-list':\n import('./pages/installable-activity/installable-activity-list-page')\n return page\n\n case 'activity-stats-list':\n import('./pages/activity-stats/activity-stats-list-page')\n return page\n }\n}\n"]}