@things-factory/learning 10.1.260 → 10.1.262

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 (103) hide show
  1. package/dist-client/components/learning-material-form.d.ts +41 -0
  2. package/dist-client/components/learning-material-form.js +202 -0
  3. package/dist-client/components/learning-material-form.js.map +1 -0
  4. package/dist-client/components/learning-unit-form.d.ts +3 -0
  5. package/dist-client/components/learning-unit-form.js +10 -2
  6. package/dist-client/components/learning-unit-form.js.map +1 -1
  7. package/dist-client/pages/learning-course-editor.d.ts +1 -0
  8. package/dist-client/pages/learning-course-editor.js +19 -6
  9. package/dist-client/pages/learning-course-editor.js.map +1 -1
  10. package/dist-client/pages/learning-course-list.d.ts +1 -5
  11. package/dist-client/pages/learning-course-list.js +54 -16
  12. package/dist-client/pages/learning-course-list.js.map +1 -1
  13. package/dist-client/pages/learning-enrollment-status.d.ts +31 -16
  14. package/dist-client/pages/learning-enrollment-status.js +104 -117
  15. package/dist-client/pages/learning-enrollment-status.js.map +1 -1
  16. package/dist-client/pages/learning-view.d.ts +11 -0
  17. package/dist-client/pages/learning-view.js +94 -19
  18. package/dist-client/pages/learning-view.js.map +1 -1
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/dist-client/utils/learning-api.d.ts +48 -3
  21. package/dist-client/utils/learning-api.js +51 -2
  22. package/dist-client/utils/learning-api.js.map +1 -1
  23. package/dist-client/utils/material-source.d.ts +12 -0
  24. package/dist-client/utils/material-source.js +77 -0
  25. package/dist-client/utils/material-source.js.map +1 -0
  26. package/dist-server/ai-reads.js +1 -1
  27. package/dist-server/ai-reads.js.map +1 -1
  28. package/dist-server/service/course/course-mutation.d.ts +1 -1
  29. package/dist-server/service/course/course-mutation.js +7 -6
  30. package/dist-server/service/course/course-mutation.js.map +1 -1
  31. package/dist-server/service/course/course-query.d.ts +2 -0
  32. package/dist-server/service/course/course-query.js +27 -3
  33. package/dist-server/service/course/course-query.js.map +1 -1
  34. package/dist-server/service/course/course-rules.d.ts +3 -1
  35. package/dist-server/service/course/course-rules.js +5 -1
  36. package/dist-server/service/course/course-rules.js.map +1 -1
  37. package/dist-server/service/course/course-service.d.ts +5 -4
  38. package/dist-server/service/course/course-service.js +10 -14
  39. package/dist-server/service/course/course-service.js.map +1 -1
  40. package/dist-server/service/course/course-type.d.ts +1 -0
  41. package/dist-server/service/course/course-type.js +4 -0
  42. package/dist-server/service/course/course-type.js.map +1 -1
  43. package/dist-server/service/course/course-unit.d.ts +3 -2
  44. package/dist-server/service/course/course-unit.js +7 -2
  45. package/dist-server/service/course/course-unit.js.map +1 -1
  46. package/dist-server/service/course/index.d.ts +2 -1
  47. package/dist-server/service/course/index.js +2 -1
  48. package/dist-server/service/course/index.js.map +1 -1
  49. package/dist-server/service/course/material-audio.d.ts +1 -0
  50. package/dist-server/service/course/material-audio.js +43 -0
  51. package/dist-server/service/course/material-audio.js.map +1 -0
  52. package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
  53. package/dist-server/service/course/material-draft-mutation.js +55 -0
  54. package/dist-server/service/course/material-draft-mutation.js.map +1 -0
  55. package/dist-server/service/course/material-draft-rules.d.ts +38 -0
  56. package/dist-server/service/course/material-draft-rules.js +83 -0
  57. package/dist-server/service/course/material-draft-rules.js.map +1 -0
  58. package/dist-server/service/course/material-draft-service.d.ts +15 -0
  59. package/dist-server/service/course/material-draft-service.js +162 -0
  60. package/dist-server/service/course/material-draft-service.js.map +1 -0
  61. package/dist-server/service/enrollment/enrollment-mutation.d.ts +1 -1
  62. package/dist-server/service/enrollment/enrollment-mutation.js +6 -6
  63. package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -1
  64. package/dist-server/service/enrollment/enrollment-query.d.ts +8 -4
  65. package/dist-server/service/enrollment/enrollment-query.js +47 -36
  66. package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
  67. package/dist-server/service/enrollment/enrollment-service.d.ts +4 -3
  68. package/dist-server/service/enrollment/enrollment-service.js +41 -18
  69. package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
  70. package/dist-server/service/enrollment/enrollment-status.d.ts +42 -18
  71. package/dist-server/service/enrollment/enrollment-status.js +143 -42
  72. package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
  73. package/dist-server/service/enrollment/enrollment-type.d.ts +2 -8
  74. package/dist-server/service/enrollment/enrollment-type.js +20 -42
  75. package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
  76. package/dist-server/service/enrollment/enrollment.d.ts +1 -0
  77. package/dist-server/service/enrollment/enrollment.js +5 -0
  78. package/dist-server/service/enrollment/enrollment.js.map +1 -1
  79. package/dist-server/service/enrollment/index.d.ts +3 -3
  80. package/dist-server/service/enrollment/index.js +3 -3
  81. package/dist-server/service/enrollment/index.js.map +1 -1
  82. package/dist-server/service/enrollment/learning-ai.d.ts +9 -2
  83. package/dist-server/service/enrollment/learning-ai.js +25 -28
  84. package/dist-server/service/enrollment/learning-ai.js.map +1 -1
  85. package/dist-server/service/enrollment/learning-documents.js +3 -4
  86. package/dist-server/service/enrollment/learning-documents.js.map +1 -1
  87. package/dist-server/service/enrollment/progress-rules.d.ts +40 -11
  88. package/dist-server/service/enrollment/progress-rules.js +54 -13
  89. package/dist-server/service/enrollment/progress-rules.js.map +1 -1
  90. package/dist-server/service/enrollment/unit-progress.d.ts +4 -0
  91. package/dist-server/service/enrollment/unit-progress.js +20 -0
  92. package/dist-server/service/enrollment/unit-progress.js.map +1 -1
  93. package/dist-server/service/index.d.ts +2 -2
  94. package/dist-server/service/index.js +2 -2
  95. package/dist-server/service/index.js.map +1 -1
  96. package/dist-server/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +14 -13
  98. package/translations/en.json +42 -9
  99. package/translations/es.json +42 -9
  100. package/translations/ja.json +42 -9
  101. package/translations/ko.json +42 -9
  102. package/translations/ms.json +42 -9
  103. package/translations/zh.json +42 -9
