@things-factory/worklist 10.0.19 → 10.1.1

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 (215) hide show
  1. package/dist-client/activity-cells.d.ts +21 -0
  2. package/dist-client/activity-cells.js +117 -0
  3. package/dist-client/activity-cells.js.map +1 -0
  4. package/dist-client/activity-summary-generator.js +57 -11
  5. package/dist-client/activity-summary-generator.js.map +1 -1
  6. package/dist-client/approval-progress.d.ts +18 -0
  7. package/dist-client/approval-progress.js +31 -0
  8. package/dist-client/approval-progress.js.map +1 -0
  9. package/dist-client/bootstrap.js +6 -11
  10. package/dist-client/bootstrap.js.map +1 -1
  11. package/dist-client/chip-filters.d.ts +53 -0
  12. package/dist-client/chip-filters.js +60 -0
  13. package/dist-client/chip-filters.js.map +1 -0
  14. package/dist-client/components/activity-approval-ribon.d.ts +19 -0
  15. package/dist-client/components/activity-approval-ribon.js +50 -126
  16. package/dist-client/components/activity-approval-ribon.js.map +1 -1
  17. package/dist-client/components/activity-fields.d.ts +50 -0
  18. package/dist-client/components/activity-fields.js +393 -0
  19. package/dist-client/components/activity-fields.js.map +1 -0
  20. package/dist-client/components/activity-instance-ribon.d.ts +19 -0
  21. package/dist-client/components/activity-instance-ribon.js +55 -122
  22. package/dist-client/components/activity-instance-ribon.js.map +1 -1
  23. package/dist-client/components/activity-thread-ribon.d.ts +25 -0
  24. package/dist-client/components/activity-thread-ribon.js +64 -121
  25. package/dist-client/components/activity-thread-ribon.js.map +1 -1
  26. package/dist-client/components/activity-thread-timeline.d.ts +14 -2
  27. package/dist-client/components/activity-thread-timeline.js +234 -104
  28. package/dist-client/components/activity-thread-timeline.js.map +1 -1
  29. package/dist-client/components/state-ribon.d.ts +40 -0
  30. package/dist-client/components/state-ribon.js +200 -0
  31. package/dist-client/components/state-ribon.js.map +1 -0
  32. package/dist-client/due.d.ts +16 -0
  33. package/dist-client/due.js +78 -0
  34. package/dist-client/due.js.map +1 -0
  35. package/dist-client/pages/activity/activity-list-page.js +4 -6
  36. package/dist-client/pages/activity/activity-list-page.js.map +1 -1
  37. package/dist-client/pages/activity-approval/activity-approval-list-page.js +2 -1
  38. package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
  39. package/dist-client/pages/activity-approval/activity-approval-page.d.ts +52 -5
  40. package/dist-client/pages/activity-approval/activity-approval-page.js +265 -118
  41. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  42. package/dist-client/pages/activity-instance/activity-instance-list-page.d.ts +0 -1
  43. package/dist-client/pages/activity-instance/activity-instance-list-page.js +8 -24
  44. package/dist-client/pages/activity-instance/activity-instance-list-page.js.map +1 -1
  45. package/dist-client/pages/activity-instance/activity-instance-start-page.d.ts +1 -0
  46. package/dist-client/pages/activity-instance/activity-instance-start-page.js +7 -0
  47. package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
  48. package/dist-client/pages/activity-instance/activity-instance-view.js +1 -1
  49. package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
  50. package/dist-client/pages/activity-stats/activity-stats-list-page.js +2 -1
  51. package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
  52. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +30 -4
  53. package/dist-client/pages/activity-thread/activity-thread-page.js +142 -66
  54. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  55. package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +17 -6
  56. package/dist-client/pages/activity-thread/activity-thread-view-page.js +118 -65
  57. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  58. package/dist-client/pages/activity-thread/activity-thread-view.d.ts +24 -7
  59. package/dist-client/pages/activity-thread/activity-thread-view.js +84 -308
  60. package/dist-client/pages/activity-thread/activity-thread-view.js.map +1 -1
  61. package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +1 -1
  62. package/dist-client/pages/installable-activity/installable-activity-list-page.js +13 -9
  63. package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
  64. package/dist-client/pages/todo/approval-done-list-page.d.ts +1 -0
  65. package/dist-client/pages/todo/approval-done-list-page.js +111 -58
  66. package/dist-client/pages/todo/approval-done-list-page.js.map +1 -1
  67. package/dist-client/pages/todo/approval-pending-list-page.d.ts +32 -1
  68. package/dist-client/pages/todo/approval-pending-list-page.js +485 -130
  69. package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
  70. package/dist-client/pages/todo/done-list-calendar-page.d.ts +2 -0
  71. package/dist-client/pages/todo/done-list-calendar-page.js +213 -29
  72. package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
  73. package/dist-client/pages/todo/done-list-page.d.ts +1 -0
  74. package/dist-client/pages/todo/done-list-page.js +126 -62
  75. package/dist-client/pages/todo/done-list-page.js.map +1 -1
  76. package/dist-client/pages/todo/draft-list-page.d.ts +1 -0
  77. package/dist-client/pages/todo/draft-list-page.js +82 -34
  78. package/dist-client/pages/todo/draft-list-page.js.map +1 -1
  79. package/dist-client/pages/todo/pickable-list-page.js +6 -10
  80. package/dist-client/pages/todo/pickable-list-page.js.map +1 -1
  81. package/dist-client/pages/todo/todo-list-page.d.ts +1 -1
  82. package/dist-client/pages/todo/todo-list-page.js +102 -78
  83. package/dist-client/pages/todo/todo-list-page.js.map +1 -1
  84. package/dist-client/route.d.ts +1 -1
  85. package/dist-client/route.js +0 -6
  86. package/dist-client/route.js.map +1 -1
  87. package/dist-client/templates/activity-approval-context-template.js +60 -138
  88. package/dist-client/templates/activity-approval-context-template.js.map +1 -1
  89. package/dist-client/templates/activity-instance-context-template.js +13 -172
  90. package/dist-client/templates/activity-instance-context-template.js.map +1 -1
  91. package/dist-client/templates/activity-thread-context-template.js +39 -131
  92. package/dist-client/templates/activity-thread-context-template.js.map +1 -1
  93. package/dist-client/templates/context-panel.d.ts +26 -0
  94. package/dist-client/templates/context-panel.js +182 -0
  95. package/dist-client/templates/context-panel.js.map +1 -0
  96. package/dist-client/tsconfig.tsbuildinfo +1 -1
  97. package/dist-client/when.d.ts +4 -0
  98. package/dist-client/when.js +72 -0
  99. package/dist-client/when.js.map +1 -0
  100. package/dist-server/controllers/activity-approval/_abort.js +2 -1
  101. package/dist-server/controllers/activity-approval/_abort.js.map +1 -1
  102. package/dist-server/controllers/activity-approval/approval-line.d.ts +106 -0
  103. package/dist-server/controllers/activity-approval/approval-line.js +151 -0
  104. package/dist-server/controllers/activity-approval/approval-line.js.map +1 -0
  105. package/dist-server/controllers/activity-approval/approve.js +61 -51
  106. package/dist-server/controllers/activity-approval/approve.js.map +1 -1
  107. package/dist-server/controllers/activity-approval/claim.d.ts +42 -0
  108. package/dist-server/controllers/activity-approval/claim.js +77 -0
  109. package/dist-server/controllers/activity-approval/claim.js.map +1 -0
  110. package/dist-server/controllers/activity-approval/create-approval.d.ts +41 -0
  111. package/dist-server/controllers/activity-approval/create-approval.js +78 -0
  112. package/dist-server/controllers/activity-approval/create-approval.js.map +1 -0
  113. package/dist-server/controllers/activity-approval/delegate.d.ts +16 -0
  114. package/dist-server/controllers/activity-approval/delegate.js +43 -7
  115. package/dist-server/controllers/activity-approval/delegate.js.map +1 -1
  116. package/dist-server/controllers/activity-approval/opinion.d.ts +10 -0
  117. package/dist-server/controllers/activity-approval/opinion.js +43 -0
  118. package/dist-server/controllers/activity-approval/opinion.js.map +1 -0
  119. package/dist-server/controllers/activity-approval/reject.js +21 -1
  120. package/dist-server/controllers/activity-approval/reject.js.map +1 -1
  121. package/dist-server/controllers/activity-approval/save.js +26 -2
  122. package/dist-server/controllers/activity-approval/save.js.map +1 -1
  123. package/dist-server/controllers/activity-installation/activity-installation-controller.d.ts +26 -0
  124. package/dist-server/controllers/activity-installation/activity-installation-controller.js +45 -0
  125. package/dist-server/controllers/activity-installation/activity-installation-controller.js.map +1 -1
  126. package/dist-server/controllers/activity-instance/approval-expectation.d.ts +34 -0
  127. package/dist-server/controllers/activity-instance/approval-expectation.js +18 -0
  128. package/dist-server/controllers/activity-instance/approval-expectation.js.map +1 -0
  129. package/dist-server/controllers/activity-instance/issue.js +32 -0
  130. package/dist-server/controllers/activity-instance/issue.js.map +1 -1
  131. package/dist-server/controllers/activity-thread/restart.js +17 -1
  132. package/dist-server/controllers/activity-thread/restart.js.map +1 -1
  133. package/dist-server/controllers/activity-thread/submit.js +23 -43
  134. package/dist-server/controllers/activity-thread/submit.js.map +1 -1
  135. package/dist-server/controllers/common.js +46 -0
  136. package/dist-server/controllers/common.js.map +1 -1
  137. package/dist-server/controllers/index.d.ts +1 -0
  138. package/dist-server/controllers/index.js +8 -0
  139. package/dist-server/controllers/index.js.map +1 -1
  140. package/dist-server/service/activity/activity-mutation.js +25 -5
  141. package/dist-server/service/activity/activity-mutation.js.map +1 -1
  142. package/dist-server/service/activity/activity-type.d.ts +14 -0
  143. package/dist-server/service/activity/activity-type.js +8 -0
  144. package/dist-server/service/activity/activity-type.js.map +1 -1
  145. package/dist-server/service/activity/activity.d.ts +18 -0
  146. package/dist-server/service/activity/activity.js +5 -0
  147. package/dist-server/service/activity/activity.js.map +1 -1
  148. package/dist-server/service/activity-approval/activity-approval-mutation.js +3 -2
  149. package/dist-server/service/activity-approval/activity-approval-mutation.js.map +1 -1
  150. package/dist-server/service/activity-approval/activity-approval-query.d.ts +19 -1
  151. package/dist-server/service/activity-approval/activity-approval-query.js +186 -56
  152. package/dist-server/service/activity-approval/activity-approval-query.js.map +1 -1
  153. package/dist-server/service/activity-approval/activity-approval.d.ts +29 -1
  154. package/dist-server/service/activity-approval/activity-approval.js +19 -0
  155. package/dist-server/service/activity-approval/activity-approval.js.map +1 -1
  156. package/dist-server/service/activity-approval/my-pending-approvals.d.ts +21 -0
  157. package/dist-server/service/activity-approval/my-pending-approvals.js +40 -0
  158. package/dist-server/service/activity-approval/my-pending-approvals.js.map +1 -0
  159. package/dist-server/service/activity-instance/activity-instance-type.d.ts +20 -0
  160. package/dist-server/service/activity-instance/activity-instance-type.js +11 -0
  161. package/dist-server/service/activity-instance/activity-instance-type.js.map +1 -1
  162. package/dist-server/service/activity-instance/activity-instance.d.ts +6 -0
  163. package/dist-server/service/activity-instance/activity-instance.js +5 -0
  164. package/dist-server/service/activity-instance/activity-instance.js.map +1 -1
  165. package/dist-server/service/activity-stats/activity-stats-query.d.ts +5 -0
  166. package/dist-server/service/activity-stats/activity-stats-query.js +149 -11
  167. package/dist-server/service/activity-stats/activity-stats-query.js.map +1 -1
  168. package/dist-server/service/activity-stats/activity-stats-type.d.ts +19 -6
  169. package/dist-server/service/activity-stats/activity-stats-type.js +37 -10
  170. package/dist-server/service/activity-stats/activity-stats-type.js.map +1 -1
  171. package/dist-server/service/activity-stats/event-placement.d.ts +55 -0
  172. package/dist-server/service/activity-stats/event-placement.js +82 -0
  173. package/dist-server/service/activity-stats/event-placement.js.map +1 -0
  174. package/dist-server/service/activity-thread/activity-thread-query.js +48 -24
  175. package/dist-server/service/activity-thread/activity-thread-query.js.map +1 -1
  176. package/dist-server/service/activity-thread/my-open-threads.d.ts +15 -0
  177. package/dist-server/service/activity-thread/my-open-threads.js +40 -0
  178. package/dist-server/service/activity-thread/my-open-threads.js.map +1 -0
  179. package/dist-server/service/activity-thread/todo-order.d.ts +49 -0
  180. package/dist-server/service/activity-thread/todo-order.js +70 -0
  181. package/dist-server/service/activity-thread/todo-order.js.map +1 -0
  182. package/dist-server/service/installable-activity/installable-activity-mutation.d.ts +6 -0
  183. package/dist-server/service/installable-activity/installable-activity-mutation.js +7 -24
  184. package/dist-server/service/installable-activity/installable-activity-mutation.js.map +1 -1
  185. package/dist-server/service/nulls-last.d.ts +30 -0
  186. package/dist-server/service/nulls-last.js +37 -0
  187. package/dist-server/service/nulls-last.js.map +1 -0
  188. package/dist-server/tsconfig.tsbuildinfo +1 -1
  189. package/helps/dataset/activity-instance-search-page.ko.md +5 -0
  190. package/helps/dataset/activity-instance-search-page.md +13 -0
  191. package/helps/worklist/activity-approval.ko.md +5 -0
  192. package/helps/worklist/activity-approval.md +14 -0
  193. package/helps/worklist/activity-bank-page.ko.md +5 -0
  194. package/helps/worklist/activity-bank-page.md +13 -0
  195. package/helps/worklist/activity-reporter-list.ko.md +5 -0
  196. package/helps/worklist/activity-reporter-list.md +13 -0
  197. package/helps/worklist/activity-store.ko.md +5 -0
  198. package/helps/worklist/activity-store.md +13 -0
  199. package/helps/worklist/activity-template.ko.md +5 -0
  200. package/helps/worklist/activity-template.md +13 -0
  201. package/helps/worklist/installable-activity.ko.md +5 -0
  202. package/helps/worklist/installable-activity.md +13 -0
  203. package/package.json +13 -12
  204. package/things-factory.config.js +0 -5
  205. package/translations/en.json +129 -51
  206. package/translations/ja.json +130 -52
  207. package/translations/ko.json +82 -4
  208. package/translations/ms.json +129 -51
  209. package/translations/zh.json +132 -54
  210. package/dist-client/pages/dashboard/dashboard-home.d.ts +0 -1
  211. package/dist-client/pages/dashboard/dashboard-home.js +0 -22
  212. package/dist-client/pages/dashboard/dashboard-home.js.map +0 -1
  213. package/dist-client/pages/worklist-home.d.ts +0 -1
  214. package/dist-client/pages/worklist-home.js +0 -17
  215. package/dist-client/pages/worklist-home.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 안건 · 업무 이름 — 이름과 설명을 한 칸에 두 줄로.
