@things-factory/worklist 10.1.167 → 10.1.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/components/activity-starter-form.js +2 -0
- package/dist-client/components/activity-starter-form.js.map +1 -1
- package/dist-client/components/decision-content.d.ts +35 -0
- package/dist-client/components/decision-content.js +310 -0
- package/dist-client/components/decision-content.js.map +1 -0
- package/dist-client/components/decision-slots.d.ts +8 -0
- package/dist-client/components/decision-slots.js +17 -0
- package/dist-client/components/decision-slots.js.map +1 -1
- package/dist-client/components/work-box-groups.d.ts +25 -0
- package/dist-client/components/work-box-groups.js +98 -0
- package/dist-client/components/work-box-groups.js.map +1 -0
- package/dist-client/my-work-menus.d.ts +1 -6
- package/dist-client/my-work-menus.js +0 -1
- package/dist-client/my-work-menus.js.map +1 -1
- package/dist-client/pages/activity/activity-list-page.js +1 -0
- package/dist-client/pages/activity/activity-list-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-page.d.ts +2 -52
- package/dist-client/pages/activity-approval/activity-approval-page.js +9 -298
- package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
- package/dist-client/pages/activity-catalog/activity-detail-page.js +9 -2
- package/dist-client/pages/activity-catalog/activity-detail-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-start-page.js +3 -0
- package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-view.d.ts +10 -1
- package/dist-client/pages/activity-instance/activity-instance-view.js +31 -2
- package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.d.ts +6 -2
- package/dist-client/pages/activity-thread/activity-thread-page.js +32 -16
- package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view-page.js +3 -2
- package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
- package/dist-client/pages/activity-thread/thread-sections.d.ts +34 -0
- package/dist-client/pages/activity-thread/thread-sections.js +36 -0
- package/dist-client/pages/activity-thread/thread-sections.js.map +1 -0
- package/dist-client/pages/todo/approval-pending-list-page.d.ts +1 -0
- package/dist-client/pages/todo/approval-pending-list-page.js +10 -77
- package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
- package/dist-client/pages/todo/draft-list-page.d.ts +20 -0
- package/dist-client/pages/todo/draft-list-page.js +103 -5
- package/dist-client/pages/todo/draft-list-page.js.map +1 -1
- package/dist-client/pages/todo/todo-list-page.d.ts +19 -0
- package/dist-client/pages/todo/todo-list-page.js +147 -23
- package/dist-client/pages/todo/todo-list-page.js.map +1 -1
- package/dist-client/pages/todo/work-box.d.ts +36 -0
- package/dist-client/pages/todo/work-box.js +44 -0
- package/dist-client/pages/todo/work-box.js.map +1 -0
- package/dist-client/pages/work-door/work-doors-page.d.ts +1 -15
- package/dist-client/pages/work-door/work-doors-page.js +18 -43
- package/dist-client/pages/work-door/work-doors-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +0 -3
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types/activity.d.ts +2 -0
- package/dist-client/types/activity.js.map +1 -1
- package/dist-server/controllers/activity-approval/create-approval.js +2 -1
- package/dist-server/controllers/activity-approval/create-approval.js.map +1 -1
- package/dist-server/controllers/activity-approval/resolve-judgeable-seats.js +2 -1
- package/dist-server/controllers/activity-approval/resolve-judgeable-seats.js.map +1 -1
- package/dist-server/service/activity-summary/index.d.ts +1 -1
- package/dist-server/service/activity-summary/open-work.d.ts +0 -1
- package/dist-server/service/activity-summary/open-work.js +3 -15
- package/dist-server/service/activity-summary/open-work.js.map +1 -1
- package/dist-server/service/activity-thread/activity-thread-query.d.ts +3 -0
- package/dist-server/service/activity-thread/activity-thread-query.js +24 -5
- package/dist-server/service/activity-thread/activity-thread-query.js.map +1 -1
- package/dist-server/service/activity-thread/index.d.ts +1 -1
- package/dist-server/service/activity-thread/my-open-threads.d.ts +23 -4
- package/dist-server/service/activity-thread/my-open-threads.js +38 -7
- package/dist-server/service/activity-thread/my-open-threads.js.map +1 -1
- package/dist-server/service/index.d.ts +3 -3
- package/dist-server/service/work-door/approval-line-standing.d.ts +14 -0
- package/dist-server/service/work-door/approval-line-standing.js +23 -5
- package/dist-server/service/work-door/approval-line-standing.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/tests/open-doors.test.ts +3 -2
- package/things-factory.config.js +0 -1
- package/translations/en.json +10 -2
- package/translations/es.json +10 -2
- package/translations/ja.json +10 -2
- package/translations/ko.json +10 -2
- package/translations/ms.json +10 -2
- package/translations/zh.json +10 -2
|
@@ -2,6 +2,7 @@ import { __decorate, __metadata } from "tslib";
|
|
|
2
2
|
import '@operato/data-grist';
|
|
3
3
|
import '@operato/data-grist/ox-filters-form.js';
|
|
4
4
|
import '@operato/input/ox-input-select-buttons.js';
|
|
5
|
+
import '../../components/work-box-groups.js';
|
|
5
6
|
import gql from 'graphql-tag';
|
|
6
7
|
import { css, html } from 'lit';
|
|
7
8
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
@@ -11,11 +12,18 @@ import { dueSoonChip, dueSoonFilters, importantChip, importantFilters } from '..
|
|
|
11
12
|
import { i18next, localize } from '@operato/i18n';
|
|
12
13
|
import { navigate, PageView } from '@operato/shell';
|
|
13
14
|
import { listWithThumbnail } from './list-thumbnail.js';
|
|
15
|
+
import { countsOnTab, groupAddress, groupAt, TODO_GROUPS } from './work-box.js';
|
|
14
16
|
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
|
|
15
17
|
import { adjustFilters, isMobileDevice } from '@operato/utils';
|
|
16
18
|
import { p13n } from '@operato/p13n';
|
|
17
19
|
import { dueCell, priorityCell, subjectCell } from '../../activity-cells.js';
|
|
18
20
|
import { ActivityThreadStatus } from '../../types/activity-thread.js';
|
|
21
|
+
/** 묶음마다 묻는 목록 — 서버의 세 질의가 같은 줄 모양을 준다. */
|
|
22
|
+
const QUERY_OF = {
|
|
23
|
+
todo: 'todoList',
|
|
24
|
+
submitted: 'submittedList',
|
|
25
|
+
completed: 'doneList'
|
|
26
|
+
};
|
|
19
27
|
const MIN = 60;
|
|
20
28
|
const HOUR = 60 * MIN;
|
|
21
29
|
const DAY = 24 * HOUR;
|
|
@@ -23,6 +31,15 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
23
31
|
constructor() {
|
|
24
32
|
super(...arguments);
|
|
25
33
|
this.mode = isMobileDevice() ? 'CARD' : 'GRID';
|
|
34
|
+
/**
|
|
35
|
+
* 지금 어느 묶음을 보고 있나 — 할 것 · 제출한 것 · 완료 (pages/todo/work-box.ts).
|
|
36
|
+
*
|
|
37
|
+
* 내가 올린 건은 내 차례일 때만 여기 있다. 제출하고 결재를 기다리는 동안과 끝난 뒤에는 상신함이
|
|
38
|
+
* 따라간다 — 서버의 myThreadsOfOthersWhere 가 그 경계다.
|
|
39
|
+
*/
|
|
40
|
+
this.group = 'todo';
|
|
41
|
+
/** 기다리는 두 묶음의 건수. 끝난 묶음은 이력이라 세지 않는다. */
|
|
42
|
+
this.counts = {};
|
|
26
43
|
}
|
|
27
44
|
static { this.styles = [
|
|
28
45
|
/* The work surface frame for a list screen (ADR-0060 decision 8). */
|
|
@@ -108,6 +125,16 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
108
125
|
render() {
|
|
109
126
|
const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID');
|
|
110
127
|
return html `
|
|
128
|
+
<work-box-groups
|
|
129
|
+
.groups=${[
|
|
130
|
+
['todo', 'label.group-todo', 'text.group-todo-hint'],
|
|
131
|
+
['submitted', 'label.group-submitted', 'text.group-submitted-hint'],
|
|
132
|
+
['completed', 'label.group-completed', 'text.group-done-hint']
|
|
133
|
+
].map(([key, label, hint]) => ({ key, label, hint, count: countsOnTab(key) ? this.counts[key] : undefined }))}
|
|
134
|
+
.selected=${this.group}
|
|
135
|
+
@select=${(e) => this.selectGroup(e.detail)}
|
|
136
|
+
></work-box-groups>
|
|
137
|
+
|
|
111
138
|
<ox-grist class="page-fill"
|
|
112
139
|
.mode=${mode}
|
|
113
140
|
.config=${this.gristConfig}
|
|
@@ -120,12 +147,7 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
120
147
|
<ox-filters-form class="filter" without-search></ox-filters-form>
|
|
121
148
|
<ox-input-select-buttons
|
|
122
149
|
.value=${this.filters}
|
|
123
|
-
.options=${
|
|
124
|
-
importantChip(),
|
|
125
|
-
dueSoonChip(),
|
|
126
|
-
{ display: i18next.t('label.filter-proceeding'), value: 'proceeding' },
|
|
127
|
-
{ display: i18next.t('label.filter-pending-approval'), value: 'pending-approval' }
|
|
128
|
-
]}
|
|
150
|
+
.options=${this.chips()}
|
|
129
151
|
@change=${(e) => {
|
|
130
152
|
this.filters = e.currentTarget?.value;
|
|
131
153
|
this.grist.fetch();
|
|
@@ -140,7 +162,62 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
140
162
|
</ox-grist>
|
|
141
163
|
`;
|
|
142
164
|
}
|
|
165
|
+
/** 묶음마다 좁히는 칩이 다르다 — 끝난 일에 「만기」는 물음이 아니다. */
|
|
166
|
+
chips() {
|
|
167
|
+
switch (this.group) {
|
|
168
|
+
case 'todo':
|
|
169
|
+
return [importantChip(), dueSoonChip(), { display: i18next.t('label.filter-proceeding'), value: 'proceeding' }];
|
|
170
|
+
case 'submitted':
|
|
171
|
+
return [importantChip(), dueSoonChip()];
|
|
172
|
+
case 'completed':
|
|
173
|
+
return [
|
|
174
|
+
importantChip(),
|
|
175
|
+
{ display: i18next.t('label.filter-recent'), value: 'recent' },
|
|
176
|
+
{ display: i18next.t('label.filter-aborted'), value: 'aborted' }
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** 주소만 바꾼다 — 목록도 설정도 pageUpdated 가 한 벌로 다시 세운다. */
|
|
181
|
+
selectGroup(group) {
|
|
182
|
+
if (this.group === group) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
navigate(groupAddress(this.lifecycle?.page, group, TODO_GROUPS));
|
|
186
|
+
}
|
|
187
|
+
syncGroup(key) {
|
|
188
|
+
const group = groupAt(key, TODO_GROUPS);
|
|
189
|
+
if (this.group === group && this.gristConfig) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.group = group;
|
|
193
|
+
this.filters = [];
|
|
194
|
+
this.applyConfig();
|
|
195
|
+
}
|
|
196
|
+
/** 안 보고 있는 기다리는 묶음의 건수 — 한 줄만 받아 total 을 읽는다. 못 읽은 수는 0 이 아니다. */
|
|
197
|
+
async countOther() {
|
|
198
|
+
const other = this.group === 'submitted' ? 'todo' : 'submitted';
|
|
199
|
+
const response = await client.query({
|
|
200
|
+
query: gql `
|
|
201
|
+
query ($pagination: Pagination) {
|
|
202
|
+
responses: ${QUERY_OF[other]}(pagination: $pagination) {
|
|
203
|
+
total
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
`,
|
|
207
|
+
variables: { pagination: { page: 1, limit: 1 } },
|
|
208
|
+
fetchPolicy: 'no-cache'
|
|
209
|
+
});
|
|
210
|
+
const total = response.errors?.length ? undefined : response.data?.responses?.total;
|
|
211
|
+
if (typeof total == 'number')
|
|
212
|
+
this.counts = { ...this.counts, [other]: total };
|
|
213
|
+
}
|
|
143
214
|
async pageInitialized(lifecycle) {
|
|
215
|
+
/* pageUpdated 가 이 함수보다 먼저 지나가므로 주소로 바로 들어온 경우는 여기서 잡힌다. */
|
|
216
|
+
this.syncGroup(lifecycle?.resourceId);
|
|
217
|
+
await this.countOther();
|
|
218
|
+
}
|
|
219
|
+
applyConfig() {
|
|
220
|
+
const completed = this.group === 'completed';
|
|
144
221
|
this.gristConfig = {
|
|
145
222
|
list: { thumbnail: 'thumbnail', fields: ['name', 'description'], details: ['createdAt', 'due'] },
|
|
146
223
|
columns: [
|
|
@@ -185,14 +262,20 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
185
262
|
sortable: true,
|
|
186
263
|
width: 85
|
|
187
264
|
},
|
|
265
|
+
/*
|
|
266
|
+
* 기한 — 끝난 일에서는 **다른 날짜 열과 같이** 시각만 적는다.
|
|
267
|
+
*
|
|
268
|
+
* dueCell 은 Date.now() 를 기준으로 「4일 13시간 지남」을 내고 1분마다 다시 센다. 아직 안 끝난
|
|
269
|
+
* 일에는 그게 물음이다 — 지금부터 얼마 남았나. 끝난 일에는 그 물음이 없고, 그대로 두면 **거짓을
|
|
270
|
+
* 말한다.** 기한 안에 끝낸 건도 오늘 날짜가 흐르면 빨간 「N일 지남」으로 바뀐다.
|
|
271
|
+
*
|
|
272
|
+
* 「기한을 지켰나」는 줄마다가 아니라 모아서 보는 물음이다 — activity-stats 의 자리다.
|
|
273
|
+
*/
|
|
188
274
|
{
|
|
189
275
|
type: 'datetime',
|
|
190
276
|
name: 'dueAt',
|
|
191
277
|
header: i18next.t('field.wl-due'),
|
|
192
|
-
record: {
|
|
193
|
-
editable: false,
|
|
194
|
-
renderer: dueCell(() => this.active)
|
|
195
|
-
},
|
|
278
|
+
record: completed ? { editable: false } : { editable: false, renderer: dueCell(() => this.active) },
|
|
196
279
|
sortable: true,
|
|
197
280
|
width: 180
|
|
198
281
|
},
|
|
@@ -229,6 +312,26 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
229
312
|
},
|
|
230
313
|
width: 85
|
|
231
314
|
},
|
|
315
|
+
...(completed
|
|
316
|
+
? [
|
|
317
|
+
{
|
|
318
|
+
type: 'datetime',
|
|
319
|
+
name: 'startedAt',
|
|
320
|
+
header: i18next.t('field.wl-started-at'),
|
|
321
|
+
record: { editable: false },
|
|
322
|
+
sortable: true,
|
|
323
|
+
width: 180
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
type: 'datetime',
|
|
327
|
+
name: 'terminatedAt',
|
|
328
|
+
header: i18next.t('field.wl-terminated-at'),
|
|
329
|
+
record: { editable: false },
|
|
330
|
+
sortable: true,
|
|
331
|
+
width: 180
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
: []),
|
|
232
335
|
{
|
|
233
336
|
type: 'datetime',
|
|
234
337
|
name: 'createdAt',
|
|
@@ -270,11 +373,15 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
270
373
|
}
|
|
271
374
|
}
|
|
272
375
|
},
|
|
273
|
-
sorters: [{ name: 'dueAt', desc: false }],
|
|
376
|
+
sorters: completed ? [{ name: 'terminatedAt', desc: true }] : [{ name: 'dueAt', desc: false }],
|
|
274
377
|
pagination: { limit: 50, pages: [20, 50, 100, 500] }
|
|
275
378
|
};
|
|
276
379
|
}
|
|
277
380
|
async pageUpdated(changes, lifecycle, changedBefore) {
|
|
381
|
+
if (lifecycle?.resourceId !== changedBefore?.resourceId) {
|
|
382
|
+
this.syncGroup(lifecycle?.resourceId);
|
|
383
|
+
await this.countOther();
|
|
384
|
+
}
|
|
278
385
|
if (this.active) {
|
|
279
386
|
this.grist?.fetch();
|
|
280
387
|
}
|
|
@@ -295,19 +402,18 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
295
402
|
if (this.filters?.includes('proceeding')) {
|
|
296
403
|
filters = adjustFilters(filters, [{ name: 'state', operator: 'eq', value: ActivityThreadStatus.Started }]);
|
|
297
404
|
}
|
|
298
|
-
if (this.filters?.includes('
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
]);
|
|
405
|
+
if (this.filters?.includes('recent')) {
|
|
406
|
+
/* 최근 24시간 이내에 끝낸 일 */
|
|
407
|
+
const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString().replace('T', ' ');
|
|
408
|
+
filters = adjustFilters(filters, [{ name: 'terminatedAt', operator: 'gte', value: since }]);
|
|
409
|
+
}
|
|
410
|
+
if (this.filters?.includes('aborted')) {
|
|
411
|
+
filters = adjustFilters(filters, [{ name: 'state', operator: 'eq', value: ActivityThreadStatus.Aborted }]);
|
|
306
412
|
}
|
|
307
413
|
const response = await client.query({
|
|
308
414
|
query: gql `
|
|
309
415
|
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
310
|
-
responses:
|
|
416
|
+
responses: ${QUERY_OF[this.group]}(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
311
417
|
items {
|
|
312
418
|
id
|
|
313
419
|
activityInstance {
|
|
@@ -332,6 +438,8 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
332
438
|
output
|
|
333
439
|
dueAt
|
|
334
440
|
assignedAt
|
|
441
|
+
startedAt
|
|
442
|
+
terminatedAt
|
|
335
443
|
}
|
|
336
444
|
total
|
|
337
445
|
}
|
|
@@ -339,22 +447,30 @@ let TodoListPage = class TodoListPage extends p13n(localize(i18next)(PageView))
|
|
|
339
447
|
`,
|
|
340
448
|
variables: { filters, pagination: { page, limit }, sortings }
|
|
341
449
|
});
|
|
450
|
+
const total = response.data.responses.total || 0;
|
|
451
|
+
/* 보고 있는 묶음의 수는 이 조회가 알려 준다. 나머지는 countOther 가 센다. */
|
|
452
|
+
if (countsOnTab(this.group))
|
|
453
|
+
this.counts = { ...this.counts, [this.group]: total };
|
|
342
454
|
return {
|
|
343
|
-
total
|
|
455
|
+
total,
|
|
344
456
|
records: (response.data.responses.items || []).map(item => {
|
|
345
457
|
return {
|
|
458
|
+
...item.activityInstance,
|
|
346
459
|
id: item.id,
|
|
347
460
|
state: item.state,
|
|
348
461
|
output: item.output,
|
|
349
462
|
dueAt: item.dueAt,
|
|
350
463
|
assignedAt: item.assignedAt,
|
|
351
|
-
|
|
464
|
+
startedAt: item.startedAt,
|
|
465
|
+
terminatedAt: item.terminatedAt
|
|
352
466
|
};
|
|
353
467
|
})
|
|
354
468
|
};
|
|
355
469
|
}
|
|
470
|
+
/** 끝난 일은 보기 화면으로 연다 — 고칠 것이 없는 화면에 저장 · 제출이 서면 안 된다. */
|
|
356
471
|
openActivityThread(activityThread) {
|
|
357
|
-
|
|
472
|
+
const page = this.group === 'completed' ? 'activity-thread-view' : 'activity-thread';
|
|
473
|
+
navigate(`${page}/${activityThread.id}?title=${activityThread.name}`);
|
|
358
474
|
}
|
|
359
475
|
};
|
|
360
476
|
__decorate([
|
|
@@ -373,6 +489,14 @@ __decorate([
|
|
|
373
489
|
state(),
|
|
374
490
|
__metadata("design:type", Array)
|
|
375
491
|
], TodoListPage.prototype, "filters", void 0);
|
|
492
|
+
__decorate([
|
|
493
|
+
state(),
|
|
494
|
+
__metadata("design:type", String)
|
|
495
|
+
], TodoListPage.prototype, "group", void 0);
|
|
496
|
+
__decorate([
|
|
497
|
+
state(),
|
|
498
|
+
__metadata("design:type", Object)
|
|
499
|
+
], TodoListPage.prototype, "counts", void 0);
|
|
376
500
|
TodoListPage = __decorate([
|
|
377
501
|
customElement('todo-list-page')
|
|
378
502
|
], TodoListPage);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"todo-list-page.js","sourceRoot":"","sources":["../../../client/pages/todo/todo-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport '@operato/data-grist/ox-filters-form.js'\nimport '@operato/input/ox-input-select-buttons.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\nimport { DataGrist, FetchOption, getRenderer } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { dueSoonChip, dueSoonFilters, importantChip, importantFilters } from '../../chip-filters.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { listWithThumbnail } from './list-thumbnail.js'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { adjustFilters, isMobileDevice, sleep } from '@operato/utils'\n\nimport { p13n } from '@operato/p13n'\n\nimport { dueCell, priorityCell, subjectCell } from '../../activity-cells.js'\n\nimport { ActivityThreadStatus } from '../../types/activity-thread.js'\n\nconst MIN = 60\nconst HOUR = 60 * MIN\nconst DAY = 24 * HOUR\n\n@customElement('todo-list-page')\nexport class TodoListPage extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n --grid-record-emphasized-background-color: #8b0000;\n --grid-record-emphasized-color: #ff6b6b;\n }\n\n /*\n * 제목은 헤드룸에 두지 않는다.\n *\n * MDI 를 켜면 제목이 이미 두 곳에 있다 — 헤더 바와 탭. 여기에 또 두면 한 화면에 같은 글자가\n * 셋이 된다. 그래서 표준 배치의 title 줄을 쓰지 않고 한 줄로 둔다.\n */\n .header {\n grid-template-areas: 'filters actions';\n }\n\n /*\n * ── 헤드룸의 왼쪽 기준선은 표와 같다 ─────────────────────────────────\n * CommonHeaderStyles 의 .filters 는 배경과 모서리와 여백을 가진 영역이다. 그 배경이\n * --md-sys-color-surface 인데 이 제품에서는 바탕색과 같은 값이라, **보이지 않는 영역이\n * 자리만 차지하고** 검색창을 여백만큼(12px) 안쪽으로 밀었다.\n *\n * 실측: 표 왼쪽 끝 212 · 필터 영역 212 · 검색창 224. 화면의 왼쪽 기준선이 둘이 됐다.\n *\n * 그 규칙들은 .header 안에 중첩되어 있어(.header .filters) 밑줄 없는 .filters 로는 이길 수\n * 없다 — 특정도가 0,1,0 대 0,2,0 이다.\n */\n .header .filters {\n padding: 0;\n background: none;\n border-radius: 0;\n gap: 12px;\n }\n\n `\n ]\n\n @property({ type: String }) mode: 'GRID' | 'LIST' | 'CARD' = isMobileDevice() ? 'CARD' : 'GRID'\n\n @query('ox-grist') grist!: DataGrist\n\n @state() gristConfig?: any\n @state() filters?: string[]\n\n get context() {\n return {\n title: i18next.t('title.todo list'),\n /*\n * 검색의 자리는 **기기가 정한다.**\n *\n * PC 에서는 화면 안으로 내린다. 상단 바에 두면 거른 조건과 걸러진 결과가 화면의 위아래 끝으로\n * 갈라지고, 지금 무엇으로 걸렀는지가 목록 옆에서 보이지 않는다.\n *\n * 좁은 화면에서는 상단 바에 그대로 둔다. 헤드룸에 두면 검색창과 필터가 목록을 그만큼 밀어\n * 내리고, 카드 두 장이 화면 밖으로 나간다. 상단 바는 그 자리를 아끼려고 있는 것이다.\n *\n * 이 저장소에는 두 방식이 다 있다 — 헤드룸 50곳, 상단 바 51곳. 정형인 것은 구조\n * (.header / .filters / .filter)이고 검색의 자리는 화면이 정한다.\n */\n /*\n * 검색은 **페이지 제목 줄의 오른쪽 끝**에 둔다 — 다른 화면들과 같은 자리다.\n *\n * 전에는 좁은 화면에서만 여기에 넣고, 넓은 화면에서는 헤드룸의 ox-filters-form 이 검색을\n * 그렸다. 그래서 같은 앱 안에서 검색 상자의 자리가 화면마다 달랐다.\n *\n * 헤드룸에는 좁히는 칩만 남는다. 폼은 without-search 로 남겨 두어, 나중에 어느 칸에\n * filter 를 달면 그 UI 가 저절로 생긴다(비면 아무것도 그리지 않는다).\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n help: 'worklist/todo-list-page',\n actions: []\n }\n }\n\n render() {\n const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')\n\n return html`\n <ox-grist class=\"page-fill\"\n .mode=${mode}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <!-- 좁은 화면에서는 상단 바가 검색을 갖는다(위 context 주석 참조). -->\n <ox-filters-form class=\"filter\" without-search></ox-filters-form>\n <ox-input-select-buttons\n .value=${this.filters}\n .options=${[\n importantChip(),\n dueSoonChip(),\n { display: i18next.t('label.filter-proceeding'), value: 'proceeding' },\n { display: i18next.t('label.filter-pending-approval'), value: 'pending-approval' }\n ]}\n @change=${(e: CustomEvent) => {\n this.filters = (e.currentTarget as any)?.value\n this.grist.fetch()\n }}\n ></ox-input-select-buttons>\n <!-- <ox-filters-form class=\"filter\" autofocus without-search></ox-filters-form> -->\n </div>\n\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n async pageInitialized(lifecycle) {\n this.gristConfig = {\n list: { thumbnail: 'thumbnail', fields: ['name', 'description'], details: ['createdAt', 'due'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.wl-subject'),\n fixed: true,\n record: {\n editable: false,\n renderer: subjectCell()\n },\n filter: 'search',\n sortable: true,\n width: 250\n },\n {\n /*\n * 설명은 제목 칸 안에서 두 줄로 보인다(activity-cells 의 subjectCell). 그래서 따로 보일 칸이\n * 필요하지 않다.\n *\n * 그런데 **칸을 없애면 설명으로 검색하는 기능이 함께 사라진다.** searchText 는\n * filter: 'search' 를 선언한 컬럼마다 필터를 만들고 hidden 은 보지 않는다\n * (data-grist.ts 의 set searchText). 실측으로 확인했다 — 컬럼을 없앤 뒤 설명에만 있는\n * 낱말로 검색하면 0건이었다.\n *\n * 그래서 감춘 칸으로 남겨 검색만 남긴다.\n */\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: false },\n filter: 'search',\n hidden: true\n },\n {\n type: 'resource-object',\n name: 'creator',\n header: i18next.t('field.wl-drafter'),\n record: { editable: false },\n sortable: true,\n width: 85\n },\n {\n type: 'datetime',\n name: 'dueAt',\n header: i18next.t('field.wl-due'),\n record: {\n editable: false,\n renderer: dueCell(() => this.active)\n },\n sortable: true,\n width: 180\n },\n {\n type: 'number',\n name: 'priority',\n header: i18next.t('field.wl-priority'),\n record: {\n editable: false,\n align: 'center',\n renderer: priorityCell()\n },\n sortable: true,\n width: 70\n },\n {\n type: 'select',\n name: 'state',\n header: i18next.t('field.wl-state'),\n record: {\n editable: false,\n align: 'center',\n options: [\n { display: i18next.t('label.activity-state-unassigned'), value: 'unassigned' },\n { display: i18next.t('label.activity-state-assigned'), value: 'assigned' },\n { display: i18next.t('label.activity-state-started'), value: 'started' },\n { display: i18next.t('label.activity-state-delegated'), value: 'delegated' },\n { display: i18next.t('label.activity-state-submitted'), value: 'submitted' },\n { display: i18next.t('label.activity-state-escalated'), value: 'escalated' },\n { display: i18next.t('label.activity-state-rejected'), value: 'rejected' },\n { display: i18next.t('label.activity-state-ended'), value: 'ended' },\n { display: i18next.t('label.activity-state-aborted'), value: 'aborted' }\n ]\n },\n width: 85\n },\n {\n type: 'datetime',\n name: 'createdAt',\n header: i18next.t('field.wl-submitted-at'),\n record: { editable: false },\n sortable: true,\n filter: false,\n width: 180\n },\n {\n type: 'string',\n name: 'uiSource',\n header: i18next.t('field.wl-view-source'),\n record: {\n editable: false,\n renderer: function (value, column, record, rowIndex, field) {\n const { uiType, uiSource } = record.activity || {}\n var type = uiType !== 'board' ? 'string' : 'board'\n return getRenderer(type)(uiSource, column, record, rowIndex, field)\n }\n },\n width: 140,\n hidden: true\n },\n {\n type: 'image',\n name: 'thumbnail',\n header: i18next.t('field.thumbnail'),\n record: { editable: false },\n hidden: true\n }\n ],\n rows: {\n appendable: false,\n editable: false,\n handlers: {\n click: (columns, data, column, record, rowIndex) => {\n this.openActivityThread(record)\n }\n }\n },\n sorters: [{ name: 'dueAt', desc: false }],\n pagination: { limit: 50, pages: [20, 50, 100, 500] }\n }\n }\n\n async pageUpdated(changes, lifecycle, changedBefore) {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n\n /* 읽은 것에 그림이 하나도 없으면 카드마다 빈 그림 칸을 세우지 않는다(§ list-thumbnail.ts). */\n async fetchHandler(options: FetchOption) {\n const fetched = await this.fetchRows(options)\n\n this.gristConfig = listWithThumbnail(this.gristConfig, fetched?.records || [])\n\n return fetched\n }\n\n async fetchRows({ page, limit, sortings = [], filters = [] }: FetchOption) {\n if (this.filters?.includes('important')) {\n filters = adjustFilters(filters, importantFilters())\n }\n\n if (this.filters?.includes('due')) {\n filters = adjustFilters(filters, dueSoonFilters())\n }\n\n if (this.filters?.includes('proceeding')) {\n filters = adjustFilters(filters, [{ name: 'state', operator: 'eq', value: ActivityThreadStatus.Started }])\n }\n\n if (this.filters?.includes('pending-approval')) {\n filters = adjustFilters(filters, [\n {\n name: 'state',\n operator: 'in',\n value: [ActivityThreadStatus.Rejected, ActivityThreadStatus.Submitted, ActivityThreadStatus.Escalated]\n }\n ])\n }\n\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: todoList(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n activityInstance {\n activityId: id\n name\n description\n priority\n input\n refBy\n activityType\n uiType\n uiSource\n thumbnail\n updatedAt\n createdAt\n creator {\n id\n name\n }\n }\n state\n output\n dueAt\n assignedAt\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings }\n })\n\n return {\n total: response.data.responses.total || 0,\n records: (response.data.responses.items || []).map(item => {\n return {\n id: item.id,\n state: item.state,\n output: item.output,\n dueAt: item.dueAt,\n assignedAt: item.assignedAt,\n ...item.activityInstance\n }\n })\n }\n }\n\n openActivityThread(activityThread) {\n navigate(`activity-thread/${activityThread.id}?title=${activityThread.name}`)\n }\n\n}\n"]}
|
|
1
|
+
{"version":3,"file":"todo-list-page.js","sourceRoot":"","sources":["../../../client/pages/todo/todo-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport '@operato/data-grist/ox-filters-form.js'\nimport '@operato/input/ox-input-select-buttons.js'\nimport '../../components/work-box-groups.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\nimport { DataGrist, FetchOption, getRenderer } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { dueSoonChip, dueSoonFilters, importantChip, importantFilters } from '../../chip-filters.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { listWithThumbnail } from './list-thumbnail.js'\nimport { countsOnTab, groupAddress, groupAt, TODO_GROUPS, TodoGroup } from './work-box.js'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { adjustFilters, isMobileDevice, sleep } from '@operato/utils'\n\nimport { p13n } from '@operato/p13n'\n\nimport { dueCell, priorityCell, subjectCell } from '../../activity-cells.js'\n\nimport { ActivityThreadStatus } from '../../types/activity-thread.js'\n\n/** 묶음마다 묻는 목록 — 서버의 세 질의가 같은 줄 모양을 준다. */\nconst QUERY_OF: Record<TodoGroup, string> = {\n todo: 'todoList',\n submitted: 'submittedList',\n completed: 'doneList'\n}\n\nconst MIN = 60\nconst HOUR = 60 * MIN\nconst DAY = 24 * HOUR\n\n@customElement('todo-list-page')\nexport class TodoListPage extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n --grid-record-emphasized-background-color: #8b0000;\n --grid-record-emphasized-color: #ff6b6b;\n }\n\n /*\n * 제목은 헤드룸에 두지 않는다.\n *\n * MDI 를 켜면 제목이 이미 두 곳에 있다 — 헤더 바와 탭. 여기에 또 두면 한 화면에 같은 글자가\n * 셋이 된다. 그래서 표준 배치의 title 줄을 쓰지 않고 한 줄로 둔다.\n */\n .header {\n grid-template-areas: 'filters actions';\n }\n\n /*\n * ── 헤드룸의 왼쪽 기준선은 표와 같다 ─────────────────────────────────\n * CommonHeaderStyles 의 .filters 는 배경과 모서리와 여백을 가진 영역이다. 그 배경이\n * --md-sys-color-surface 인데 이 제품에서는 바탕색과 같은 값이라, **보이지 않는 영역이\n * 자리만 차지하고** 검색창을 여백만큼(12px) 안쪽으로 밀었다.\n *\n * 실측: 표 왼쪽 끝 212 · 필터 영역 212 · 검색창 224. 화면의 왼쪽 기준선이 둘이 됐다.\n *\n * 그 규칙들은 .header 안에 중첩되어 있어(.header .filters) 밑줄 없는 .filters 로는 이길 수\n * 없다 — 특정도가 0,1,0 대 0,2,0 이다.\n */\n .header .filters {\n padding: 0;\n background: none;\n border-radius: 0;\n gap: 12px;\n }\n\n `\n ]\n\n @property({ type: String }) mode: 'GRID' | 'LIST' | 'CARD' = isMobileDevice() ? 'CARD' : 'GRID'\n\n @query('ox-grist') grist!: DataGrist\n\n @state() gristConfig?: any\n @state() filters?: string[]\n\n /**\n * 지금 어느 묶음을 보고 있나 — 할 것 · 제출한 것 · 완료 (pages/todo/work-box.ts).\n *\n * 내가 올린 건은 내 차례일 때만 여기 있다. 제출하고 결재를 기다리는 동안과 끝난 뒤에는 상신함이\n * 따라간다 — 서버의 myThreadsOfOthersWhere 가 그 경계다.\n */\n @state() private group: TodoGroup = 'todo'\n\n /** 기다리는 두 묶음의 건수. 끝난 묶음은 이력이라 세지 않는다. */\n @state() private counts: Partial<Record<TodoGroup, number>> = {}\n\n get context() {\n return {\n title: i18next.t('title.todo list'),\n /*\n * 검색의 자리는 **기기가 정한다.**\n *\n * PC 에서는 화면 안으로 내린다. 상단 바에 두면 거른 조건과 걸러진 결과가 화면의 위아래 끝으로\n * 갈라지고, 지금 무엇으로 걸렀는지가 목록 옆에서 보이지 않는다.\n *\n * 좁은 화면에서는 상단 바에 그대로 둔다. 헤드룸에 두면 검색창과 필터가 목록을 그만큼 밀어\n * 내리고, 카드 두 장이 화면 밖으로 나간다. 상단 바는 그 자리를 아끼려고 있는 것이다.\n *\n * 이 저장소에는 두 방식이 다 있다 — 헤드룸 50곳, 상단 바 51곳. 정형인 것은 구조\n * (.header / .filters / .filter)이고 검색의 자리는 화면이 정한다.\n */\n /*\n * 검색은 **페이지 제목 줄의 오른쪽 끝**에 둔다 — 다른 화면들과 같은 자리다.\n *\n * 전에는 좁은 화면에서만 여기에 넣고, 넓은 화면에서는 헤드룸의 ox-filters-form 이 검색을\n * 그렸다. 그래서 같은 앱 안에서 검색 상자의 자리가 화면마다 달랐다.\n *\n * 헤드룸에는 좁히는 칩만 남는다. 폼은 without-search 로 남겨 두어, 나중에 어느 칸에\n * filter 를 달면 그 UI 가 저절로 생긴다(비면 아무것도 그리지 않는다).\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n help: 'worklist/todo-list-page',\n actions: []\n }\n }\n\n render() {\n const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')\n\n return html`\n <work-box-groups\n .groups=${(\n [\n ['todo', 'label.group-todo', 'text.group-todo-hint'],\n ['submitted', 'label.group-submitted', 'text.group-submitted-hint'],\n ['completed', 'label.group-completed', 'text.group-done-hint']\n ] as const\n ).map(([key, label, hint]) => ({ key, label, hint, count: countsOnTab(key) ? this.counts[key] : undefined }))}\n .selected=${this.group}\n @select=${(e: CustomEvent) => this.selectGroup(e.detail)}\n ></work-box-groups>\n\n <ox-grist class=\"page-fill\"\n .mode=${mode}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <!-- 좁은 화면에서는 상단 바가 검색을 갖는다(위 context 주석 참조). -->\n <ox-filters-form class=\"filter\" without-search></ox-filters-form>\n <ox-input-select-buttons\n .value=${this.filters}\n .options=${this.chips()}\n @change=${(e: CustomEvent) => {\n this.filters = (e.currentTarget as any)?.value\n this.grist.fetch()\n }}\n ></ox-input-select-buttons>\n <!-- <ox-filters-form class=\"filter\" autofocus without-search></ox-filters-form> -->\n </div>\n\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n /** 묶음마다 좁히는 칩이 다르다 — 끝난 일에 「만기」는 물음이 아니다. */\n private chips() {\n switch (this.group) {\n case 'todo':\n return [importantChip(), dueSoonChip(), { display: i18next.t('label.filter-proceeding'), value: 'proceeding' }]\n case 'submitted':\n return [importantChip(), dueSoonChip()]\n case 'completed':\n return [\n importantChip(),\n { display: i18next.t('label.filter-recent'), value: 'recent' },\n { display: i18next.t('label.filter-aborted'), value: 'aborted' }\n ]\n }\n }\n\n /** 주소만 바꾼다 — 목록도 설정도 pageUpdated 가 한 벌로 다시 세운다. */\n private selectGroup(group: TodoGroup) {\n if (this.group === group) {\n return\n }\n\n navigate(groupAddress((this as any).lifecycle?.page, group, TODO_GROUPS))\n }\n\n private syncGroup(key?: string) {\n const group = groupAt(key, TODO_GROUPS)\n\n if (this.group === group && this.gristConfig) {\n return\n }\n\n this.group = group\n this.filters = []\n this.applyConfig()\n }\n\n /** 안 보고 있는 기다리는 묶음의 건수 — 한 줄만 받아 total 을 읽는다. 못 읽은 수는 0 이 아니다. */\n private async countOther() {\n const other = this.group === 'submitted' ? 'todo' : 'submitted'\n const response = await client.query({\n query: gql`\n query ($pagination: Pagination) {\n responses: ${QUERY_OF[other]}(pagination: $pagination) {\n total\n }\n }\n `,\n variables: { pagination: { page: 1, limit: 1 } },\n fetchPolicy: 'no-cache'\n })\n\n const total = response.errors?.length ? undefined : response.data?.responses?.total\n if (typeof total == 'number') this.counts = { ...this.counts, [other]: total }\n }\n\n async pageInitialized(lifecycle) {\n /* pageUpdated 가 이 함수보다 먼저 지나가므로 주소로 바로 들어온 경우는 여기서 잡힌다. */\n this.syncGroup(lifecycle?.resourceId)\n await this.countOther()\n }\n\n private applyConfig() {\n const completed = this.group === 'completed'\n\n this.gristConfig = {\n list: { thumbnail: 'thumbnail', fields: ['name', 'description'], details: ['createdAt', 'due'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.wl-subject'),\n fixed: true,\n record: {\n editable: false,\n renderer: subjectCell()\n },\n filter: 'search',\n sortable: true,\n width: 250\n },\n {\n /*\n * 설명은 제목 칸 안에서 두 줄로 보인다(activity-cells 의 subjectCell). 그래서 따로 보일 칸이\n * 필요하지 않다.\n *\n * 그런데 **칸을 없애면 설명으로 검색하는 기능이 함께 사라진다.** searchText 는\n * filter: 'search' 를 선언한 컬럼마다 필터를 만들고 hidden 은 보지 않는다\n * (data-grist.ts 의 set searchText). 실측으로 확인했다 — 컬럼을 없앤 뒤 설명에만 있는\n * 낱말로 검색하면 0건이었다.\n *\n * 그래서 감춘 칸으로 남겨 검색만 남긴다.\n */\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { editable: false },\n filter: 'search',\n hidden: true\n },\n {\n type: 'resource-object',\n name: 'creator',\n header: i18next.t('field.wl-drafter'),\n record: { editable: false },\n sortable: true,\n width: 85\n },\n /*\n * 기한 — 끝난 일에서는 **다른 날짜 열과 같이** 시각만 적는다.\n *\n * dueCell 은 Date.now() 를 기준으로 「4일 13시간 지남」을 내고 1분마다 다시 센다. 아직 안 끝난\n * 일에는 그게 물음이다 — 지금부터 얼마 남았나. 끝난 일에는 그 물음이 없고, 그대로 두면 **거짓을\n * 말한다.** 기한 안에 끝낸 건도 오늘 날짜가 흐르면 빨간 「N일 지남」으로 바뀐다.\n *\n * 「기한을 지켰나」는 줄마다가 아니라 모아서 보는 물음이다 — activity-stats 의 자리다.\n */\n {\n type: 'datetime',\n name: 'dueAt',\n header: i18next.t('field.wl-due'),\n record: completed ? { editable: false } : { editable: false, renderer: dueCell(() => this.active) },\n sortable: true,\n width: 180\n },\n {\n type: 'number',\n name: 'priority',\n header: i18next.t('field.wl-priority'),\n record: {\n editable: false,\n align: 'center',\n renderer: priorityCell()\n },\n sortable: true,\n width: 70\n },\n {\n type: 'select',\n name: 'state',\n header: i18next.t('field.wl-state'),\n record: {\n editable: false,\n align: 'center',\n options: [\n { display: i18next.t('label.activity-state-unassigned'), value: 'unassigned' },\n { display: i18next.t('label.activity-state-assigned'), value: 'assigned' },\n { display: i18next.t('label.activity-state-started'), value: 'started' },\n { display: i18next.t('label.activity-state-delegated'), value: 'delegated' },\n { display: i18next.t('label.activity-state-submitted'), value: 'submitted' },\n { display: i18next.t('label.activity-state-escalated'), value: 'escalated' },\n { display: i18next.t('label.activity-state-rejected'), value: 'rejected' },\n { display: i18next.t('label.activity-state-ended'), value: 'ended' },\n { display: i18next.t('label.activity-state-aborted'), value: 'aborted' }\n ]\n },\n width: 85\n },\n ...(completed\n ? [\n {\n type: 'datetime',\n name: 'startedAt',\n header: i18next.t('field.wl-started-at'),\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'terminatedAt',\n header: i18next.t('field.wl-terminated-at'),\n record: { editable: false },\n sortable: true,\n width: 180\n }\n ]\n : []),\n {\n type: 'datetime',\n name: 'createdAt',\n header: i18next.t('field.wl-submitted-at'),\n record: { editable: false },\n sortable: true,\n filter: false,\n width: 180\n },\n {\n type: 'string',\n name: 'uiSource',\n header: i18next.t('field.wl-view-source'),\n record: {\n editable: false,\n renderer: function (value, column, record, rowIndex, field) {\n const { uiType, uiSource } = record.activity || {}\n var type = uiType !== 'board' ? 'string' : 'board'\n return getRenderer(type)(uiSource, column, record, rowIndex, field)\n }\n },\n width: 140,\n hidden: true\n },\n {\n type: 'image',\n name: 'thumbnail',\n header: i18next.t('field.thumbnail'),\n record: { editable: false },\n hidden: true\n }\n ],\n rows: {\n appendable: false,\n editable: false,\n handlers: {\n click: (columns, data, column, record, rowIndex) => {\n this.openActivityThread(record)\n }\n }\n },\n sorters: completed ? [{ name: 'terminatedAt', desc: true }] : [{ name: 'dueAt', desc: false }],\n pagination: { limit: 50, pages: [20, 50, 100, 500] }\n }\n }\n\n async pageUpdated(changes, lifecycle, changedBefore) {\n if (lifecycle?.resourceId !== changedBefore?.resourceId) {\n this.syncGroup(lifecycle?.resourceId)\n await this.countOther()\n }\n\n if (this.active) {\n this.grist?.fetch()\n }\n }\n\n /* 읽은 것에 그림이 하나도 없으면 카드마다 빈 그림 칸을 세우지 않는다(§ list-thumbnail.ts). */\n async fetchHandler(options: FetchOption) {\n const fetched = await this.fetchRows(options)\n\n this.gristConfig = listWithThumbnail(this.gristConfig, fetched?.records || [])\n\n return fetched\n }\n\n async fetchRows({ page, limit, sortings = [], filters = [] }: FetchOption) {\n if (this.filters?.includes('important')) {\n filters = adjustFilters(filters, importantFilters())\n }\n\n if (this.filters?.includes('due')) {\n filters = adjustFilters(filters, dueSoonFilters())\n }\n\n if (this.filters?.includes('proceeding')) {\n filters = adjustFilters(filters, [{ name: 'state', operator: 'eq', value: ActivityThreadStatus.Started }])\n }\n\n if (this.filters?.includes('recent')) {\n /* 최근 24시간 이내에 끝낸 일 */\n const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString().replace('T', ' ')\n filters = adjustFilters(filters, [{ name: 'terminatedAt', operator: 'gte', value: since }])\n }\n\n if (this.filters?.includes('aborted')) {\n filters = adjustFilters(filters, [{ name: 'state', operator: 'eq', value: ActivityThreadStatus.Aborted }])\n }\n\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: ${QUERY_OF[this.group]}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n activityInstance {\n activityId: id\n name\n description\n priority\n input\n refBy\n activityType\n uiType\n uiSource\n thumbnail\n updatedAt\n createdAt\n creator {\n id\n name\n }\n }\n state\n output\n dueAt\n assignedAt\n startedAt\n terminatedAt\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings }\n })\n\n const total = response.data.responses.total || 0\n /* 보고 있는 묶음의 수는 이 조회가 알려 준다. 나머지는 countOther 가 센다. */\n if (countsOnTab(this.group)) this.counts = { ...this.counts, [this.group]: total }\n\n return {\n total,\n records: (response.data.responses.items || []).map(item => {\n return {\n ...item.activityInstance,\n id: item.id,\n state: item.state,\n output: item.output,\n dueAt: item.dueAt,\n assignedAt: item.assignedAt,\n startedAt: item.startedAt,\n terminatedAt: item.terminatedAt\n }\n })\n }\n }\n\n /** 끝난 일은 보기 화면으로 연다 — 고칠 것이 없는 화면에 저장 · 제출이 서면 안 된다. */\n openActivityThread(activityThread) {\n const page = this.group === 'completed' ? 'activity-thread-view' : 'activity-thread'\n navigate(`${page}/${activityThread.id}?title=${activityThread.name}`)\n }\n\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three boxes of my work, and the groups each one is split into.
|
|
3
|
+
*
|
|
4
|
+
* 할 일 what I have to do 할 것 · 제출한 것 · 완료
|
|
5
|
+
* 내 결재함 what I have to decide 결재할 것 · 참조로 받은 것 · 완료
|
|
6
|
+
* 상신함 what I gave others to do 작성 중 · 진행 중 · 완료
|
|
7
|
+
*
|
|
8
|
+
* Each box ends in its own history, so a separate 「완료」 screen is not needed: it listed my
|
|
9
|
+
* threads that ended, which mixed work I finished for someone with my own requests that someone
|
|
10
|
+
* else decided. The first group of each box is what waits on me, and it is what the badge counts.
|
|
11
|
+
*
|
|
12
|
+
* Kept free of imports so the test runner can hold it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TODO_GROUPS: readonly ["todo", "submitted", "completed"];
|
|
15
|
+
export declare const APPROVAL_GROUPS: readonly ["approval", "reference", "completed"];
|
|
16
|
+
export declare const DRAFT_GROUPS: readonly ["draft", "progress", "completed"];
|
|
17
|
+
export type TodoGroup = (typeof TODO_GROUPS)[number];
|
|
18
|
+
export type ApprovalGroup = (typeof APPROVAL_GROUPS)[number];
|
|
19
|
+
export type DraftGroup = (typeof DRAFT_GROUPS)[number];
|
|
20
|
+
/**
|
|
21
|
+
* The address names the group: `todo-list/submitted`. What is listed changes, so it is part of the
|
|
22
|
+
* address and can be sent to someone, unlike a filter. No name, or one the box does not have, is
|
|
23
|
+
* the first group.
|
|
24
|
+
*/
|
|
25
|
+
export declare function groupAt<G extends string>(resourceId: string | undefined | null, groups: readonly G[]): G;
|
|
26
|
+
/** The address of a group - the first group is the page itself. */
|
|
27
|
+
export declare function groupAddress<G extends string>(page: string, group: G, groups: readonly G[]): string;
|
|
28
|
+
/** A finished group is history, so it has no count on its tab. */
|
|
29
|
+
export declare function countsOnTab(group: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 상신함 groups by the state of what I put up (ActivityInstanceStatus, written out).
|
|
32
|
+
*
|
|
33
|
+
* Every state is in exactly one group - test/work-box.test.ts holds this to the server's enum, so a
|
|
34
|
+
* new state cannot fall out of the box unseen.
|
|
35
|
+
*/
|
|
36
|
+
export declare const DRAFT_GROUP_STATES: Record<DraftGroup, readonly string[]>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three boxes of my work, and the groups each one is split into.
|
|
3
|
+
*
|
|
4
|
+
* 할 일 what I have to do 할 것 · 제출한 것 · 완료
|
|
5
|
+
* 내 결재함 what I have to decide 결재할 것 · 참조로 받은 것 · 완료
|
|
6
|
+
* 상신함 what I gave others to do 작성 중 · 진행 중 · 완료
|
|
7
|
+
*
|
|
8
|
+
* Each box ends in its own history, so a separate 「완료」 screen is not needed: it listed my
|
|
9
|
+
* threads that ended, which mixed work I finished for someone with my own requests that someone
|
|
10
|
+
* else decided. The first group of each box is what waits on me, and it is what the badge counts.
|
|
11
|
+
*
|
|
12
|
+
* Kept free of imports so the test runner can hold it.
|
|
13
|
+
*/
|
|
14
|
+
export const TODO_GROUPS = ['todo', 'submitted', 'completed'];
|
|
15
|
+
export const APPROVAL_GROUPS = ['approval', 'reference', 'completed'];
|
|
16
|
+
export const DRAFT_GROUPS = ['draft', 'progress', 'completed'];
|
|
17
|
+
/**
|
|
18
|
+
* The address names the group: `todo-list/submitted`. What is listed changes, so it is part of the
|
|
19
|
+
* address and can be sent to someone, unlike a filter. No name, or one the box does not have, is
|
|
20
|
+
* the first group.
|
|
21
|
+
*/
|
|
22
|
+
export function groupAt(resourceId, groups) {
|
|
23
|
+
return groups.includes(String(resourceId)) ? resourceId : groups[0];
|
|
24
|
+
}
|
|
25
|
+
/** The address of a group - the first group is the page itself. */
|
|
26
|
+
export function groupAddress(page, group, groups) {
|
|
27
|
+
return group === groups[0] ? page : `${page}/${group}`;
|
|
28
|
+
}
|
|
29
|
+
/** A finished group is history, so it has no count on its tab. */
|
|
30
|
+
export function countsOnTab(group) {
|
|
31
|
+
return group !== 'completed';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 상신함 groups by the state of what I put up (ActivityInstanceStatus, written out).
|
|
35
|
+
*
|
|
36
|
+
* Every state is in exactly one group - test/work-box.test.ts holds this to the server's enum, so a
|
|
37
|
+
* new state cannot fall out of the box unseen.
|
|
38
|
+
*/
|
|
39
|
+
export const DRAFT_GROUP_STATES = {
|
|
40
|
+
draft: ['draft'],
|
|
41
|
+
progress: ['issued', 'unassigned', 'assigned', 'started', 'pending'],
|
|
42
|
+
completed: ['ended', 'aborted']
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=work-box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-box.js","sourceRoot":"","sources":["../../../client/pages/todo/work-box.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,CAAU,CAAA;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,CAAU,CAAA;AAC9E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,CAAU,CAAA;AAMvE;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAmB,UAAqC,EAAE,MAAoB;IACnG,OAAQ,MAA4B,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAE,UAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;AACnG,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAmB,IAAY,EAAE,KAAQ,EAAE,MAAoB;IACzF,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAA;AACxD,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,KAAK,KAAK,WAAW,CAAA;AAC9B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA0C;IACvE,KAAK,EAAE,CAAC,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC;IACpE,SAAS,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;CAChC,CAAA","sourcesContent":["/**\n * The three boxes of my work, and the groups each one is split into.\n *\n * 할 일 what I have to do 할 것 · 제출한 것 · 완료\n * 내 결재함 what I have to decide 결재할 것 · 참조로 받은 것 · 완료\n * 상신함 what I gave others to do 작성 중 · 진행 중 · 완료\n *\n * Each box ends in its own history, so a separate 「완료」 screen is not needed: it listed my\n * threads that ended, which mixed work I finished for someone with my own requests that someone\n * else decided. The first group of each box is what waits on me, and it is what the badge counts.\n *\n * Kept free of imports so the test runner can hold it.\n */\n\nexport const TODO_GROUPS = ['todo', 'submitted', 'completed'] as const\nexport const APPROVAL_GROUPS = ['approval', 'reference', 'completed'] as const\nexport const DRAFT_GROUPS = ['draft', 'progress', 'completed'] as const\n\nexport type TodoGroup = (typeof TODO_GROUPS)[number]\nexport type ApprovalGroup = (typeof APPROVAL_GROUPS)[number]\nexport type DraftGroup = (typeof DRAFT_GROUPS)[number]\n\n/**\n * The address names the group: `todo-list/submitted`. What is listed changes, so it is part of the\n * address and can be sent to someone, unlike a filter. No name, or one the box does not have, is\n * the first group.\n */\nexport function groupAt<G extends string>(resourceId: string | undefined | null, groups: readonly G[]): G {\n return (groups as readonly string[]).includes(String(resourceId)) ? (resourceId as G) : groups[0]\n}\n\n/** The address of a group - the first group is the page itself. */\nexport function groupAddress<G extends string>(page: string, group: G, groups: readonly G[]): string {\n return group === groups[0] ? page : `${page}/${group}`\n}\n\n/** A finished group is history, so it has no count on its tab. */\nexport function countsOnTab(group: string): boolean {\n return group !== 'completed'\n}\n\n/**\n * 상신함 groups by the state of what I put up (ActivityInstanceStatus, written out).\n *\n * Every state is in exactly one group - test/work-box.test.ts holds this to the server's enum, so a\n * new state cannot fall out of the box unseen.\n */\nexport const DRAFT_GROUP_STATES: Record<DraftGroup, readonly string[]> = {\n draft: ['draft'],\n progress: ['issued', 'unassigned', 'assigned', 'started', 'pending'],\n completed: ['ended', 'aborted']\n}\n"]}
|
|
@@ -17,21 +17,7 @@ export declare class WorkDoorsPage extends WorkDoorsPage_base {
|
|
|
17
17
|
pageInitialized(): Promise<void>;
|
|
18
18
|
pageUpdated(changes: any, lifecycle: any): Promise<void>;
|
|
19
19
|
private load;
|
|
20
|
-
/**
|
|
21
|
-
* 다음에 할 일 — **순서대로.**
|
|
22
|
-
*
|
|
23
|
-
* ── 문제만 말하는 화면이었다 ──────────────────────────────────────────
|
|
24
|
-
* 열두 줄이 「비었습니다」라고 적혀 있고, 어디로 가야 채우는지는 어디에도 없었다. 이 저장소에서
|
|
25
|
-
* 가장 자주 나는 결함을 내 화면이 하고 있었다 — **자리는 있고 길이 없다.**
|
|
26
|
-
*
|
|
27
|
-
* ── 순서를 지키는 이유 ────────────────────────────────────────────────
|
|
28
|
-
* 결재선은 역할을 가리킨다. 역할이 하나도 없는 공장에서 양식 화면으로 가면 **결재선 칸이 빈
|
|
29
|
-
* 목록**이고, 사람은 헛걸음한 채 무엇이 잘못됐는지 모르고 돌아온다. 그래서 자리가 안 섰으면
|
|
30
|
-
* 자리부터 말한다.
|
|
31
|
-
*
|
|
32
|
-
* 여기서 고치게 하지 않는다. 역할도 업무 양식도 **남의 화면이 임자**이고, 같은 일을 하는 자리를
|
|
33
|
-
* 둘 만들면 한쪽이 낡는다. 이 화면이 하는 일은 **가리키는 것**이다.
|
|
34
|
-
*/
|
|
20
|
+
/** 결재선은 각 업무에서 지정하고, 역할이 부족할 때만 별도로 안내한다. */
|
|
35
21
|
private renderNext;
|
|
36
22
|
render(): import("lit-html").TemplateResult<1>;
|
|
37
23
|
private renderDoors;
|
|
@@ -7,7 +7,7 @@ import { client } from '@operato/graphql';
|
|
|
7
7
|
import { i18next, localize } from '@operato/i18n';
|
|
8
8
|
import { PageView } from '@operato/shell';
|
|
9
9
|
import { DocumentPageStyles } from '@operato/styles';
|
|
10
|
-
import { detailAddress,
|
|
10
|
+
import { detailAddress, opensDetail } from './door-links.js';
|
|
11
11
|
import { doorLabel, doorSentence, doorsSummary, whenNoLineTag } from './door-sentence.js';
|
|
12
12
|
import { kindWord, reasonWord, standingWord, whenNoLineBecauseWord, whenNoLineWord } from './door-words.js';
|
|
13
13
|
/**
|
|
@@ -250,54 +250,22 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
250
250
|
this.seatsStanding = response.data?.workDoors?.seatsStanding ?? 0;
|
|
251
251
|
this.seatsOffered = response.data?.workDoors?.seatsOffered ?? 0;
|
|
252
252
|
}
|
|
253
|
-
/**
|
|
254
|
-
* 다음에 할 일 — **순서대로.**
|
|
255
|
-
*
|
|
256
|
-
* ── 문제만 말하는 화면이었다 ──────────────────────────────────────────
|
|
257
|
-
* 열두 줄이 「비었습니다」라고 적혀 있고, 어디로 가야 채우는지는 어디에도 없었다. 이 저장소에서
|
|
258
|
-
* 가장 자주 나는 결함을 내 화면이 하고 있었다 — **자리는 있고 길이 없다.**
|
|
259
|
-
*
|
|
260
|
-
* ── 순서를 지키는 이유 ────────────────────────────────────────────────
|
|
261
|
-
* 결재선은 역할을 가리킨다. 역할이 하나도 없는 공장에서 양식 화면으로 가면 **결재선 칸이 빈
|
|
262
|
-
* 목록**이고, 사람은 헛걸음한 채 무엇이 잘못됐는지 모르고 돌아온다. 그래서 자리가 안 섰으면
|
|
263
|
-
* 자리부터 말한다.
|
|
264
|
-
*
|
|
265
|
-
* 여기서 고치게 하지 않는다. 역할도 업무 양식도 **남의 화면이 임자**이고, 같은 일을 하는 자리를
|
|
266
|
-
* 둘 만들면 한쪽이 낡는다. 이 화면이 하는 일은 **가리키는 것**이다.
|
|
267
|
-
*/
|
|
253
|
+
/** 결재선은 각 업무에서 지정하고, 역할이 부족할 때만 별도로 안내한다. */
|
|
268
254
|
renderNext() {
|
|
269
|
-
if (!this.todo) {
|
|
270
|
-
return '';
|
|
271
|
-
}
|
|
272
|
-
const seatsShort = this.seatsStanding < this.seatsOffered;
|
|
273
|
-
const toSet = doorsToSetHere(this.doors || []);
|
|
274
|
-
/* 남은 것이 전부 남의 양식이거나 배정 양식이면 여기서 가리킬 곳이 없다 — 빈 칸을 세우지 않는다. */
|
|
275
|
-
if (!seatsShort && !toSet.length) {
|
|
255
|
+
if (!this.todo || this.seatsStanding >= this.seatsOffered) {
|
|
276
256
|
return '';
|
|
277
257
|
}
|
|
278
258
|
return html `
|
|
279
259
|
<section class="page-section">
|
|
280
260
|
<div class="page-section-header"><h3>${i18next.t('title.work-doors-next')}</h3></div>
|
|
281
261
|
<ol class="next">
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
<!-- field.roles 는 auth-ui 설치본의 키다. 내가 menu.roles 를 썼다가 고쳤다 —
|
|
290
|
-
그 키는 어디에도 없어서 화면에 키 글자가 그대로 떴을 것이다. -->
|
|
291
|
-
</li>`
|
|
292
|
-
: ''}
|
|
293
|
-
${toSet.length
|
|
294
|
-
? html `<li>
|
|
295
|
-
${i18next.t('text.work-doors-next-line', { count: toSet.length })}
|
|
296
|
-
${toSet.map((door, index) => html `${index ? ' · ' : ''}<a class="page-nav-link" href=${detailAddress(door.name)}
|
|
297
|
-
>${doorLabel(i18next.t, door)}</a
|
|
298
|
-
>`)}
|
|
299
|
-
</li>`
|
|
300
|
-
: ''}
|
|
262
|
+
<li>
|
|
263
|
+
${i18next.t('text.work-doors-next-seats', {
|
|
264
|
+
standing: this.seatsStanding,
|
|
265
|
+
offered: this.seatsOffered
|
|
266
|
+
})}
|
|
267
|
+
<a class="page-nav-link" href="roles">${i18next.t('field.roles')}</a>
|
|
268
|
+
</li>
|
|
301
269
|
</ol>
|
|
302
270
|
</section>
|
|
303
271
|
`;
|
|
@@ -336,7 +304,14 @@ let WorkDoorsPage = class WorkDoorsPage extends localize(i18next)(PageView) {
|
|
|
336
304
|
<span class="mark ${door.standing}">${i18next.t(`text.door-standing-${door.standing}`)}</span>
|
|
337
305
|
</div>
|
|
338
306
|
<div class="says">
|
|
339
|
-
<span
|
|
307
|
+
<span>
|
|
308
|
+
${doorSentence(i18next.t, door)}
|
|
309
|
+
${door.kind === 'approval' && door.standing !== 'missing' && opensDetail(door)
|
|
310
|
+
? html `<a class="page-nav-link" href=${detailAddress(door.name)}
|
|
311
|
+
>${i18next.t('button.activity-set-approval-line')}</a
|
|
312
|
+
>`
|
|
313
|
+
: ''}
|
|
314
|
+
</span>
|
|
340
315
|
${when ? html `<span class="when">${when}</span>` : ''}
|
|
341
316
|
${
|
|
342
317
|
/*
|