@@ -1,12 +1,11 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
+ import '@operato/data-grist/ox-filters-form.js';
2
3
  import '@operato/data-grist/ox-grist.js';
3
- import '@operato/input/ox-select.js';
4
- import '@operato/popup/ox-popup-list.js';
5
4
  import gql from 'graphql-tag';
6
- import { css, html, nothing } from 'lit';
5
+ import { html } from 'lit';
7
6
  import { customElement, query, state } from 'lit/decorators.js';
8
7
  import { DataGrist } from '@operato/data-grist';
9
- import { MutationController, QueryController } from '@operato/graphql';
8
+ import { client, MutationController, QUERY_ANSWERED, QueryController } from '@operato/graphql';
10
9
  import { i18next, localize } from '@operato/i18n';
11
10
  import { notify } from '@operato/layout';
12
11
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
@@ -17,63 +16,38 @@ import { hasPrivilege } from '@things-factory/auth-base/dist-client';
17
16
  import { issueDocument } from '@things-factory/pdf/dist-client';
18
17
  import { STATUS_ROW_FIELDS } from '../utils/learning-api.js';
19
18
  import { dayOf, minutesOf, STATES, stateWord, t } from '../utils/learning-words.js';
19
+ /** The states the list opens on - a cancelled enrolment is shown only when its chip is pressed. */
20
+ export const SHOWN_STATES = STATES.filter(state => state !== 'CANCELLED');
21
+ const TEAM_ROW_FIELDS = 'id state departmentName units unitsDone user { id name } course { id name } enrollment { id status dueOn completedAt }';
20
22
  /**
21
- * 수강 현황 (ADR-0194 decisions 10, 11) - where every enrolment of a course stands: who, their department, the state
22
- * today, units done, minutes, score and the completion day, with counts on top. A manager reminds and cancels from here
23
- * and issues the completion register (PDF, through the issued document ledger) or exports it (Excel). A department head
24
- * sees the people of their departments - the state and days only.
23
+ * 수강 현황 (ADR-0194 decisions 10, 11) - where enrolments stand: who, their department, the state today, units done,
24
+ * minutes, score and the completion day. The conditions - course, state with its counts, search, sort, page - are the
25
+ * grid's, kept in the address, and read by the server. A manager reminds and cancels from here and issues the completion
26
+ * register of the course filtered (PDF, through the issued document ledger) or exports the list (Excel). A department
27
+ * head sees the people of their departments - the state and days only.
25
28
  */