3
+ *
4
+ * 둘은 함께 읽는 것인데 따로 두면 그 사이를 우선순위 · 차수 같은 칸이 가로막는다. 그리고 표식(메모 ·
5
+ * 재상신)은 이름과 함께 보여야 뜻이 선다.
6
+ *
7
+ * `marks` 는 화면이 정한다 — 결재함은 메모와 재상신을 보이고, 작성 중 목록은 보일 것이 없다.
8
+ */
9
+ export declare function subjectCell(marks?: (record: any) => unknown[]): (value: any, column: any, record: any, rowIndex: number, field: any) => import("lit-html").TemplateResult<1>;
10
+ /** 결재함이 쓰는 표식 — 「메모 있음」과 「N차 재상신」. */
11
+ export declare function approvalMarks(record: any): unknown[];
12
+ /**
13
+ * 기한 한 칸 — 남은 시간이 위, 절대 시각이 아래다.
14
+ *
15
+ * 전에는 「만기 시간」과 「남은 시간」이 각각 180px 을 쓰는 두 칸이었다. 사람이 묻는 것은 「얼마나
16
+ * 남았나」 하나이고, 절대 시각은 그것을 확인할 때만 본다.
17
+ *
18
+ * 기한이 지난 것은 남은 시간에 강조를 준다.
19
+ */
20
+ export declare function dueCell(isActive: () => boolean): (value: any, column: any, record: any, rowIndex: number, field: any) => import("lit-html").TemplateResult<1>;
21
+ export declare function priorityCell(): (value: any) => import("lit-html").TemplateResult<1>;
@@ -0,0 +1,117 @@
1
+ import { html } from 'lit';
2
+ import { asyncReplace } from 'lit/directives/async-replace.js';
3
+ import { i18next } from '@operato/i18n';
4
+ import { dueTimer, isOverdue } from './due.js';
5
+ /*
6
+ * 업무 목록의 칸을 그리는 방식 — **한 곳에서만 정한다.**
7
+ *
8
+ * ── 왜 모았나 (2026-09-01) ─────────────────────────────────────────────────
9
+ * 업무 목록 화면이 다섯이고(할 일 · 완료 · 작성 중 · 결재 대기 · 결재 완료) 컬럼 목록은 서로 다르다.
10
+ * 그런데 **칸을 그리는 방식은 같은 것을 복사해 쓰고 있었다.** 우선순위의 별 렌더러가 네 곳에 같은
11
+ * 코드로 있었고, 기한 계산 함수는 세 곳에 있었는데 그중 하나가 틀려 있었다(due.ts 머리말 참조).
12
+ *
13
+ * 복사본이 여럿이면 한 곳을 고쳐도 나머지는 그대로다. 그래서 화면마다 컬럼 목록은 그대로 두고
14
+ * **그리는 방식만** 여기로 모았다.
15
+ */
16
+ /**
17
+ * 안건 · 업무 이름 — 이름과 설명을 한 칸에 두 줄로.
18
+ *
19
+ * 둘은 함께 읽는 것인데 따로 두면 그 사이를 우선순위 · 차수 같은 칸이 가로막는다. 그리고 표식(메모 ·
20
+ * 재상신)은 이름과 함께 보여야 뜻이 선다.
21
+ *
22
+ * `marks` 는 화면이 정한다 — 결재함은 메모와 재상신을 보이고, 작성 중 목록은 보일 것이 없다.
23
+ */
24
+ export function subjectCell(marks) {
25
+ return (value, column, record, rowIndex, field) => {
26
+ field?.setAttribute?.('twoline', '');
27
+ const m = marks?.(record) ?? [];
28
+ /*
29
+ * 값을 span 으로 감싼다. 텍스트 노드로 두면 CSS 가 표식과의 사이를 띄울 방법이 없다 — 형제
30
+ * 선택자는 텍스트 노드를 보지 못한다. `.line` 이 간격과 높이를 잡는다(data-grid-field.ts).
31
+ */
32
+ return html `<span class="line"><span>${value ?? ''}</span>${m}</span
33
+ ><span class="sub">${record?.description ?? ''}</span>`;
34
+ };
35
+ }
36
+ /** 결재함이 쓰는 표식 — 「메모 있음」과 「N차 재상신」. */
37
+ export function approvalMarks(record) {
38
+ const marks = [];
39
+ /* 승인자가 보다가 메모를 남기고 미뤄 둔 것 — 내가 검토하던 것이 무엇인지 한눈에 보여야 한다. */
40
+ if (record?.comment)
41
+ marks.push(html `<span class="pill info">${i18next.t('label.approval-noted')}</span>`);
42
+ /* 재상신은 예외라 표식으로 낸다. 거의 모든 줄이 1차인 목록에서 차수 칸은 아무것도 알려 주지 않는다. */
43
+ if (record?.round > 1)
44
+ marks.push(html `<span class="pill warn">${i18next.t('label.approval-round-n', { n: record.round })}</span>`);
45
+ return marks;
46
+ }
47
+ /**
48
+ * 기한 한 칸 — 남은 시간이 위, 절대 시각이 아래다.
49
+ *
50
+ * 전에는 「만기 시간」과 「남은 시간」이 각각 180px 을 쓰는 두 칸이었다. 사람이 묻는 것은 「얼마나
51
+ * 남았나」 하나이고, 절대 시각은 그것을 확인할 때만 본다.
52
+ *
53
+ * 기한이 지난 것은 남은 시간에 강조를 준다.
54
+ */
55
+ export function dueCell(isActive) {
56
+ return (value, column, record, rowIndex, field) => {
57
+ const dueAt = record?.dueAt;
58
+ /* 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 구별되지 않는다. */
59
+ if (dueAt === null || dueAt === undefined || dueAt === '') {
60
+ return html `<span class="none">${i18next.t('text.no-due')}</span>`;
61
+ }
62
+ field?.setAttribute?.('twoline', '');
63
+ return html `<span class=${isOverdue(dueAt) ? 'crit' : ''}>${asyncReplace(dueTimer(dueAt, isActive))}</span
64
+ ><span class="sub"
65
+ >${new Date(Date.parse(String(dueAt))).toLocaleString(i18next.language || undefined, {
66
+ month: 'numeric',
67
+ day: 'numeric',
68
+ hour: 'numeric',
69
+ minute: '2-digit'
70
+ })}</span
71
+ >`;
72
+ };
73
+ }
74
+ /**
75
+ * 우선순위 — **별의 개수가 크기를 말한다.**
76
+ *
77
+ * 별을 숫자로 바꿔 봤다가 되돌렸다. 숫자만 적으면 **큰 쪽이 급한지 작은 쪽이 급한지 알 수 없다.**
78
+ * 별은 개수가 곧 크기라서 표를 외우지 않아도 읽힌다. 그것이 이 칸에서 가장 중요한 것이다.
79
+ *
80
+ * 고친 것은 색이다. 전에는 값마다 색이 달랐다.
81
+ *
82
+ * 1 #98FB98 흰 바탕에서 대비 1.4 — 보이지 않았다
83
+ * 2 #87CEEB
84
+ * 3 #FF6B6B
85
+ * 4 #8B0000
86
+ *
87
+ * 개수가 이미 크기를 말하는데 색까지 값마다 다르면 같은 것을 두 번 말하는 것이고, 네 색을 외운 사람만
88
+ * 정확히 읽을 수 있다. 그리고 첫 색은 아예 안 보였다.
89
+ *
90
+ * 그래서 색은 **두 단계**만 쓴다 — 보통은 흐리게, 「중요」 「중요」 필터가 잡는 경계(priority >= 2)부터
91
+ * 강조한다. 개수가 크기를 말하고 색이 「이 줄은 선을 넘었다」를 말한다. 서로 다른 것을 말하므로
92
+ * 겹치지 않는다.
93
+ *
94
+ * 값이 없으면 — 다. 0 개의 별로 그리면 「보통」과 「값 없음」이 같아 보인다.
95
+ */
96
+ const PRIORITY_STAR_CAP = 5;
97
+ export function priorityCell() {
98
+ return (value) => {
99
+ if (value === null || value === undefined || value === '')
100
+ return html `<span class="none">—</span>`;
101
+ const n = Number(value);
102
+ if (!Number.isFinite(n) || n <= 0)
103
+ return html `<span class="none">—</span>`;
104
+ /*
105
+ * 열린 숫자 축이라(표준 Priority) 현장이 9 를 쓸 수도 있다. 별 아홉 개는 칸을 넘고 세어 읽을 수도
106
+ * 없으므로, 다섯을 넘으면 별 다섯과 숫자를 함께 적는다 — 값을 잃지 않으면서 자리를 지킨다.
107
+ */
108
+ const stars = Math.min(Math.round(n), PRIORITY_STAR_CAP);
109
+ const emphasis = n >= 2 ? 'crit' : 'quiet';
110
+ return html `<span class="line ${emphasis}"
111
+ >${Array.from({ length: stars }, () => html `<md-icon class="star">star</md-icon>`)}${n > PRIORITY_STAR_CAP
112
+ ? html `<span class="sub">${n}</span>`
113
+ : ''}</span
114
+ >`;
115
+ };
116
+ }
117
+ //# sourceMappingURL=activity-cells.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-cells.js","sourceRoot":"","sources":["../client/activity-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { dueTimer, isOverdue } from './due.js'\n\n/*\n * 업무 목록의 칸을 그리는 방식 — **한 곳에서만 정한다.**\n *\n * ── 왜 모았나 (2026-09-01) ─────────────────────────────────────────────────\n * 업무 목록 화면이 다섯이고(할 일 · 완료 · 작성 중 · 결재 대기 · 결재 완료) 컬럼 목록은 서로 다르다.\n * 그런데 **칸을 그리는 방식은 같은 것을 복사해 쓰고 있었다.** 우선순위의 별 렌더러가 네 곳에 같은\n * 코드로 있었고, 기한 계산 함수는 세 곳에 있었는데 그중 하나가 틀려 있었다(due.ts 머리말 참조).\n *\n * 복사본이 여럿이면 한 곳을 고쳐도 나머지는 그대로다. 그래서 화면마다 컬럼 목록은 그대로 두고\n * **그리는 방식만** 여기로 모았다.\n */\n\n/**\n * 안건 · 업무 이름 — 이름과 설명을 한 칸에 두 줄로.\n *\n * 둘은 함께 읽는 것인데 따로 두면 그 사이를 우선순위 · 차수 같은 칸이 가로막는다. 그리고 표식(메모 ·\n * 재상신)은 이름과 함께 보여야 뜻이 선다.\n *\n * `marks` 는 화면이 정한다 — 결재함은 메모와 재상신을 보이고, 작성 중 목록은 보일 것이 없다.\n */\nexport function subjectCell(marks?: (record: any) => unknown[]) {\n return (value: any, column: any, record: any, rowIndex: number, field: any) => {\n field?.setAttribute?.('twoline', '')\n\n const m = marks?.(record) ?? []\n\n /*\n * 값을 span 으로 감싼다. 텍스트 노드로 두면 CSS 가 표식과의 사이를 띄울 방법이 없다 — 형제\n * 선택자는 텍스트 노드를 보지 못한다. `.line` 이 간격과 높이를 잡는다(data-grid-field.ts).\n */\n return html`<span class=\"line\"><span>${value ?? ''}</span>${m}</span\n ><span class=\"sub\">${record?.description ?? ''}</span>`\n }\n}\n\n/** 결재함이 쓰는 표식 — 「메모 있음」과 「N차 재상신」. */\nexport function approvalMarks(record: any): unknown[] {\n const marks: unknown[] = []\n\n /* 승인자가 보다가 메모를 남기고 미뤄 둔 것 — 내가 검토하던 것이 무엇인지 한눈에 보여야 한다. */\n if (record?.comment) marks.push(html`<span class=\"pill info\">${i18next.t('label.approval-noted')}</span>`)\n\n /* 재상신은 예외라 표식으로 낸다. 거의 모든 줄이 1차인 목록에서 차수 칸은 아무것도 알려 주지 않는다. */\n if (record?.round > 1)\n marks.push(html`<span class=\"pill warn\">${i18next.t('label.approval-round-n', { n: record.round })}</span>`)\n\n return marks\n}\n\n/**\n * 기한 한 칸 — 남은 시간이 위, 절대 시각이 아래다.\n *\n * 전에는 「만기 시간」과 「남은 시간」이 각각 180px 을 쓰는 두 칸이었다. 사람이 묻는 것은 「얼마나\n * 남았나」 하나이고, 절대 시각은 그것을 확인할 때만 본다.\n *\n * 기한이 지난 것은 남은 시간에 강조를 준다.\n */\nexport function dueCell(isActive: () => boolean) {\n return (value: any, column: any, record: any, rowIndex: number, field: any) => {\n const dueAt = record?.dueAt\n\n /* 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 구별되지 않는다. */\n if (dueAt === null || dueAt === undefined || dueAt === '') {\n return html`<span class=\"none\">${i18next.t('text.no-due')}</span>`\n }\n\n field?.setAttribute?.('twoline', '')\n\n return html`<span class=${isOverdue(dueAt) ? 'crit' : ''}>${asyncReplace(dueTimer(dueAt, isActive))}</span\n ><span class=\"sub\"\n >${new Date(Date.parse(String(dueAt))).toLocaleString(i18next.language || undefined, {\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n })}</span\n >`\n }\n}\n\n/**\n * 우선순위 — **별의 개수가 크기를 말한다.**\n *\n * 별을 숫자로 바꿔 봤다가 되돌렸다. 숫자만 적으면 **큰 쪽이 급한지 작은 쪽이 급한지 알 수 없다.**\n * 별은 개수가 곧 크기라서 표를 외우지 않아도 읽힌다. 그것이 이 칸에서 가장 중요한 것이다.\n *\n * 고친 것은 색이다. 전에는 값마다 색이 달랐다.\n *\n * 1 #98FB98 흰 바탕에서 대비 1.4 — 보이지 않았다\n * 2 #87CEEB\n * 3 #FF6B6B\n * 4 #8B0000\n *\n * 개수가 이미 크기를 말하는데 색까지 값마다 다르면 같은 것을 두 번 말하는 것이고, 네 색을 외운 사람만\n * 정확히 읽을 수 있다. 그리고 첫 색은 아예 안 보였다.\n *\n * 그래서 색은 **두 단계**만 쓴다 — 보통은 흐리게, 「중요」 「중요」 필터가 잡는 경계(priority >= 2)부터\n * 강조한다. 개수가 크기를 말하고 색이 「이 줄은 선을 넘었다」를 말한다. 서로 다른 것을 말하므로\n * 겹치지 않는다.\n *\n * 값이 없으면 — 다. 0 개의 별로 그리면 「보통」과 「값 없음」이 같아 보인다.\n */\nconst PRIORITY_STAR_CAP = 5\n\nexport function priorityCell() {\n return (value: any) => {\n if (value === null || value === undefined || value === '') return html`<span class=\"none\">—</span>`\n\n const n = Number(value)\n if (!Number.isFinite(n) || n <= 0) return html`<span class=\"none\">—</span>`\n\n /*\n * 열린 숫자 축이라(표준 Priority) 현장이 9 를 쓸 수도 있다. 별 아홉 개는 칸을 넘고 세어 읽을 수도\n * 없으므로, 다섯을 넘으면 별 다섯과 숫자를 함께 적는다 — 값을 잃지 않으면서 자리를 지킨다.\n */\n const stars = Math.min(Math.round(n), PRIORITY_STAR_CAP)\n const emphasis = n >= 2 ? 'crit' : 'quiet'\n\n return html`<span class=\"line ${emphasis}\"\n >${Array.from({ length: stars }, () => html`<md-icon class=\"star\">star</md-icon>`)}${n > PRIORITY_STAR_CAP\n ? html`<span class=\"sub\">${n}</span>`\n : ''}</span\n >`\n }\n}\n\n"]}
@@ -58,7 +58,26 @@ var generatorLocks = {
58
58
  numberOfToDos: new AsyncLock(true),
59
59
  numberOfApprovalPendings: new AsyncLock(true),
60
60
  numberOfPicks: new AsyncLock(true),
61
- numberOfDrafts: new AsyncLock(true)
61
+ numberOfDrafts: new AsyncLock(true),
62
+ /*
63
+ * 「내 일」 전체 — 서버가 내는 값이 아니라 **여기서 더한 것**이다.
64
+ *
65
+ * ── 왜 필요한가 ─────────────────────────────────────────────────────────
66
+ * 왼쪽 메뉴를 접으면 아이콘만 남고 하위가 감춰진다. 그러면 할 일 · 결재함 · 작성 중의 건수를
67
+ * 열어 보지 않고는 알 수 없다. **열어 봐야 보이는 건수는 건수가 아니다.**
68
+ *
69
+ * ── 왜 이름을 따로 두나 ─────────────────────────────────────────────────
70
+ * 이름마다 락이 하나이고 같은 이름에 새 소비자가 붙으면 앞의 것이 끝난다. 합계를 보려고
71
+ * 세 이름을 다시 구독하면 그 세 이름을 이미 보고 있던 하위 배지들에서 구독을 빼앗아, 최상위와
72
+ * 하위를 함께 그릴 수 없다. 이름을 하나 더 두면 그 문제가 없다 — 값은 이미 아는 것이라
73
+ * 새로 물어볼 것도 없다.
74
+ *
75
+ * ── 집을 일은 넣지 않는다 ───────────────────────────────────────────────
76
+ * `numberOfPicks` 는 아직 아무도 집지 않은 것 중 내가 집을 수 있는 것이다. **아직 내 것이
77
+ * 아니다.** 나머지 셋은 이미 내 앞에 온 것이라 성격이 다르고, 섞으면 「내 일 12건」이 실제로
78
+ * 내가 해야 하는 것보다 커진다.
79
+ */
80
+ numberOfMyWork: new AsyncLock(true)
62
81
  };