26
29
  let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i18next)(PageView) {
27
30
  constructor() {
28
31
  super(...arguments);
29
- this.courseId = '';
30
- this.stateFilter = '';
32
+ /* The courses the course filter offers - a manager's only. */
31
33
  this.courses = new QueryController(this, gql `query { courses(pagination: { page: 1, limit: 500 }) { items { id name } } }`, {
32
34
  variables: () => (this.manager ? {} : undefined)
33
35
  });
34
- this.rows = new QueryController(this, gql `query ($filter: EnrollmentStatusFilter) { enrollmentStatus(filter: $filter) { items { ${STATUS_ROW_FIELDS} } summary { total assigned inProgress completed overdue } } }`, { variables: () => (this.manager === true ? { filter: this.filter } : undefined), keepPrevious: true });
35
- this.teamRows = new QueryController(this, gql `query ($filter: EnrollmentStatusFilter) { teamEnrollmentStatus(filter: $filter) { items { id state departmentName units unitsDone user { id name } course { id name } enrollment { id status dueOn completedAt } } summary { total assigned inProgress completed overdue } } }`, { variables: () => (this.manager === false ? { filter: this.filter } : undefined), keepPrevious: true });
36
36
  this.reminder = new MutationController(this, gql `mutation ($ids: [String!]!) { remindEnrollments(ids: $ids) }`);
37
- this.canceller = new MutationController(this, gql `mutation ($id: String!, $reason: String!) { cancelEnrollment(id: $id, reason: $reason) { id } }`, { refetch: () => [this.rows] });
37
+ this.canceller = new MutationController(this, gql `mutation ($ids: [String!]!, $reason: String!) { cancelEnrollments(ids: $ids, reason: $reason) { id } }`, { showsError: true });
38
38
  }
39
- static { this.styles = [
40
- WorkSurfacePageStyles,
41
- CommonGristStyles,
42
- CommonHeaderStyles,
43
- ScrollbarStyles,
44
- css `
45
- .filters {
46
- display: flex;
47
- flex-wrap: wrap;
48
- align-items: center;
49
- gap: var(--spacing-medium, 8px);
50
- }
51
- ox-select {
52
- min-width: 160px;
53
- }
54
- .counts {
55
- display: flex;
56
- flex-wrap: wrap;
57
- gap: var(--spacing-small, 4px) var(--spacing-large, 12px);
58
- color: var(--ui-muted);
59
- font-size: var(--ui-t-small, 12px);
60
- }
61
- .counts strong {
62
- color: var(--ui-ink);
63
- font-variant-numeric: tabular-nums;
64
- }
65
- `
66
- ]; }
67
- get filter() {
68
- return { ...(this.courseId ? { courseId: this.courseId } : {}), ...(this.stateFilter ? { state: this.stateFilter } : {}) };
69
- }
70
- get list() {
71
- return (this.manager ? this.rows.data?.enrollmentStatus : this.teamRows.data?.teamEnrollmentStatus) ?? { items: [], summary: null };
39
+ static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
40
+ /** The course the grid's `course` filter names - the completion register is of it. */
41
+ get filteredCourse() {
42
+ const id = this.grist?.filters?.find((one) => one.name === 'course')?.value;
43
+ return id ? (this.courses.data?.courses?.items ?? []).find((one) => one.id === id) : undefined;
72
44
  }
73
45
  get context() {
74
- const course = this.courseList.find(one => one.id === this.courseId);
46
+ const course = this.filteredCourse;
75
47
  return {
76
48
  title: t('title.learning-enrollment-status'),
49
+ search: { handler: (search) => (this.grist.searchText = search), value: this.grist?.searchText || '' },
50
+ filter: { handler: () => this.grist.toggleHeadroom() },
77
51
  ...(this.manager
78
52
  ? {
79
53
  actions: [
@@ -86,23 +60,34 @@ let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i
86
60
  : {})
87
61
  };
88
62
  }
89
- get courseList() {
90
- return this.courses.data?.courses?.items ?? [];
91
- }
92
- async pageUpdated(changes, lifecycle) {
93
- if (!this.active)
94
- return;
95
- if (changes.params || changes.active) {
96
- const course = lifecycle?.params?.course;
97
- if (course && course !== this.courseId)
98
- this.courseId = course;
99
- }
63
+ render() {
64
+ return this.gristConfig
65
+ ? html `<ox-grist
66
+ class="page-fill"
67
+ search-mode="term"
68
+ .mode=${isMobileDevice() ? 'CARD' : 'GRID'}
69
+ .config=${this.gristConfig}
70
+ .fetchHandler=${this.fetchHandler.bind(this)}
71
+ .countHandler=${this.countHandler.bind(this)}
72
+ ?url-params-sensitive=${this.active}
73
+ >
74
+ <div slot="headroom" class="header">
75
+ <div class="filters"><ox-filters-form without-search></ox-filters-form></div>
76
+ </div>
77
+ </ox-grist>`
78
+ : html ``;
100
79
  }
101
80
  async pageInitialized() {
102
81
  this.manager = !!(await hasPrivilege({ category: 'course', privilege: 'query', domainOwnerGranted: true }));
103
82
  this.gristConfig = this.configOf(this.manager);
104
83
  this.updateContext();
105
84
  }
85
+ /** The courses for the course filter, once the read of them has answered. */
86
+ async courseOptions() {
87
+ if (this.courses.loading)
88
+ await new Promise(resolve => this.addEventListener(QUERY_ANSWERED, resolve, { once: true }));
89
+ return [{ value: '', display: t('text.learning-course-all') }, ...(this.courses.data?.courses?.items ?? []).map((one) => ({ value: one.id, display: one.name }))];
90
+ }
106
91
  recordOf(row) {
107
92
  const e = row.enrollment ?? {};
108
93
  return {
@@ -121,63 +106,67 @@ let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i
121
106
  };
122
107
  }
123
108
  configOf(manager) {
124
- const column = (name, header, width, extra = {}) => ({ type: 'string', name, header, width, record: { editable: false, ...extra } });
109
+ const column = (name, header, width, extra = {}) => ({ type: 'string', name, header, width, sortable: false, ...extra, record: { editable: false, ...extra.record } });
125
110
  return {
126
111
  rows: { appendable: false, selectable: manager ? { multiple: true } : false },
127
- pagination: { infinite: true },
112
+ pagination: { limit: 100, pages: [50, 100, 500] },
128
113
  emptyNote: { title: t('text.learning-no-enrollment') },
129
114
  list: { fields: ['name', 'state'], details: ['course', 'dueOn'] },
130
115
  columns: [
131
116
  ...(manager ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),
132
117
  column('name', t('field.learning-learner'), 120),
133
118
  column('department', t('field.learning-department'), 120),
134
- column('course', t('field.learning-course-name'), 220),
135
- column('state', t('field.learning-state'), 100, { renderer: (value) => stateWord(value) }),
136
- column('dueOn', t('field.learning-due-on'), 110),
137
- column('units', t('field.learning-units-done'), 90, { align: 'right' }),
138
- ...(manager ? [column('minutes', t('field.learning-minutes'), 120, { align: 'right' }), column('score', t('field.learning-score'), 70, { align: 'right' })] : []),
119
+ column('course', t('field.learning-course-name'), 220, manager ? { filter: { type: 'select', operator: 'eq', options: () => this.courseOptions() } } : {}),
120
+ column('state', t('field.learning-state'), 100, {
121
+ record: { renderer: (value) => stateWord(value) },
122
+ /* Chips with their counts; the list opens on every state but cancelled, and none pressed is every state. */
123
+ filter: { type: 'select-buttons', operator: 'in', value: SHOWN_STATES, options: STATES.map(value => ({ value, display: stateWord(value) })) }
124
+ }),
125
+ column('dueOn', t('field.learning-due-on'), 110, { sortable: true }),
126
+ column('units', t('field.learning-units-done'), 90, { record: { align: 'right' } }),
127
+ ...(manager ? [column('minutes', t('field.learning-minutes'), 120, { record: { align: 'right' } }), column('score', t('field.learning-score'), 70, { record: { align: 'right' } })] : []),
139
128
  column('completedOn', t('field.learning-completed-on'), 110),
140
129
  ...(manager ? [column('certificateNo', t('field.learning-certificate-no'), 180)] : [])
141
- ]
130
+ ],
131
+ sorters: [{ name: 'dueOn' }]
142
132
  };
143
133
  }
144
- render() {
145
- const summary = this.list.summary;
146
- const states = [{ value: '', display: t('text.learning-state-all') }, ...STATES.map(value => ({ value, display: stateWord(value) }))];
147
- const courses = [{ id: '', name: t('text.learning-course-all') }, ...this.courseList];
148
- const records = this.list.items.map((row) => this.recordOf(row));
149
- return html `<ox-grist class="page-fill" .mode=${isMobileDevice() ? 'CARD' : 'GRID'} .config=${this.gristConfig} .data=${{ records, total: records.length }}>
150
- <div slot="headroom" class="header">
151
- <div class="filters">
152
- ${this.manager
153
- ? html `<ox-select .value=${this.courseId} aria-label=${t('field.learning-course-name')} @change=${(e) => this.pick('course', String(e.detail ?? ''))}>
154
- <span slot="label">${courses.find(one => one.id === this.courseId)?.name ?? ''}</span>
155
- <ox-popup-list align-left nowrap with-search>${courses.map(one => html `<div option value=${one.id}>${one.name}</div>`)}</ox-popup-list>
156
- </ox-select>`
157
- : nothing}
158
- <ox-select .value=${this.stateFilter} aria-label=${t('field.learning-state')} @change=${(e) => this.pick('state', String(e.detail ?? ''))}>
159
- <span slot="label">${states.find(one => one.value === this.stateFilter)?.display}</span>
160
- <ox-popup-list align-left nowrap>${states.map(one => html `<div option value=${one.value}>${one.display}</div>`)}</ox-popup-list>
161
- </ox-select>
162
- ${summary
163
- ? html `<div class="counts" role="status">
164
- <span>${t('text.learning-count-total')} <strong>${summary.total}</strong></span>
165
- <span>${t('text.learning-count-assigned')} <strong>${summary.assigned}</strong></span>
166
- <span>${t('text.learning-count-in-progress')} <strong>${summary.inProgress}</strong></span>
167
- <span>${t('text.learning-count-completed')} <strong>${summary.completed}</strong></span>
168
- <span>${t('text.learning-count-overdue')} <strong>${summary.overdue}</strong></span>
169
- </div>`
170
- : nothing}
171
- </div>
172
- </div>
173
- </ox-grist>`;
174
- }
175
- pick(which, value) {
176
- if (which === 'course' && value !== this.courseId)
177
- this.courseId = value;
178
- if (which === 'state' && value !== this.stateFilter)
179
- this.stateFilter = value;
134
+ async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [], search, signal }) {
135
+ const door = this.manager ? 'enrollmentStatus' : 'teamEnrollmentStatus';
136
+ const response = await client.query({
137
+ query: gql `
138
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {
139
+ responses: ${door}(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {
140
+ items { ${this.manager ? STATUS_ROW_FIELDS : TEAM_ROW_FIELDS} }
141
+ total
142
+ }
143
+ }
144
+ `,
145
+ variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },
146
+ fetchPolicy: 'no-cache',
147
+ context: { fetchOptions: { signal }, queryDeduplication: false }
148
+ });
149
+ if (response.errors?.length)
150
+ throw response.errors[0];
151
+ /* The register action names the course filtered - said again with each read. */
180
152
  this.updateContext();
153
+ return { total: response.data.responses.total, records: response.data.responses.items.map((row) => this.recordOf(row)) };
154
+ }
155
+ /* The state chips' numbers, under the list's other conditions - the chips' own left out, counted by the server. */
156
+ async countHandler({ filters = [], search, signal }) {
157
+ const response = await client.query({
158
+ query: gql `
159
+ query ($filters: [Filter!], $search: String) {
160
+ enrollmentStateCounts(filters: $filters, search: $search) { value count }
161
+ }
162
+ `,
163
+ variables: { filters: filters.filter((one) => one.name !== 'state'), search: search?.trim() || undefined },
164
+ fetchPolicy: 'no-cache',
165
+ context: { fetchOptions: { signal }, queryDeduplication: false }
166
+ });
167
+ if (response.errors?.length)
168
+ throw response.errors[0];
169
+ return { state: Object.fromEntries(response.data.enrollmentStateCounts.map((row) => [row.value, row.count])) };
181
170
  }
182
171
  selectedOpen() {
183
172
  return (this.grist?.selected ?? []).filter((one) => one.open);
@@ -190,25 +179,21 @@ let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i
190
179
  if (done)
191
180
  notify({ message: t('text.learning-reminded', { count: done.remindEnrollments }) });
192
181
  }
182
+ /** Cancels the enrolments picked - all but the cancelled ones - with one reason, together. */
193
183
  async cancel() {
194
- const [one] = this.grist?.selected ?? [];
195
- if (!one)
196
- return notify({ level: 'warn', message: t('text.learning-pick-one') });
197
- const reason = await OxPrompt.prompt({ title: t('button.learning-cancel-enrollment'), text: t('field.learning-cancel-reason'), multiline: true });
184
+ const ids = (this.grist?.selected ?? []).filter((one) => one.state !== 'CANCELLED').map((one) => one.id);
185
+ if (!ids.length)
186
+ return notify({ level: 'warn', message: t('text.learning-pick-cancellable') });
187
+ const reason = await OxPrompt.prompt({ title: t('title.learning-cancel-enrollments', { count: ids.length }), text: t('field.learning-cancel-reason'), multiline: true });
198
188
  if (reason === null || !String(reason).trim())
199
189
  return;
200
- if (await this.canceller.run({ id: one.id, reason: String(reason).trim() }))
201
- notify({ message: t('text.learning-cancelled') });
190
+ const done = await this.canceller.run({ ids, reason: String(reason).trim() });
191
+ if (done) {
192
+ notify({ message: t('text.learning-cancelled', { count: done.cancelEnrollments.length }) });
193
+ this.grist.fetch();
194
+ }
202
195
  }
203
196
  };
204
- __decorate([
205
- state(),
206
- __metadata("design:type", Object)
207
- ], LearningEnrollmentStatus.prototype, "courseId", void 0);
208
- __decorate([
209
- state(),
210
- __metadata("design:type", Object)
211
- ], LearningEnrollmentStatus.prototype, "stateFilter", void 0);
212
197
  __decorate([
213
198
  state(),
214
199
  __metadata("design:type", Boolean)
@@ -219,7 +204,9 @@ __decorate([
219
204
  ], LearningEnrollmentStatus.prototype, "gristConfig", void 0);
220
205
  __decorate([
221
206
  query('ox-grist'),
222
- __metadata("design:type", DataGrist)
207
+ __metadata("design:type", DataGrist
208
+ /* The courses the course filter offers - a manager's only. */
209
+ )
223
210
  ], LearningEnrollmentStatus.prototype, "grist", void 0);
224
211
  LearningEnrollmentStatus = __decorate([
225
212
  customElement('learning-enrollment-status')
@@ -1 +1 @@
1
- {"version":3,"file":"learning-enrollment-status.js","sourceRoot":"","sources":["../../client/pages/learning-enrollment-status.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/input/ox-select.js'\nimport '@operato/popup/ox-popup-list.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist } from '@operato/data-grist'\nimport { MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\nimport { issueDocument } from '@things-factory/pdf/dist-client'\n\nimport { STATUS_ROW_FIELDS } from '../utils/learning-api.js'\nimport { dayOf, minutesOf, STATES, stateWord, t } from '../utils/learning-words.js'\n\n/**\n * 수강 현황 (ADR-0194 decisions 10, 11) - where every enrolment of a course stands: who, their department, the state\n * today, units done, minutes, score and the completion day, with counts on top. A manager reminds and cancels from here\n * and issues the completion register (PDF, through the issued document ledger) or exports it (Excel). A department head\n * sees the people of their departments - the state and days only.\n */\n@customElement('learning-enrollment-status')\nexport class LearningEnrollmentStatus extends localize(i18next)(PageView) {\n static styles = [\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n .filters {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n ox-select {\n min-width: 160px;\n }\n .counts {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-large, 12px);\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 12px);\n }\n .counts strong {\n color: var(--ui-ink);\n font-variant-numeric: tabular-nums;\n }\n `\n ]\n\n @state() private courseId = ''\n @state() private stateFilter = ''\n /* A manager reads everyone; anyone else reads their departments - known once asked. */\n @state() private manager?: boolean\n @state() private gristConfig: any\n @query('ox-grist') private grist!: DataGrist\n\n private courses = new QueryController(this, gql`query { courses(pagination: { page: 1, limit: 500 }) { items { id name } } }`, {\n variables: () => (this.manager ? {} : undefined)\n })\n private rows = new QueryController(\n this,\n gql`query ($filter: EnrollmentStatusFilter) { enrollmentStatus(filter: $filter) { items { ${STATUS_ROW_FIELDS} } summary { total assigned inProgress completed overdue } } }`,\n { variables: () => (this.manager === true ? { filter: this.filter } : undefined), keepPrevious: true }\n )\n private teamRows = new QueryController(\n this,\n gql`query ($filter: EnrollmentStatusFilter) { teamEnrollmentStatus(filter: $filter) { items { id state departmentName units unitsDone user { id name } course { id name } enrollment { id status dueOn completedAt } } summary { total assigned inProgress completed overdue } } }`,\n { variables: () => (this.manager === false ? { filter: this.filter } : undefined), keepPrevious: true }\n )\n private reminder = new MutationController(this, gql`mutation ($ids: [String!]!) { remindEnrollments(ids: $ids) }`)\n private canceller = new MutationController(this, gql`mutation ($id: String!, $reason: String!) { cancelEnrollment(id: $id, reason: $reason) { id } }`, { refetch: () => [this.rows] })\n\n private get filter() {\n return { ...(this.courseId ? { courseId: this.courseId } : {}), ...(this.stateFilter ? { state: this.stateFilter } : {}) }\n }\n\n private get list(): any {\n return (this.manager ? this.rows.data?.enrollmentStatus : this.teamRows.data?.teamEnrollmentStatus) ?? { items: [], summary: null }\n }\n\n get context() {\n const course = this.courseList.find(one => one.id === this.courseId)\n return {\n title: t('title.learning-enrollment-status'),\n ...(this.manager\n ? {\n actions: [\n { title: t('button.learning-remind'), icon: 'notifications_active', action: () => this.remind() },\n { title: t('button.learning-cancel-enrollment'), icon: 'block', action: () => this.cancel() },\n ...(course ? [{ title: t('button.learning-register-pdf'), icon: 'picture_as_pdf', action: () => issueDocument({ kind: 'learning.completion-register', refId: course.id, withAsOf: true }) }] : [])\n ],\n exportable: { name: course ? t('text.learning-register-name', { course: course.name }) : t('title.learning-enrollment-status'), data: () => this.grist.exportRecords() }\n }\n : {})\n }\n }\n\n private get courseList(): { id: string; name: string }[] {\n return this.courses.data?.courses?.items ?? []\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (!this.active) return\n if (changes.params || changes.active) {\n const course = lifecycle?.params?.course\n if (course && course !== this.courseId) this.courseId = course\n }\n }\n\n async pageInitialized() {\n this.manager = !!(await hasPrivilege({ category: 'course', privilege: 'query', domainOwnerGranted: true }))\n this.gristConfig = this.configOf(this.manager)\n this.updateContext()\n }\n\n private recordOf(row: any) {\n const e = row.enrollment ?? {}\n return {\n id: row.id,\n name: row.user?.name ?? '',\n department: row.departmentName ?? '',\n course: row.course?.name ?? '',\n state: row.state,\n dueOn: e.dueOn ?? '',\n units: `${row.unitsDone}/${row.units}`,\n minutes: e.learnedSeconds != null ? minutesOf(e.learnedSeconds) : null,\n score: e.score ?? null,\n completedOn: dayOf(e.completedAt),\n certificateNo: e.certificateNo ?? '',\n open: e.status === 'ASSIGNED' || e.status === 'IN_PROGRESS'\n }\n }\n\n private configOf(manager: boolean) {\n const column = (name: string, header: string, width: any, extra: any = {}) => ({ type: 'string', name, header, width, record: { editable: false, ...extra } })\n return {\n rows: { appendable: false, selectable: manager ? { multiple: true } : false },\n pagination: { infinite: true },\n emptyNote: { title: t('text.learning-no-enrollment') },\n list: { fields: ['name', 'state'], details: ['course', 'dueOn'] },\n columns: [\n ...(manager ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),\n column('name', t('field.learning-learner'), 120),\n column('department', t('field.learning-department'), 120),\n column('course', t('field.learning-course-name'), 220),\n column('state', t('field.learning-state'), 100, { renderer: (value: string) => stateWord(value) }),\n column('dueOn', t('field.learning-due-on'), 110),\n column('units', t('field.learning-units-done'), 90, { align: 'right' }),\n ...(manager ? [column('minutes', t('field.learning-minutes'), 120, { align: 'right' }), column('score', t('field.learning-score'), 70, { align: 'right' })] : []),\n column('completedOn', t('field.learning-completed-on'), 110),\n ...(manager ? [column('certificateNo', t('field.learning-certificate-no'), 180)] : [])\n ]\n }\n }\n\n render() {\n const summary = this.list.summary\n const states = [{ value: '', display: t('text.learning-state-all') }, ...STATES.map(value => ({ value, display: stateWord(value) }))]\n const courses = [{ id: '', name: t('text.learning-course-all') }, ...this.courseList]\n const records = this.list.items.map((row: any) => this.recordOf(row))\n return html`<ox-grist class=\"page-fill\" .mode=${isMobileDevice() ? 'CARD' : 'GRID'} .config=${this.gristConfig} .data=${{ records, total: records.length }}>\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n ${this.manager\n ? html`<ox-select .value=${this.courseId} aria-label=${t('field.learning-course-name')} @change=${(e: CustomEvent) => this.pick('course', String(e.detail ?? ''))}>\n <span slot=\"label\">${courses.find(one => one.id === this.courseId)?.name ?? ''}</span>\n <ox-popup-list align-left nowrap with-search>${courses.map(one => html`<div option value=${one.id}>${one.name}</div>`)}</ox-popup-list>\n </ox-select>`\n : nothing}\n <ox-select .value=${this.stateFilter} aria-label=${t('field.learning-state')} @change=${(e: CustomEvent) => this.pick('state', String(e.detail ?? ''))}>\n <span slot=\"label\">${states.find(one => one.value === this.stateFilter)?.display}</span>\n <ox-popup-list align-left nowrap>${states.map(one => html`<div option value=${one.value}>${one.display}</div>`)}</ox-popup-list>\n </ox-select>\n ${summary\n ? html`<div class=\"counts\" role=\"status\">\n <span>${t('text.learning-count-total')} <strong>${summary.total}</strong></span>\n <span>${t('text.learning-count-assigned')} <strong>${summary.assigned}</strong></span>\n <span>${t('text.learning-count-in-progress')} <strong>${summary.inProgress}</strong></span>\n <span>${t('text.learning-count-completed')} <strong>${summary.completed}</strong></span>\n <span>${t('text.learning-count-overdue')} <strong>${summary.overdue}</strong></span>\n </div>`\n : nothing}\n </div>\n </div>\n </ox-grist>`\n }\n\n private pick(which: 'course' | 'state', value: string) {\n if (which === 'course' && value !== this.courseId) this.courseId = value\n if (which === 'state' && value !== this.stateFilter) this.stateFilter = value\n this.updateContext()\n }\n\n private selectedOpen(): any[] {\n return (this.grist?.selected ?? []).filter((one: any) => one.open)\n }\n\n private async remind() {\n const ids = this.selectedOpen().map(one => one.id)\n if (!ids.length) return notify({ level: 'warn', message: t('text.learning-pick-open') })\n const done = await this.reminder.run({ ids })\n if (done) notify({ message: t('text.learning-reminded', { count: done.remindEnrollments }) })\n }\n\n private async cancel() {\n const [one] = this.grist?.selected ?? []\n if (!one) return notify({ level: 'warn', message: t('text.learning-pick-one') })\n const reason = await OxPrompt.prompt({ title: t('button.learning-cancel-enrollment'), text: t('field.learning-cancel-reason'), multiline: true })\n if (reason === null || !String(reason).trim()) return\n if (await this.canceller.run({ id: one.id, reason: String(reason).trim() })) notify({ message: t('text.learning-cancelled') })\n }\n}\n\n"]}
1
+ {"version":3,"file":"learning-enrollment-status.js","sourceRoot":"","sources":["../../client/pages/learning-enrollment-status.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-filters-form.js'\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, MutationController, QUERY_ANSWERED, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\nimport { issueDocument } from '@things-factory/pdf/dist-client'\n\nimport { STATUS_ROW_FIELDS } from '../utils/learning-api.js'\nimport { dayOf, minutesOf, STATES, stateWord, t } from '../utils/learning-words.js'\n\n/** The states the list opens on - a cancelled enrolment is shown only when its chip is pressed. */\nexport const SHOWN_STATES = STATES.filter(state => state !== 'CANCELLED')\n\nconst TEAM_ROW_FIELDS = 'id state departmentName units unitsDone user { id name } course { id name } enrollment { id status dueOn completedAt }'\n\n/**\n * 수강 현황 (ADR-0194 decisions 10, 11) - where enrolments stand: who, their department, the state today, units done,\n * minutes, score and the completion day. The conditions - course, state with its counts, search, sort, page - are the\n * grid's, kept in the address, and read by the server. A manager reminds and cancels from here and issues the completion\n * register of the course filtered (PDF, through the issued document ledger) or exports the list (Excel). A department\n * head sees the people of their departments - the state and days only.\n */\n@customElement('learning-enrollment-status')\nexport class LearningEnrollmentStatus extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]\n\n /* A manager reads everyone; anyone else reads their departments - known once asked. */\n @state() private manager?: boolean\n @state() private gristConfig: any\n @query('ox-grist') private grist!: DataGrist\n\n /* The courses the course filter offers - a manager's only. */\n private courses = new QueryController(this, gql`query { courses(pagination: { page: 1, limit: 500 }) { items { id name } } }`, {\n variables: () => (this.manager ? {} : undefined)\n })\n private reminder = new MutationController(this, gql`mutation ($ids: [String!]!) { remindEnrollments(ids: $ids) }`)\n private canceller = new MutationController(this, gql`mutation ($ids: [String!]!, $reason: String!) { cancelEnrollments(ids: $ids, reason: $reason) { id } }`, { showsError: true })\n\n /** The course the grid's `course` filter names - the completion register is of it. */\n private get filteredCourse(): { id: string; name: string } | undefined {\n const id = this.grist?.filters?.find((one: any) => one.name === 'course')?.value\n return id ? (this.courses.data?.courses?.items ?? []).find((one: any) => one.id === id) : undefined\n }\n\n get context() {\n const course = this.filteredCourse\n return {\n title: t('title.learning-enrollment-status'),\n search: { handler: (search: string) => (this.grist.searchText = search), value: this.grist?.searchText || '' },\n filter: { handler: () => this.grist.toggleHeadroom() },\n ...(this.manager\n ? {\n actions: [\n { title: t('button.learning-remind'), icon: 'notifications_active', action: () => this.remind() },\n { title: t('button.learning-cancel-enrollment'), icon: 'block', action: () => this.cancel() },\n ...(course ? [{ title: t('button.learning-register-pdf'), icon: 'picture_as_pdf', action: () => issueDocument({ kind: 'learning.completion-register', refId: course.id, withAsOf: true }) }] : [])\n ],\n exportable: { name: course ? t('text.learning-register-name', { course: course.name }) : t('title.learning-enrollment-status'), data: () => this.grist.exportRecords() }\n }\n : {})\n }\n }\n\n render() {\n return this.gristConfig\n ? html`<ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${isMobileDevice() ? 'CARD' : 'GRID'}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .countHandler=${this.countHandler.bind(this)}\n ?url-params-sensitive=${this.active}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\"><ox-filters-form without-search></ox-filters-form></div>\n </div>\n </ox-grist>`\n : html``\n }\n\n async pageInitialized() {\n this.manager = !!(await hasPrivilege({ category: 'course', privilege: 'query', domainOwnerGranted: true }))\n this.gristConfig = this.configOf(this.manager)\n this.updateContext()\n }\n\n /** The courses for the course filter, once the read of them has answered. */\n private async courseOptions(): Promise<{ value: string; display: string }[]> {\n if (this.courses.loading) await new Promise(resolve => this.addEventListener(QUERY_ANSWERED, resolve, { once: true }))\n return [{ value: '', display: t('text.learning-course-all') }, ...(this.courses.data?.courses?.items ?? []).map((one: any) => ({ value: one.id, display: one.name }))]\n }\n\n private recordOf(row: any) {\n const e = row.enrollment ?? {}\n return {\n id: row.id,\n name: row.user?.name ?? '',\n department: row.departmentName ?? '',\n course: row.course?.name ?? '',\n state: row.state,\n dueOn: e.dueOn ?? '',\n units: `${row.unitsDone}/${row.units}`,\n minutes: e.learnedSeconds != null ? minutesOf(e.learnedSeconds) : null,\n score: e.score ?? null,\n completedOn: dayOf(e.completedAt),\n certificateNo: e.certificateNo ?? '',\n open: e.status === 'ASSIGNED' || e.status === 'IN_PROGRESS'\n }\n }\n\n private configOf(manager: boolean) {\n const column = (name: string, header: string, width: any, extra: any = {}) => ({ type: 'string', name, header, width, sortable: false, ...extra, record: { editable: false, ...extra.record } })\n return {\n rows: { appendable: false, selectable: manager ? { multiple: true } : false },\n pagination: { limit: 100, pages: [50, 100, 500] },\n emptyNote: { title: t('text.learning-no-enrollment') },\n list: { fields: ['name', 'state'], details: ['course', 'dueOn'] },\n columns: [\n ...(manager ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),\n column('name', t('field.learning-learner'), 120),\n column('department', t('field.learning-department'), 120),\n column('course', t('field.learning-course-name'), 220, manager ? { filter: { type: 'select', operator: 'eq', options: () => this.courseOptions() } } : {}),\n column('state', t('field.learning-state'), 100, {\n record: { renderer: (value: string) => stateWord(value) },\n /* Chips with their counts; the list opens on every state but cancelled, and none pressed is every state. */\n filter: { type: 'select-buttons', operator: 'in', value: SHOWN_STATES, options: STATES.map(value => ({ value, display: stateWord(value) })) }\n }),\n column('dueOn', t('field.learning-due-on'), 110, { sortable: true }),\n column('units', t('field.learning-units-done'), 90, { record: { align: 'right' } }),\n ...(manager ? [column('minutes', t('field.learning-minutes'), 120, { record: { align: 'right' } }), column('score', t('field.learning-score'), 70, { record: { align: 'right' } })] : []),\n column('completedOn', t('field.learning-completed-on'), 110),\n ...(manager ? [column('certificateNo', t('field.learning-certificate-no'), 180)] : [])\n ],\n sorters: [{ name: 'dueOn' }]\n }\n }\n\n async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [], search, signal }: FetchOption) {\n const door = this.manager ? 'enrollmentStatus' : 'teamEnrollmentStatus'\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: ${door}(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items { ${this.manager ? STATUS_ROW_FIELDS : TEAM_ROW_FIELDS} }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },\n fetchPolicy: 'no-cache',\n context: { fetchOptions: { signal }, queryDeduplication: false }\n })\n if (response.errors?.length) throw response.errors[0]\n /* The register action names the course filtered - said again with each read. */\n this.updateContext()\n return { total: response.data.responses.total, records: response.data.responses.items.map((row: any) => this.recordOf(row)) }\n }\n\n /* The state chips' numbers, under the list's other conditions - the chips' own left out, counted by the server. */\n async countHandler({ filters = [], search, signal }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $search: String) {\n enrollmentStateCounts(filters: $filters, search: $search) { value count }\n }\n `,\n variables: { filters: filters.filter((one: any) => one.name !== 'state'), search: search?.trim() || undefined },\n fetchPolicy: 'no-cache',\n context: { fetchOptions: { signal }, queryDeduplication: false }\n })\n if (response.errors?.length) throw response.errors[0]\n return { state: Object.fromEntries(response.data.enrollmentStateCounts.map((row: { value: string; count: number }) => [row.value, row.count])) }\n }\n\n private selectedOpen(): any[] {\n return (this.grist?.selected ?? []).filter((one: any) => one.open)\n }\n\n private async remind() {\n const ids = this.selectedOpen().map(one => one.id)\n if (!ids.length) return notify({ level: 'warn', message: t('text.learning-pick-open') })\n const done = await this.reminder.run({ ids })\n if (done) notify({ message: t('text.learning-reminded', { count: done.remindEnrollments }) })\n }\n\n /** Cancels the enrolments picked - all but the cancelled ones - with one reason, together. */\n private async cancel() {\n const ids = (this.grist?.selected ?? []).filter((one: any) => one.state !== 'CANCELLED').map((one: any) => one.id)\n if (!ids.length) return notify({ level: 'warn', message: t('text.learning-pick-cancellable') })\n const reason = await OxPrompt.prompt({ title: t('title.learning-cancel-enrollments', { count: ids.length }), text: t('field.learning-cancel-reason'), multiline: true })\n if (reason === null || !String(reason).trim()) return\n const done = await this.canceller.run({ ids, reason: String(reason).trim() })\n if (done) {\n notify({ message: t('text.learning-cancelled', { count: done.cancelEnrollments.length }) })\n this.grist.fetch()\n }\n }\n}\n\n"]}
@@ -20,12 +20,16 @@ export declare class LearningView extends LearningView_base {
20
20
  private result?;
21
21
  private agreed;
22
22
  private tickError;
23
+ private switching;
23
24
  private read;
25
+ private savedAt?;
24
26
  private tickSave;
25
27
  private quizStart;
26
28
  private quizSubmit;
27
29
  private acknowledgement;
28
30
  private ticks;
31
+ /** Where each lesson starts - the server's last answer for its unit, else what the page read (LessonStarts). */
32
+ private starts;
29
33
  get enrollment(): any;
30
34
  get context(): {
31
35
  subject?: {
@@ -43,8 +47,15 @@ export declare class LearningView extends LearningView_base {
43
47
  private renderLesson;
44
48
  private renderQuiz;
45
49
  private renderAcknowledgement;
50
+ /**
51
+ * Opens a unit. The lesson shown leaves first and its last tick is answered, so the unit opened - the same one coming
52
+ * back too - starts from what the server counted, not from what the page read when it opened (2026-11-08).
53
+ */
46
54
  private open;
55
+ /** Keeps the server's answer for a unit - the next time it opens, it starts there. */
56
+ private keep;
47
57
  private tick;
58
+ private tickLeaving;
48
59
  private startQuiz;
49
60
  private submit;
50
61
  private errorText;