63
82
  const fetchActivitySummary = debounce(async () => {
64
83
  const response = await client.query({
@@ -73,17 +92,44 @@ const fetchActivitySummary = debounce(async () => {
73
92
  }
74
93
  `
75
94
  });
76
- if (response.data) {
77
- const { numberOfToDos, numberOfApprovalPendings, numberOfPicks, numberOfDrafts } = response.data.activitySummary;
78
- summary['numberOfToDos'] = numberOfToDos;
79
- summary['numberOfApprovalPendings'] = numberOfApprovalPendings;
80
- summary['numberOfPicks'] = numberOfPicks;
81
- summary['numberOfDrafts'] = numberOfDrafts;
82
- generatorLocks['numberOfToDos']?.unlock(numberOfToDos);
83
- generatorLocks['numberOfApprovalPendings']?.unlock(numberOfApprovalPendings);
84
- generatorLocks['numberOfPicks']?.unlock(numberOfPicks);
85
- generatorLocks['numberOfDrafts']?.unlock(numberOfDrafts);
95
+ /*
96
+ * The client runs with errorPolicy: 'all', so a failed query resolves with `errors` and a null or
97
+ * partial `data` rather than rejecting. Reading straight through it would throw on the
98
+ * destructure below; skipping leaves every count undefined, and undefined already draws no badge.
99
+ * That is the honest outcome — a count we could not fetch is not zero.
100
+ */
101
+ if (response.errors?.length || !response.data?.activitySummary) {
102
+ return summary;
86
103
  }
104
+ const { numberOfToDos, numberOfApprovalPendings, numberOfPicks, numberOfDrafts } = response.data.activitySummary;
105
+ /*
106
+ * A field the server did not send is **not zero**, and the sum is where that used to leak.
107
+ *
108
+ * Each badge already leaves a missing count undefined, which draws nothing — the honest outcome.
109
+ * The total, though, read `(a ?? 0) + (b ?? 0) + (c ?? 0)`, so a missing part quietly became a
110
+ * zero part and the badge showed a confident, smaller number. Someone reading 「내 일 3」 has no
111
+ * way to know the approvals never arrived. Under-reporting how much is waiting on a person is
112
+ * the wrong direction to be wrong in.
113
+ *
114
+ * A total of parts we do not fully have is not a total. It stays undefined, and undefined draws
115
+ * nothing, same as the parts.
116
+ *
117
+ * (Picks are left out on purpose: work nobody has claimed is not yet anyone's.)
118
+ */
119
+ const parts = [numberOfToDos, numberOfApprovalPendings, numberOfDrafts];
120
+ const numberOfMyWork = parts.every(one => typeof one == 'number')
121
+ ? parts.reduce((sum, one) => sum + one, 0)
122
+ : undefined;
123
+ summary['numberOfToDos'] = numberOfToDos;
124
+ summary['numberOfApprovalPendings'] = numberOfApprovalPendings;
125
+ summary['numberOfPicks'] = numberOfPicks;
126
+ summary['numberOfDrafts'] = numberOfDrafts;
127
+ summary['numberOfMyWork'] = numberOfMyWork;
128
+ generatorLocks['numberOfToDos']?.unlock(numberOfToDos);
129
+ generatorLocks['numberOfApprovalPendings']?.unlock(numberOfApprovalPendings);
130
+ generatorLocks['numberOfPicks']?.unlock(numberOfPicks);
131
+ generatorLocks['numberOfDrafts']?.unlock(numberOfDrafts);
132
+ generatorLocks['numberOfMyWork']?.unlock(numberOfMyWork);
87
133
  }, 1000);
88
134
  export async function* generateActivitySummary(name) {
89
135
  /* 1. 앞에 사용되었던 generator가 종료될 기회를 준다. */
@@ -1 +1 @@
1
- {"version":3,"file":"activity-summary-generator.js","sourceRoot":"","sources":["../client/activity-summary-generator.ts"],"names":[],"mappings":"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","sourcesContent":["import debounce from 'lodash-es/debounce'\nimport gql from 'graphql-tag'\nimport { client, subscribe } from '@operato/graphql'\nimport { AsyncLock } from '@operato/utils'\n\nasync function subscribeActivityInstance() {\n return await subscribe(\n {\n query: gql`\n subscription {\n activityInstance {\n name\n }\n }\n `\n },\n {\n next: async ({ data }) => {\n if (data) {\n fetchActivitySummary()\n }\n }\n }\n )\n}\n\nasync function subscribeActivityThread() {\n return await subscribe(\n {\n query: gql`\n subscription {\n activityThread {\n transaction\n }\n }\n `\n },\n {\n next: async ({ data }) => {\n if (data) {\n fetchActivitySummary()\n }\n }\n }\n )\n}\n\nasync function subscribeActivityApproval() {\n return await subscribe(\n {\n query: gql`\n subscription {\n activityApproval {\n judgment\n }\n }\n `\n },\n {\n next: async ({ data }) => {\n if (data) {\n fetchActivitySummary()\n }\n }\n }\n )\n}\n\nvar summary = {}\nvar generatorLocks = {\n numberOfToDos: new AsyncLock(true),\n numberOfApprovalPendings: new AsyncLock(true),\n numberOfPicks: new AsyncLock(true),\n numberOfDrafts: new AsyncLock(true)\n}\n\nconst fetchActivitySummary = debounce(async () => {\n const response = await client.query({\n query: gql`\n query {\n activitySummary {\n numberOfToDos\n numberOfApprovalPendings\n numberOfPicks\n numberOfDrafts\n }\n }\n `\n })\n\n if (response.data) {\n const { numberOfToDos, numberOfApprovalPendings, numberOfPicks, numberOfDrafts } = response.data.activitySummary\n\n summary['numberOfToDos'] = numberOfToDos\n summary['numberOfApprovalPendings'] = numberOfApprovalPendings\n summary['numberOfPicks'] = numberOfPicks\n summary['numberOfDrafts'] = numberOfDrafts\n\n generatorLocks['numberOfToDos']?.unlock(numberOfToDos)\n generatorLocks['numberOfApprovalPendings']?.unlock(numberOfApprovalPendings)\n generatorLocks['numberOfPicks']?.unlock(numberOfPicks)\n generatorLocks['numberOfDrafts']?.unlock(numberOfDrafts)\n }\n}, 1000)\n\nexport async function* generateActivitySummary(name: string) {\n /* 1. 앞에 사용되었던 generator가 종료될 기회를 준다. */\n generatorLocks[name]?.unlock()\n\n /* 2. 마지막 값을 일단 yield 한다. */\n yield summary[name]\n\n while (true) {\n generatorLocks[name].lock()\n\n let badge = await generatorLocks[name].promise\n if (badge === undefined) {\n /* 새로운 generator가 생성된 경우이면, 기존 generator는 종료한다. */\n return\n }\n\n yield badge\n }\n}\n\nexport async function startSubscribeActivitySummary() {\n await subscribeActivityInstance()\n await subscribeActivityThread()\n await subscribeActivityApproval()\n\n fetchActivitySummary()\n}\n"]}
1
+ {"version":3,"file":"activity-summary-generator.js","sourceRoot":"","sources":["../client/activity-summary-generator.ts"],"names":[],"mappings":"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","sourcesContent":["import debounce from 'lodash-es/debounce'\nimport gql from 'graphql-tag'\nimport { client, subscribe } from '@operato/graphql'\nimport { AsyncLock } from '@operato/utils'\n\nasync function subscribeActivityInstance() {\n return await subscribe(\n {\n query: gql`\n subscription {\n activityInstance {\n name\n }\n }\n `\n },\n {\n next: async ({ data }) => {\n if (data) {\n fetchActivitySummary()\n }\n }\n }\n )\n}\n\nasync function subscribeActivityThread() {\n return await subscribe(\n {\n query: gql`\n subscription {\n activityThread {\n transaction\n }\n }\n `\n },\n {\n next: async ({ data }) => {\n if (data) {\n fetchActivitySummary()\n }\n }\n }\n )\n}\n\nasync function subscribeActivityApproval() {\n return await subscribe(\n {\n query: gql`\n subscription {\n activityApproval {\n judgment\n }\n }\n `\n },\n {\n next: async ({ data }) => {\n if (data) {\n fetchActivitySummary()\n }\n }\n }\n )\n}\n\nvar summary = {}\nvar generatorLocks = {\n numberOfToDos: new AsyncLock(true),\n numberOfApprovalPendings: new AsyncLock(true),\n numberOfPicks: new AsyncLock(true),\n numberOfDrafts: new AsyncLock(true),\n /*\n * 「내 일」 전체 — 서버가 내는 값이 아니라 **여기서 더한 것**이다.\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────\n * 왼쪽 메뉴를 접으면 아이콘만 남고 하위가 감춰진다. 그러면 할 일 · 결재함 · 작성 중의 건수를\n * 열어 보지 않고는 알 수 없다. **열어 봐야 보이는 건수는 건수가 아니다.**\n *\n * ── 왜 이름을 따로 두나 ─────────────────────────────────────────────────\n * 이름마다 락이 하나이고 같은 이름에 새 소비자가 붙으면 앞의 것이 끝난다. 합계를 보려고\n * 세 이름을 다시 구독하면 그 세 이름을 이미 보고 있던 하위 배지들에서 구독을 빼앗아, 최상위와\n * 하위를 함께 그릴 수 없다. 이름을 하나 더 두면 그 문제가 없다 — 값은 이미 아는 것이라\n * 새로 물어볼 것도 없다.\n *\n * ── 집을 일은 넣지 않는다 ───────────────────────────────────────────────\n * `numberOfPicks` 는 아직 아무도 집지 않은 것 중 내가 집을 수 있는 것이다. **아직 내 것이\n * 아니다.** 나머지 셋은 이미 내 앞에 온 것이라 성격이 다르고, 섞으면 「내 일 12건」이 실제로\n * 내가 해야 하는 것보다 커진다.\n */\n numberOfMyWork: new AsyncLock(true)\n}\n\nconst fetchActivitySummary = debounce(async () => {\n const response = await client.query({\n query: gql`\n query {\n activitySummary {\n numberOfToDos\n numberOfApprovalPendings\n numberOfPicks\n numberOfDrafts\n }\n }\n `\n })\n\n /*\n * The client runs with errorPolicy: 'all', so a failed query resolves with `errors` and a null or\n * partial `data` rather than rejecting. Reading straight through it would throw on the\n * destructure below; skipping leaves every count undefined, and undefined already draws no badge.\n * That is the honest outcome — a count we could not fetch is not zero.\n */\n if (response.errors?.length || !response.data?.activitySummary) {\n return summary\n }\n\n const { numberOfToDos, numberOfApprovalPendings, numberOfPicks, numberOfDrafts } = response.data.activitySummary\n\n /*\n * A field the server did not send is **not zero**, and the sum is where that used to leak.\n *\n * Each badge already leaves a missing count undefined, which draws nothing — the honest outcome.\n * The total, though, read `(a ?? 0) + (b ?? 0) + (c ?? 0)`, so a missing part quietly became a\n * zero part and the badge showed a confident, smaller number. Someone reading 「내 일 3」 has no\n * way to know the approvals never arrived. Under-reporting how much is waiting on a person is\n * the wrong direction to be wrong in.\n *\n * A total of parts we do not fully have is not a total. It stays undefined, and undefined draws\n * nothing, same as the parts.\n *\n * (Picks are left out on purpose: work nobody has claimed is not yet anyone's.)\n */\n const parts = [numberOfToDos, numberOfApprovalPendings, numberOfDrafts]\n const numberOfMyWork = parts.every(one => typeof one == 'number')\n ? parts.reduce((sum, one) => sum + one, 0)\n : undefined\n\n summary['numberOfToDos'] = numberOfToDos\n summary['numberOfApprovalPendings'] = numberOfApprovalPendings\n summary['numberOfPicks'] = numberOfPicks\n summary['numberOfDrafts'] = numberOfDrafts\n summary['numberOfMyWork'] = numberOfMyWork\n\n generatorLocks['numberOfToDos']?.unlock(numberOfToDos)\n generatorLocks['numberOfApprovalPendings']?.unlock(numberOfApprovalPendings)\n generatorLocks['numberOfPicks']?.unlock(numberOfPicks)\n generatorLocks['numberOfDrafts']?.unlock(numberOfDrafts)\n generatorLocks['numberOfMyWork']?.unlock(numberOfMyWork)\n}, 1000)\n\nexport async function* generateActivitySummary(name: string) {\n /* 1. 앞에 사용되었던 generator가 종료될 기회를 준다. */\n generatorLocks[name]?.unlock()\n\n /* 2. 마지막 값을 일단 yield 한다. */\n yield summary[name]\n\n while (true) {\n generatorLocks[name].lock()\n\n let badge = await generatorLocks[name].promise\n if (badge === undefined) {\n /* 새로운 generator가 생성된 경우이면, 기존 generator는 종료한다. */\n return\n }\n\n yield badge\n }\n}\n\nexport async function startSubscribeActivitySummary() {\n await subscribeActivityInstance()\n await subscribeActivityThread()\n await subscribeActivityApproval()\n\n fetchActivitySummary()\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 결재선에서 지금 몇 번째가 판단할 차례인가.
3
+ *
4
+ * ── 왜 필요한가 (2026-09-06) ───────────────────────────────────────────────
5
+ * approval-line-brief 는 current 를 안 주면 -1 을 쓰고, 그러면 **기안자(1단계)를 항상 현재로
6
+ * 표시한다.** 세 ribbon 중 activity-approval-ribon 만 이 값을 넘기고 있었다. 업무 상세 화면은
7
+ * 결재가 3단계까지 올라간 건에서도 1단계에 표시를 그렸다 — 화면이 사실과 다른 것을 말한다.
8
+ *
9
+ * ── 어떻게 정하나 ──────────────────────────────────────────────────────────
10
+ * 판단이 비어 있는 것 중 가장 앞선 order 가 지금 차례다. 재상신이 있으면 차수(round)가 올라가고
11
+ * 앞 차수의 판단은 지나간 것이므로 **가장 높은 차수만** 본다.
12
+ *
13
+ * 전부 판단이 끝났으면 결재선을 다 지난 것이라 마지막 다음 번호를 낸다.
14
+ *
15
+ * lit 을 가져오지 않는다 — 이 규칙을 시험으로 고정할 수 있어야 하고, 시험이 화면 부품을 통째로
16
+ * 불러오면 브라우저 API 가 없어 뜨지 않는다.
17
+ */
18
+ export declare function currentApprovalOrder(activityApprovals?: any[]): number;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 결재선에서 지금 몇 번째가 판단할 차례인가.
3
+ *
4
+ * ── 왜 필요한가 (2026-09-06) ───────────────────────────────────────────────
5
+ * approval-line-brief 는 current 를 안 주면 -1 을 쓰고, 그러면 **기안자(1단계)를 항상 현재로
6
+ * 표시한다.** 세 ribbon 중 activity-approval-ribon 만 이 값을 넘기고 있었다. 업무 상세 화면은
7
+ * 결재가 3단계까지 올라간 건에서도 1단계에 표시를 그렸다 — 화면이 사실과 다른 것을 말한다.
8
+ *
9
+ * ── 어떻게 정하나 ──────────────────────────────────────────────────────────
10
+ * 판단이 비어 있는 것 중 가장 앞선 order 가 지금 차례다. 재상신이 있으면 차수(round)가 올라가고
11
+ * 앞 차수의 판단은 지나간 것이므로 **가장 높은 차수만** 본다.
12
+ *
13
+ * 전부 판단이 끝났으면 결재선을 다 지난 것이라 마지막 다음 번호를 낸다.
14
+ *
15
+ * lit 을 가져오지 않는다 — 이 규칙을 시험으로 고정할 수 있어야 하고, 시험이 화면 부품을 통째로
16
+ * 불러오면 브라우저 API 가 없어 뜨지 않는다.
17
+ */
18
+ export function currentApprovalOrder(activityApprovals) {
19
+ const approvals = activityApprovals || [];
20
+ if (!approvals.length) {
21
+ return -1;
22
+ }
23
+ const round = approvals.reduce((max, a) => Math.max(max, Number(a?.round) || 0), 0);
24
+ const thisRound = approvals.filter(a => (Number(a?.round) || 0) === round);
25
+ const pending = thisRound.filter(a => !a?.judgment).map(a => Number(a?.order) || 0);
26
+ if (pending.length) {
27
+ return Math.min(...pending);
28
+ }
29
+ return Math.max(...thisRound.map(a => Number(a?.order) || 0)) + 1;
30
+ }
31
+ //# sourceMappingURL=approval-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-progress.js","sourceRoot":"","sources":["../client/approval-progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,oBAAoB,CAAC,iBAAyB;IAC5D,MAAM,SAAS,GAAG,iBAAiB,IAAI,EAAE,CAAA;IAEzC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,CAAC,CAAA;IACX,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACnF,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAA;IAE1E,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAEnF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;IAC7B,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AACnE,CAAC","sourcesContent":["/**\n * 결재선에서 지금 몇 번째가 판단할 차례인가.\n *\n * ── 왜 필요한가 (2026-09-06) ───────────────────────────────────────────────\n * approval-line-brief 는 current 를 안 주면 -1 을 쓰고, 그러면 **기안자(1단계)를 항상 현재로\n * 표시한다.** 세 ribbon 중 activity-approval-ribon 만 이 값을 넘기고 있었다. 업무 상세 화면은\n * 결재가 3단계까지 올라간 건에서도 1단계에 표시를 그렸다 — 화면이 사실과 다른 것을 말한다.\n *\n * ── 어떻게 정하나 ──────────────────────────────────────────────────────────\n * 판단이 비어 있는 것 중 가장 앞선 order 가 지금 차례다. 재상신이 있으면 차수(round)가 올라가고\n * 앞 차수의 판단은 지나간 것이므로 **가장 높은 차수만** 본다.\n *\n * 전부 판단이 끝났으면 결재선을 다 지난 것이라 마지막 다음 번호를 낸다.\n *\n * lit 을 가져오지 않는다 — 이 규칙을 시험으로 고정할 수 있어야 하고, 시험이 화면 부품을 통째로\n * 불러오면 브라우저 API 가 없어 뜨지 않는다.\n */\nexport function currentApprovalOrder(activityApprovals?: any[]): number {\n const approvals = activityApprovals || []\n\n if (!approvals.length) {\n return -1\n }\n\n const round = approvals.reduce((max, a) => Math.max(max, Number(a?.round) || 0), 0)\n const thisRound = approvals.filter(a => (Number(a?.round) || 0) === round)\n\n const pending = thisRound.filter(a => !a?.judgment).map(a => Number(a?.order) || 0)\n\n if (pending.length) {\n return Math.min(...pending)\n }\n\n return Math.max(...thisRound.map(a => Number(a?.order) || 0)) + 1\n}\n"]}
@@ -4,7 +4,7 @@ import './templates/activity-thread-context-template.js';
4
4
  import './templates/activity-instance-context-template.js';
5
5
  import { html } from 'lit-html';
6
6
  import { store } from '@operato/shell';
7
- import { appendViewpart, openOverlay, TOOL_POSITION, VIEWPART_POSITION } from '@operato/layout';
7
+ import { openOverlay, TOOL_POSITION } from '@operato/layout';
8
8
  import { APPEND_CONTEXT_TOOL } from '@things-factory/context-ui/dist-client';
9
9
  import { registerEditor as registerGristEditor, registerRenderer as registerGristRenderer } from '@operato/data-grist';
10
10
  import { OxGristEditorDuration } from '@operato/grist-editor/ox-grist-editor-duration.js';
@@ -14,16 +14,11 @@ import { OxGristRendererQuantifier } from '@operato/grist-editor/ox-grist-render
14
14
  import { GristEditorActivitySearchKeys } from './grist-editor/grist-editor-activity-search-key.js';
15
15
  import { GristRendererActivitySearchKeys } from './grist-editor/grist-renderer-activity-search-key.js';
16
16
  export default function bootstrap() {
17
- appendViewpart({
18
- name: 'activity-info-overlay',
19
- viewpart: {
20
- show: false,
21
- hovering: 'edge',
22
- backdrop: true,
23
- template: html ``
24
- },
25
- position: VIEWPART_POSITION.ASIDEBAR
26
- });
17
+ /*
18
+ * 여기에 'activity-info-overlay' aside viewpart 를 등록해 두었었다. 걷었다 —
19
+ * 기본 정보는 이제 팝업으로 연다(activity-thread-ribon 의 openInfo 머리말 참조).
20
+ * ASIDEBAR 은 앱이 하나만 갖는 자리이고, 페이지에 매인 판이 그 자리를 차지할 이유가 없다.
21
+ */
27
22
  [
28
23
  {
29
24
  template: html ` <activity-approval-context-template></activity-approval-context-template> `,
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,mDAAmD,CAAA;AAC1D,OAAO,iDAAiD,CAAA;AACxD,OAAO,mDAAmD,CAAA;AAE1D,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC/F,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAE5E,OAAO,EAAE,cAAc,IAAI,mBAAmB,EAAE,gBAAgB,IAAI,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AACtH,OAAO,EAAE,qBAAqB,EAAE,MAAM,mDAAmD,CAAA;AACzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,qDAAqD,CAAA;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,qDAAqD,CAAA;AAC7F,OAAO,EAAE,yBAAyB,EAAE,MAAM,uDAAuD,CAAA;AACjG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAClG,OAAO,EAAE,+BAA+B,EAAE,MAAM,sDAAsD,CAAA;AAEtG,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B,cAAc,CAAC;QACb,IAAI,EAAE,uBAAuB;QAC7B,QAAQ,EAAE;YACR,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,MAAM;YAChB,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI,CAAA,EAAE;SACjB;QACD,QAAQ,EAAE,iBAAiB,CAAC,QAAQ;KACrC,CAAC,CACD;IAAA;QACC;YACE,QAAQ,EAAE,IAAI,CAAA,6EAA6E;YAC3F,OAAO,EAAE,kBAAkB;SAC5B;QACD;YACE,QAAQ,EAAE,IAAI,CAAA,yEAAyE;YACvF,OAAO,EAAE,gBAAgB;SAC1B;QACD;YACE,QAAQ,EAAE,IAAI,CAAA,6EAA6E;YAC3F,OAAO,EAAE,kBAAkB;SAC5B;KACF,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QAClC,KAAK,CAAC,QAAQ,CAAC;YACb,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE;gBACJ,QAAQ,EAAE,aAAa,CAAC,KAAK;gBAC7B,QAAQ,EAAE,IAAI,CAAA;;qBAED,KAAK,EAAC,CAAC,EAAC,EAAE;oBACjB,WAAW,CAAC,yBAAyB,EAAE;wBACrC,QAAQ;qBACT,CAAC,CAAA;gBACJ,CAAC;;;SAGJ;gBACD,OAAO;aACR;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAA;IACtD,qBAAqB,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAA;IAC1D,mBAAmB,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAA;IAC1D,qBAAqB,CAAC,YAAY,EAAE,yBAAyB,CAAC,CAAA;IAE9D,mBAAmB,CAAC,sBAAsB,EAAE,6BAA6B,CAAC,CAAA;IAC1E,qBAAqB,CAAC,sBAAsB,EAAE,+BAA+B,CAAC,CAAA;AAChF,CAAC","sourcesContent":["import '@material/web/icon/icon.js'\nimport './templates/activity-approval-context-template.js'\nimport './templates/activity-thread-context-template.js'\nimport './templates/activity-instance-context-template.js'\n\nimport { html } from 'lit-html'\nimport { store } from '@operato/shell'\nimport { appendViewpart, openOverlay, TOOL_POSITION, VIEWPART_POSITION } from '@operato/layout'\nimport { APPEND_CONTEXT_TOOL } from '@things-factory/context-ui/dist-client'\n\nimport { registerEditor as registerGristEditor, registerRenderer as registerGristRenderer } from '@operato/data-grist'\nimport { OxGristEditorDuration } from '@operato/grist-editor/ox-grist-editor-duration.js'\nimport { OxGristEditorQuantifier } from '@operato/grist-editor/ox-grist-editor-quantifier.js'\nimport { OxGristRendererDuration } from '@operato/grist-editor/ox-grist-renderer-duration.js'\nimport { OxGristRendererQuantifier } from '@operato/grist-editor/ox-grist-renderer-quantifier.js'\nimport { GristEditorActivitySearchKeys } from './grist-editor/grist-editor-activity-search-key.js'\nimport { GristRendererActivitySearchKeys } from './grist-editor/grist-renderer-activity-search-key.js'\n\nexport default function bootstrap() {\n appendViewpart({\n name: 'activity-info-overlay',\n viewpart: {\n show: false,\n hovering: 'edge',\n backdrop: true,\n template: html``\n },\n position: VIEWPART_POSITION.ASIDEBAR\n })\n ;[\n {\n template: html` <activity-approval-context-template></activity-approval-context-template> `,\n context: 'activityApproval'\n },\n {\n template: html` <activity-thread-context-template></activity-thread-context-template> `,\n context: 'activityThread'\n },\n {\n template: html` <activity-instance-context-template></activity-instance-context-template> `,\n context: 'activityInstance'\n }\n ].forEach(({ template, context }) => {\n store.dispatch({\n type: APPEND_CONTEXT_TOOL,\n tool: {\n position: TOOL_POSITION.FRONT,\n template: html`\n <md-icon\n @click=${async e => {\n openOverlay('context-toolbar-overlay', {\n template\n })\n }}\n >info</md-icon\n >\n `,\n context\n }\n })\n })\n\n registerGristEditor('duration', OxGristEditorDuration)\n registerGristRenderer('duration', OxGristRendererDuration)\n registerGristEditor('quantifier', OxGristEditorQuantifier)\n registerGristRenderer('quantifier', OxGristRendererQuantifier)\n\n registerGristEditor('activity-search-keys', GristEditorActivitySearchKeys)\n registerGristRenderer('activity-search-keys', GristRendererActivitySearchKeys)\n}\n"]}
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,mDAAmD,CAAA;AAC1D,OAAO,iDAAiD,CAAA;AACxD,OAAO,mDAAmD,CAAA;AAE1D,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAE5E,OAAO,EAAE,cAAc,IAAI,mBAAmB,EAAE,gBAAgB,IAAI,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AACtH,OAAO,EAAE,qBAAqB,EAAE,MAAM,mDAAmD,CAAA;AACzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,qDAAqD,CAAA;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,qDAAqD,CAAA;AAC7F,OAAO,EAAE,yBAAyB,EAAE,MAAM,uDAAuD,CAAA;AACjG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAClG,OAAO,EAAE,+BAA+B,EAAE,MAAM,sDAAsD,CAAA;AAEtG,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B;;;;OAIG;IACH;QACE;YACE,QAAQ,EAAE,IAAI,CAAA,6EAA6E;YAC3F,OAAO,EAAE,kBAAkB;SAC5B;QACD;YACE,QAAQ,EAAE,IAAI,CAAA,yEAAyE;YACvF,OAAO,EAAE,gBAAgB;SAC1B;QACD;YACE,QAAQ,EAAE,IAAI,CAAA,6EAA6E;YAC3F,OAAO,EAAE,kBAAkB;SAC5B;KACF,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QAClC,KAAK,CAAC,QAAQ,CAAC;YACb,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE;gBACJ,QAAQ,EAAE,aAAa,CAAC,KAAK;gBAC7B,QAAQ,EAAE,IAAI,CAAA;;qBAED,KAAK,EAAC,CAAC,EAAC,EAAE;oBACjB,WAAW,CAAC,yBAAyB,EAAE;wBACrC,QAAQ;qBACT,CAAC,CAAA;gBACJ,CAAC;;;SAGJ;gBACD,OAAO;aACR;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAA;IACtD,qBAAqB,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAA;IAC1D,mBAAmB,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAA;IAC1D,qBAAqB,CAAC,YAAY,EAAE,yBAAyB,CAAC,CAAA;IAE9D,mBAAmB,CAAC,sBAAsB,EAAE,6BAA6B,CAAC,CAAA;IAC1E,qBAAqB,CAAC,sBAAsB,EAAE,+BAA+B,CAAC,CAAA;AAChF,CAAC","sourcesContent":["import '@material/web/icon/icon.js'\nimport './templates/activity-approval-context-template.js'\nimport './templates/activity-thread-context-template.js'\nimport './templates/activity-instance-context-template.js'\n\nimport { html } from 'lit-html'\nimport { store } from '@operato/shell'\nimport { openOverlay, TOOL_POSITION } from '@operato/layout'\nimport { APPEND_CONTEXT_TOOL } from '@things-factory/context-ui/dist-client'\n\nimport { registerEditor as registerGristEditor, registerRenderer as registerGristRenderer } from '@operato/data-grist'\nimport { OxGristEditorDuration } from '@operato/grist-editor/ox-grist-editor-duration.js'\nimport { OxGristEditorQuantifier } from '@operato/grist-editor/ox-grist-editor-quantifier.js'\nimport { OxGristRendererDuration } from '@operato/grist-editor/ox-grist-renderer-duration.js'\nimport { OxGristRendererQuantifier } from '@operato/grist-editor/ox-grist-renderer-quantifier.js'\nimport { GristEditorActivitySearchKeys } from './grist-editor/grist-editor-activity-search-key.js'\nimport { GristRendererActivitySearchKeys } from './grist-editor/grist-renderer-activity-search-key.js'\n\nexport default function bootstrap() {\n /*\n * 여기에 'activity-info-overlay' aside viewpart 등록해 두었었다. 걷었다 —\n * 기본 정보는 이제 팝업으로 연다(activity-thread-ribon 의 openInfo 머리말 참조).\n * ASIDEBAR 은 앱이 하나만 갖는 자리이고, 페이지에 매인 판이 그 자리를 차지할 이유가 없다.\n */\n [\n {\n template: html` <activity-approval-context-template></activity-approval-context-template> `,\n context: 'activityApproval'\n },\n {\n template: html` <activity-thread-context-template></activity-thread-context-template> `,\n context: 'activityThread'\n },\n {\n template: html` <activity-instance-context-template></activity-instance-context-template> `,\n context: 'activityInstance'\n }\n ].forEach(({ template, context }) => {\n store.dispatch({\n type: APPEND_CONTEXT_TOOL,\n tool: {\n position: TOOL_POSITION.FRONT,\n template: html`\n <md-icon\n @click=${async e => {\n openOverlay('context-toolbar-overlay', {\n template\n })\n }}\n >info</md-icon\n >\n `,\n context\n }\n })\n })\n\n registerGristEditor('duration', OxGristEditorDuration)\n registerGristRenderer('duration', OxGristRendererDuration)\n registerGristEditor('quantifier', OxGristEditorQuantifier)\n registerGristRenderer('quantifier', OxGristRendererQuantifier)\n\n registerGristEditor('activity-search-keys', GristEditorActivitySearchKeys)\n registerGristRenderer('activity-search-keys', GristRendererActivitySearchKeys)\n}\n"]}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * 목록 위의 칩이 무엇을 걸러 내는가 — **한 곳에서 정한다.**
3
+ *
4
+ * ── 왜 모았나 (2026-09-02) ─────────────────────────────────────────────────
5
+ * 「중요」와 「만기」가 다섯 화면에 있는데, 그 뜻이 다섯 곳에 복사돼 있었다.
6
+ *
7
+ * priority gte 2 5곳
8
+ * dueAt lte (지금 + 24 * 3600 * 1000) 4곳
9
+ *
10
+ * 한 화면에서 「중요」를 우선순위 3 이상으로 바꾸면 나머지 넷은 2로 남는다. 그러면 **같은 이름의 칩이
11
+ * 화면마다 다른 것을 걸러 낸다.** 사용자는 그것을 알 방법이 없다.
12
+ *
13
+ * ── 칩은 컬럼 필터가 아니다 ────────────────────────────────────────────────
14
+ * `ox-filters-form` 은 칸 하나와 연산자 하나를 그린다. 이 칩들은 **이름 붙인 조건 묶음**이다 —
15
+ * 「중요」는 `priority = ?` 가 아니라 `priority >= 2` 이고, 「만기」는 지금 시각에서 계산한다.
16
+ *
17
+ * 그래서 사용자가 스스로 만들 수 없다. 「중요 = 우선순위 2 이상」은 제품이 아는 사실이다.
18
+ *
19
+ * ── 그러니 그 뜻을 화면이 말해야 한다 ──────────────────────────────────────
20
+ * 이름만 보이면 눌러 보고 결과를 되짚어야 한다. 칩마다 `hint` 를 함께 낸다(마우스를 올리면 보인다).
21
+ */
22
+ /** 「중요」의 경계. 이 값 이상이 중요하다. */
23
+ export declare const IMPORTANT_FROM = 2;
24
+ /** 「만기」의 창. 이 시간 안에 만기가 오는 것. */
25
+ export declare const DUE_WITHIN_HOURS = 24;
26
+ /** 우선순위가 경계 이상인 것. */
27
+ export declare function importantFilters(): {
28
+ name: string;
29
+ operator: "gte";
30
+ value: number;
31
+ }[];
32
+ /**
33
+ * 만기가 창 안에 드는 것.
34
+ *
35
+ * 부를 때마다 지금 시각으로 계산한다 — 화면을 열어 둔 채 시간이 지나면 「만기 임박」의 범위도 함께
36
+ * 움직여야 한다.
37
+ */
38
+ export declare function dueSoonFilters(): {
39
+ name: string;
40
+ operator: "lte";
41
+ value: string;
42
+ }[];
43
+ /** 칩 하나 — 이름과 뜻. */
44
+ export declare function importantChip(): {
45
+ display: string;
46
+ value: string;
47
+ hint: string;
48
+ };
49
+ export declare function dueSoonChip(): {
50
+ display: string;
51
+ value: string;
52
+ hint: string;
53
+ };
@@ -0,0 +1,60 @@
1
+ import { i18next } from '@operato/i18n';
2
+ /**
3
+ * 목록 위의 칩이 무엇을 걸러 내는가 — **한 곳에서 정한다.**
4
+ *
5
+ * ── 왜 모았나 (2026-09-02) ─────────────────────────────────────────────────
6
+ * 「중요」와 「만기」가 다섯 화면에 있는데, 그 뜻이 다섯 곳에 복사돼 있었다.
7
+ *
8
+ * priority gte 2 5곳
9
+ * dueAt lte (지금 + 24 * 3600 * 1000) 4곳
10
+ *
11
+ * 한 화면에서 「중요」를 우선순위 3 이상으로 바꾸면 나머지 넷은 2로 남는다. 그러면 **같은 이름의 칩이
12
+ * 화면마다 다른 것을 걸러 낸다.** 사용자는 그것을 알 방법이 없다.
13
+ *
14
+ * ── 칩은 컬럼 필터가 아니다 ────────────────────────────────────────────────
15
+ * `ox-filters-form` 은 칸 하나와 연산자 하나를 그린다. 이 칩들은 **이름 붙인 조건 묶음**이다 —
16
+ * 「중요」는 `priority = ?` 가 아니라 `priority >= 2` 이고, 「만기」는 지금 시각에서 계산한다.
17
+ *
18
+ * 그래서 사용자가 스스로 만들 수 없다. 「중요 = 우선순위 2 이상」은 제품이 아는 사실이다.
19
+ *
20
+ * ── 그러니 그 뜻을 화면이 말해야 한다 ──────────────────────────────────────
21
+ * 이름만 보이면 눌러 보고 결과를 되짚어야 한다. 칩마다 `hint` 를 함께 낸다(마우스를 올리면 보인다).
22
+ */
23
+ /** 「중요」의 경계. 이 값 이상이 중요하다. */
24
+ export const IMPORTANT_FROM = 2;
25
+ /** 「만기」의 창. 이 시간 안에 만기가 오는 것. */
26
+ export const DUE_WITHIN_HOURS = 24;
27
+ /*
28
+ * 연산자를 리터럴로 좁힌다(`as const`). `adjustFilters` 가 받는 타입은 연산자가 정해진 낱말 중
29
+ * 하나인데, 그 타입이 내보내지지 않아 여기서 가져올 수 없다. `string` 으로 두면 넘길 때 거절된다.
30
+ */
31
+ /** 우선순위가 경계 이상인 것. */
32
+ export function importantFilters() {
33
+ return [{ name: 'priority', operator: 'gte', value: IMPORTANT_FROM }];
34
+ }
35
+ /**
36
+ * 만기가 창 안에 드는 것.
37
+ *
38
+ * 부를 때마다 지금 시각으로 계산한다 — 화면을 열어 둔 채 시간이 지나면 「만기 임박」의 범위도 함께
39
+ * 움직여야 한다.
40
+ */
41
+ export function dueSoonFilters() {
42
+ const until = new Date(Date.now() + DUE_WITHIN_HOURS * 3600 * 1000).toISOString().replace('T', ' ');
43
+ return [{ name: 'dueAt', operator: 'lte', value: until }];
44
+ }
45
+ /** 칩 하나 — 이름과 뜻. */
46
+ export function importantChip() {
47
+ return {
48
+ display: i18next.t('label.filter-important'),
49
+ value: 'important',
50
+ hint: i18next.t('text.filter-important-hint', { from: IMPORTANT_FROM })
51
+ };
52
+ }
53
+ export function dueSoonChip() {
54
+ return {
55
+ display: i18next.t('label.filter-due'),
56
+ value: 'due',
57
+ hint: i18next.t('text.filter-due-hint', { hours: DUE_WITHIN_HOURS })
58
+ };
59
+ }
60
+ //# sourceMappingURL=chip-filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chip-filters.js","sourceRoot":"","sources":["../client/chip-filters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8BAA8B;AAC9B,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAA;AAE/B,iCAAiC;AACjC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAElC;;;GAGG;AAEH,sBAAsB;AACtB,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAc,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;AAChF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAEnG,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAc,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;AACpE,CAAC;AAED,oBAAoB;AACpB,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;QAC5C,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,4BAA4B,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;KACxE,CAAA;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC;QACtC,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;KACrE,CAAA;AACH,CAAC","sourcesContent":["import { i18next } from '@operato/i18n'\n\n/**\n * 목록 위의 칩이 무엇을 걸러 내는가 — **한 곳에서 정한다.**\n *\n * ── 왜 모았나 (2026-09-02) ─────────────────────────────────────────────────\n * 「중요」와 「만기」가 다섯 화면에 있는데, 그 뜻이 다섯 곳에 복사돼 있었다.\n *\n * priority gte 2 5곳\n * dueAt lte (지금 + 24 * 3600 * 1000) 4곳\n *\n * 한 화면에서 「중요」를 우선순위 3 이상으로 바꾸면 나머지 넷은 2로 남는다. 그러면 **같은 이름의 칩이\n * 화면마다 다른 것을 걸러 낸다.** 사용자는 그것을 알 방법이 없다.\n *\n * ── 칩은 컬럼 필터가 아니다 ────────────────────────────────────────────────\n * `ox-filters-form` 은 칸 하나와 연산자 하나를 그린다. 이 칩들은 **이름 붙인 조건 묶음**이다 —\n * 「중요」는 `priority = ?` 가 아니라 `priority >= 2` 이고, 「만기」는 지금 시각에서 계산한다.\n *\n * 그래서 사용자가 스스로 만들 수 없다. 「중요 = 우선순위 2 이상」은 제품이 아는 사실이다.\n *\n * ── 그러니 그 뜻을 화면이 말해야 한다 ──────────────────────────────────────\n * 이름만 보이면 눌러 보고 결과를 되짚어야 한다. 칩마다 `hint` 를 함께 낸다(마우스를 올리면 보인다).\n */\n\n/** 「중요」의 경계. 이 값 이상이 중요하다. */\nexport const IMPORTANT_FROM = 2\n\n/** 「만기」의 창. 이 시간 안에 만기가 오는 것. */\nexport const DUE_WITHIN_HOURS = 24\n\n/*\n * 연산자를 리터럴로 좁힌다(`as const`). `adjustFilters` 가 받는 타입은 연산자가 정해진 낱말 중\n * 하나인데, 그 타입이 내보내지지 않아 여기서 가져올 수 없다. `string` 으로 두면 넘길 때 거절된다.\n */\n\n/** 우선순위가 경계 이상인 것. */\nexport function importantFilters() {\n return [{ name: 'priority', operator: 'gte' as const, value: IMPORTANT_FROM }]\n}\n\n/**\n * 만기가 창 안에 드는 것.\n *\n * 부를 때마다 지금 시각으로 계산한다 — 화면을 열어 둔 채 시간이 지나면 「만기 임박」의 범위도 함께\n * 움직여야 한다.\n */\nexport function dueSoonFilters() {\n const until = new Date(Date.now() + DUE_WITHIN_HOURS * 3600 * 1000).toISOString().replace('T', ' ')\n\n return [{ name: 'dueAt', operator: 'lte' as const, value: until }]\n}\n\n/** 칩 하나 — 이름과 뜻. */\nexport function importantChip() {\n return {\n display: i18next.t('label.filter-important'),\n value: 'important',\n hint: i18next.t('text.filter-important-hint', { from: IMPORTANT_FROM })\n }\n}\n\nexport function dueSoonChip() {\n return {\n display: i18next.t('label.filter-due'),\n value: 'due',\n hint: i18next.t('text.filter-due-hint', { hours: DUE_WITHIN_HOURS })\n }\n}\n"]}
@@ -7,6 +7,25 @@ export declare class ActivityApprovalRibon extends ActivityApprovalRibon_base {
7
7
  static styles: import("lit").CSSResult[];
8
8
  activityApproval: any;
9
9
  render(): import("lit-html").TemplateResult<1>;
10
+ /**
11
+ * 이 건의 기본 정보 — **팝업으로 연다.**
12
+ *
13
+ * ── 왜 aside 가 아닌가 (2026-09-06) ────────────────────────────────────
14
+ * 전에는 화면 오른쪽에서 나오는 aside 판이었다. 두 가지가 걸린다.
15
+ *
16
+ * 자리를 다툰다 화면 오른쪽 세로 판은 operato-twin 의 twin-entity360 이 쓰는 자리다
17
+ * (position: fixed · right: 0 · height: 100vh). 같은 자리에 둘이 선다.
18
+ * 전역 슬롯이다 VIEWPART_POSITION.ASIDEBAR 은 앱이 하나만 갖는다. 열 개가 넘는 패키지가
19
+ * 그 자리를 쓰고 있어(twin 의 AI 협의 도크 · board-ui · help · more-ui …),
20
+ * 이 판을 열면 그중 열려 있던 것이 닫힌다.
21
+ *
22
+ * 그리고 자리의 뜻이 다르다. 오른쪽 드로어는 **보고 있는 대상을 따라다니는 상세**이고(360 이
23
+ * 그것이다 — 포커스가 바뀌면 내용이 바뀌고 화면을 옮겨도 남는다), 이 판은 **이 화면의 이 건**에
24
+ * 대한 것이라 화면을 벗어나면 뜻이 없다. 페이지에 매인 것을 앱 전역 자리에 두지 않는다.
25
+ *
26
+ * 내용도 팝업 크기다 — 값 여덟 줄과 접힌 타임라인이라 세로 100vh 를 쓸 일이 없었다.
27
+ */
28
+ private openInfo;
10
29
  updated(changes: any): void;
11
30
  }
12
31
  export {};