@things-factory/learning 10.1.260 → 10.1.261
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/learning-material-form.d.ts +41 -0
- package/dist-client/components/learning-material-form.js +202 -0
- package/dist-client/components/learning-material-form.js.map +1 -0
- package/dist-client/components/learning-unit-form.d.ts +3 -0
- package/dist-client/components/learning-unit-form.js +10 -2
- package/dist-client/components/learning-unit-form.js.map +1 -1
- package/dist-client/pages/learning-course-editor.d.ts +1 -0
- package/dist-client/pages/learning-course-editor.js +19 -6
- package/dist-client/pages/learning-course-editor.js.map +1 -1
- package/dist-client/pages/learning-course-list.d.ts +1 -1
- package/dist-client/pages/learning-course-list.js +6 -2
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +28 -16
- package/dist-client/pages/learning-enrollment-status.js +95 -112
- package/dist-client/pages/learning-enrollment-status.js.map +1 -1
- package/dist-client/pages/learning-view.d.ts +11 -0
- package/dist-client/pages/learning-view.js +94 -19
- package/dist-client/pages/learning-view.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/learning-api.d.ts +48 -3
- package/dist-client/utils/learning-api.js +51 -2
- package/dist-client/utils/learning-api.js.map +1 -1
- package/dist-client/utils/material-source.d.ts +12 -0
- package/dist-client/utils/material-source.js +77 -0
- package/dist-client/utils/material-source.js.map +1 -0
- package/dist-server/ai-reads.js +1 -1
- package/dist-server/ai-reads.js.map +1 -1
- package/dist-server/service/course/course-mutation.d.ts +1 -1
- package/dist-server/service/course/course-mutation.js +7 -6
- package/dist-server/service/course/course-mutation.js.map +1 -1
- package/dist-server/service/course/course-query.d.ts +1 -0
- package/dist-server/service/course/course-query.js +14 -3
- package/dist-server/service/course/course-query.js.map +1 -1
- package/dist-server/service/course/course-rules.d.ts +3 -1
- package/dist-server/service/course/course-rules.js +5 -1
- package/dist-server/service/course/course-rules.js.map +1 -1
- package/dist-server/service/course/course-service.d.ts +5 -4
- package/dist-server/service/course/course-service.js +10 -14
- package/dist-server/service/course/course-service.js.map +1 -1
- package/dist-server/service/course/course-type.d.ts +1 -0
- package/dist-server/service/course/course-type.js +4 -0
- package/dist-server/service/course/course-type.js.map +1 -1
- package/dist-server/service/course/course-unit.d.ts +3 -2
- package/dist-server/service/course/course-unit.js +7 -2
- package/dist-server/service/course/course-unit.js.map +1 -1
- package/dist-server/service/course/index.d.ts +2 -1
- package/dist-server/service/course/index.js +2 -1
- package/dist-server/service/course/index.js.map +1 -1
- package/dist-server/service/course/material-audio.d.ts +1 -0
- package/dist-server/service/course/material-audio.js +43 -0
- package/dist-server/service/course/material-audio.js.map +1 -0
- package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
- package/dist-server/service/course/material-draft-mutation.js +55 -0
- package/dist-server/service/course/material-draft-mutation.js.map +1 -0
- package/dist-server/service/course/material-draft-rules.d.ts +38 -0
- package/dist-server/service/course/material-draft-rules.js +83 -0
- package/dist-server/service/course/material-draft-rules.js.map +1 -0
- package/dist-server/service/course/material-draft-service.d.ts +15 -0
- package/dist-server/service/course/material-draft-service.js +162 -0
- package/dist-server/service/course/material-draft-service.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-query.d.ts +5 -4
- package/dist-server/service/enrollment/enrollment-query.js +26 -35
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.js +13 -3
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +31 -18
- package/dist-server/service/enrollment/enrollment-status.js +101 -42
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +1 -14
- package/dist-server/service/enrollment/enrollment-type.js +6 -54
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
- package/dist-server/service/enrollment/enrollment.d.ts +1 -0
- package/dist-server/service/enrollment/enrollment.js +5 -0
- package/dist-server/service/enrollment/enrollment.js.map +1 -1
- package/dist-server/service/enrollment/learning-ai.d.ts +9 -2
- package/dist-server/service/enrollment/learning-ai.js +25 -28
- package/dist-server/service/enrollment/learning-ai.js.map +1 -1
- package/dist-server/service/enrollment/learning-documents.js +3 -4
- package/dist-server/service/enrollment/learning-documents.js.map +1 -1
- package/dist-server/service/enrollment/progress-rules.d.ts +36 -11
- package/dist-server/service/enrollment/progress-rules.js +47 -12
- package/dist-server/service/enrollment/progress-rules.js.map +1 -1
- package/dist-server/service/enrollment/unit-progress.d.ts +4 -0
- package/dist-server/service/enrollment/unit-progress.js +20 -0
- package/dist-server/service/enrollment/unit-progress.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/translations/en.json +30 -7
- package/translations/es.json +30 -7
- package/translations/ja.json +30 -7
- package/translations/ko.json +30 -7
- package/translations/ms.json +30 -7
- package/translations/zh.json +30 -7
|
@@ -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 {
|
|
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,36 @@ 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
|
+
const TEAM_ROW_FIELDS = 'id state departmentName units unitsDone user { id name } course { id name } enrollment { id status dueOn completedAt }';
|
|
20
20
|
/**
|
|
21
|
-
* 수강 현황 (ADR-0194 decisions 10, 11) - where
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* 수강 현황 (ADR-0194 decisions 10, 11) - where enrolments stand: who, their department, the state today, units done,
|
|
22
|
+
* minutes, score and the completion day. The conditions - course, state with its counts, search, sort, page - are the
|
|
23
|
+
* grid's, kept in the address, and read by the server. A manager reminds and cancels from here and issues the completion
|
|
24
|
+
* register of the course filtered (PDF, through the issued document ledger) or exports the list (Excel). A department
|
|
25
|
+
* head sees the people of their departments - the state and days only.
|
|
25
26
|
*/
|
|
26
27
|
let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i18next)(PageView) {
|
|
27
28
|
constructor() {
|
|
28
29
|
super(...arguments);
|
|
29
|
-
|
|
30
|
-
this.stateFilter = '';
|
|
30
|
+
/* The courses the course filter offers - a manager's only. */
|
|
31
31
|
this.courses = new QueryController(this, gql `query { courses(pagination: { page: 1, limit: 500 }) { items { id name } } }`, {
|
|
32
32
|
variables: () => (this.manager ? {} : undefined)
|
|
33
33
|
});
|
|
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
34
|
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 } }
|
|
35
|
+
this.canceller = new MutationController(this, gql `mutation ($id: String!, $reason: String!) { cancelEnrollment(id: $id, reason: $reason) { id } }`);
|
|
38
36
|
}
|
|
39
|
-
static { this.styles = [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 };
|
|
37
|
+
static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
|
|
38
|
+
/** The course the grid's `course` filter names - the completion register is of it. */
|
|
39
|
+
get filteredCourse() {
|
|
40
|
+
const id = this.grist?.filters?.find((one) => one.name === 'course')?.value;
|
|
41
|
+
return id ? (this.courses.data?.courses?.items ?? []).find((one) => one.id === id) : undefined;
|
|
72
42
|
}
|
|
73
43
|
get context() {
|
|
74
|
-
const course = this.
|
|
44
|
+
const course = this.filteredCourse;
|
|
75
45
|
return {
|
|
76
46
|
title: t('title.learning-enrollment-status'),
|
|
47
|
+
search: { handler: (search) => (this.grist.searchText = search), value: this.grist?.searchText || '' },
|
|
48
|
+
filter: { handler: () => this.grist.toggleHeadroom() },
|
|
77
49
|
...(this.manager
|
|
78
50
|
? {
|
|
79
51
|
actions: [
|
|
@@ -86,23 +58,34 @@ let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i
|
|
|
86
58
|
: {})
|
|
87
59
|
};
|
|
88
60
|
}
|
|
89
|
-
|
|
90
|
-
return this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
61
|
+
render() {
|
|
62
|
+
return this.gristConfig
|
|
63
|
+
? html `<ox-grist
|
|
64
|
+
class="page-fill"
|
|
65
|
+
search-mode="term"
|
|
66
|
+
.mode=${isMobileDevice() ? 'CARD' : 'GRID'}
|
|
67
|
+
.config=${this.gristConfig}
|
|
68
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
69
|
+
.countHandler=${this.countHandler.bind(this)}
|
|
70
|
+
?url-params-sensitive=${this.active}
|
|
71
|
+
>
|
|
72
|
+
<div slot="headroom" class="header">
|
|
73
|
+
<div class="filters"><ox-filters-form without-search></ox-filters-form></div>
|
|
74
|
+
</div>
|
|
75
|
+
</ox-grist>`
|
|
76
|
+
: html ``;
|
|
100
77
|
}
|
|
101
78
|
async pageInitialized() {
|
|
102
79
|
this.manager = !!(await hasPrivilege({ category: 'course', privilege: 'query', domainOwnerGranted: true }));
|
|
103
80
|
this.gristConfig = this.configOf(this.manager);
|
|
104
81
|
this.updateContext();
|
|
105
82
|
}
|
|
83
|
+
/** The courses for the course filter, once the read of them has answered. */
|
|
84
|
+
async courseOptions() {
|
|
85
|
+
if (this.courses.loading)
|
|
86
|
+
await new Promise(resolve => this.addEventListener(QUERY_ANSWERED, resolve, { once: true }));
|
|
87
|
+
return [{ value: '', display: t('text.learning-course-all') }, ...(this.courses.data?.courses?.items ?? []).map((one) => ({ value: one.id, display: one.name }))];
|
|
88
|
+
}
|
|
106
89
|
recordOf(row) {
|
|
107
90
|
const e = row.enrollment ?? {};
|
|
108
91
|
return {
|
|
@@ -121,63 +104,67 @@ let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i
|
|
|
121
104
|
};
|
|
122
105
|
}
|
|
123
106
|
configOf(manager) {
|
|
124
|
-
const column = (name, header, width, extra = {}) => ({ type: 'string', name, header, width, record: { editable: false, ...extra } });
|
|
107
|
+
const column = (name, header, width, extra = {}) => ({ type: 'string', name, header, width, sortable: false, ...extra, record: { editable: false, ...extra.record } });
|
|
125
108
|
return {
|
|
126
109
|
rows: { appendable: false, selectable: manager ? { multiple: true } : false },
|
|
127
|
-
pagination: {
|
|
110
|
+
pagination: { limit: 100, pages: [50, 100, 500] },
|
|
128
111
|
emptyNote: { title: t('text.learning-no-enrollment') },
|
|
129
112
|
list: { fields: ['name', 'state'], details: ['course', 'dueOn'] },
|
|
130
113
|
columns: [
|
|
131
114
|
...(manager ? [{ type: 'gutter', gutterName: 'row-selector', multiple: true }] : []),
|
|
132
115
|
column('name', t('field.learning-learner'), 120),
|
|
133
116
|
column('department', t('field.learning-department'), 120),
|
|
134
|
-
column('course', t('field.learning-course-name'), 220),
|
|
135
|
-
column('state', t('field.learning-state'), 100, {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
117
|
+
column('course', t('field.learning-course-name'), 220, manager ? { filter: { type: 'select', operator: 'eq', options: () => this.courseOptions() } } : {}),
|
|
118
|
+
column('state', t('field.learning-state'), 100, {
|
|
119
|
+
record: { renderer: (value) => stateWord(value) },
|
|
120
|
+
/* Chips with their counts; none pressed is every state. */
|
|
121
|
+
filter: { type: 'select-buttons', operator: 'in', options: STATES.map(value => ({ value, display: stateWord(value) })) }
|
|
122
|
+
}),
|
|
123
|
+
column('dueOn', t('field.learning-due-on'), 110, { sortable: true }),
|
|
124
|
+
column('units', t('field.learning-units-done'), 90, { record: { align: 'right' } }),
|
|
125
|
+
...(manager ? [column('minutes', t('field.learning-minutes'), 120, { record: { align: 'right' } }), column('score', t('field.learning-score'), 70, { record: { align: 'right' } })] : []),
|
|
139
126
|
column('completedOn', t('field.learning-completed-on'), 110),
|
|
140
127
|
...(manager ? [column('certificateNo', t('field.learning-certificate-no'), 180)] : [])
|
|
141
|
-
]
|
|
128
|
+
],
|
|
129
|
+
sorters: [{ name: 'dueOn' }]
|
|
142
130
|
};
|
|
143
131
|
}
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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;
|
|
132
|
+
async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [], search, signal }) {
|
|
133
|
+
const door = this.manager ? 'enrollmentStatus' : 'teamEnrollmentStatus';
|
|
134
|
+
const response = await client.query({
|
|
135
|
+
query: gql `
|
|
136
|
+
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {
|
|
137
|
+
responses: ${door}(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {
|
|
138
|
+
items { ${this.manager ? STATUS_ROW_FIELDS : TEAM_ROW_FIELDS} }
|
|
139
|
+
total
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`,
|
|
143
|
+
variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },
|
|
144
|
+
fetchPolicy: 'no-cache',
|
|
145
|
+
context: { fetchOptions: { signal }, queryDeduplication: false }
|
|
146
|
+
});
|
|
147
|
+
if (response.errors?.length)
|
|
148
|
+
throw response.errors[0];
|
|
149
|
+
/* The register action names the course filtered - said again with each read. */
|
|
180
150
|
this.updateContext();
|
|
151
|
+
return { total: response.data.responses.total, records: response.data.responses.items.map((row) => this.recordOf(row)) };
|
|
152
|
+
}
|
|
153
|
+
/* The state chips' numbers, under the list's other conditions - the chips' own left out, counted by the server. */
|
|
154
|
+
async countHandler({ filters = [], search, signal }) {
|
|
155
|
+
const response = await client.query({
|
|
156
|
+
query: gql `
|
|
157
|
+
query ($filters: [Filter!], $search: String) {
|
|
158
|
+
enrollmentStateCounts(filters: $filters, search: $search) { value count }
|
|
159
|
+
}
|
|
160
|
+
`,
|
|
161
|
+
variables: { filters: filters.filter((one) => one.name !== 'state'), search: search?.trim() || undefined },
|
|
162
|
+
fetchPolicy: 'no-cache',
|
|
163
|
+
context: { fetchOptions: { signal }, queryDeduplication: false }
|
|
164
|
+
});
|
|
165
|
+
if (response.errors?.length)
|
|
166
|
+
throw response.errors[0];
|
|
167
|
+
return { state: Object.fromEntries(response.data.enrollmentStateCounts.map((row) => [row.value, row.count])) };
|
|
181
168
|
}
|
|
182
169
|
selectedOpen() {
|
|
183
170
|
return (this.grist?.selected ?? []).filter((one) => one.open);
|
|
@@ -197,18 +184,12 @@ let LearningEnrollmentStatus = class LearningEnrollmentStatus extends localize(i
|
|
|
197
184
|
const reason = await OxPrompt.prompt({ title: t('button.learning-cancel-enrollment'), text: t('field.learning-cancel-reason'), multiline: true });
|
|
198
185
|
if (reason === null || !String(reason).trim())
|
|
199
186
|
return;
|
|
200
|
-
if (await this.canceller.run({ id: one.id, reason: String(reason).trim() }))
|
|
187
|
+
if (await this.canceller.run({ id: one.id, reason: String(reason).trim() })) {
|
|
201
188
|
notify({ message: t('text.learning-cancelled') });
|
|
189
|
+
this.grist.fetch();
|
|
190
|
+
}
|
|
202
191
|
}
|
|
203
192
|
};
|
|
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
193
|
__decorate([
|
|
213
194
|
state(),
|
|
214
195
|
__metadata("design:type", Boolean)
|
|
@@ -219,7 +200,9 @@ __decorate([
|
|
|
219
200
|
], LearningEnrollmentStatus.prototype, "gristConfig", void 0);
|
|
220
201
|
__decorate([
|
|
221
202
|
query('ox-grist'),
|
|
222
|
-
__metadata("design:type", DataGrist
|
|
203
|
+
__metadata("design:type", DataGrist
|
|
204
|
+
/* The courses the course filter offers - a manager's only. */
|
|
205
|
+
)
|
|
223
206
|
], LearningEnrollmentStatus.prototype, "grist", void 0);
|
|
224
207
|
LearningEnrollmentStatus = __decorate([
|
|
225
208
|
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\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 ($id: String!, $reason: String!) { cancelEnrollment(id: $id, reason: $reason) { id } }`)\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; none pressed is every state. */\n filter: { type: 'select-buttons', operator: 'in', 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 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() })) {\n notify({ message: t('text.learning-cancelled') })\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;
|
|
@@ -15,8 +15,14 @@ import { i18next, localize } from '@operato/i18n';
|
|
|
15
15
|
import { PageView } from '@operato/shell';
|
|
16
16
|
import { DocumentPageStyles } from '@operato/styles';
|
|
17
17
|
import { issueDocument } from '@things-factory/pdf/dist-client';
|
|
18
|
-
import { LEARNING_FIELDS, unitsDoneOf } from '../utils/learning-api.js';
|
|
18
|
+
import { LEARNING_FIELDS, LessonStarts, pageLookedOf, PROGRESS_FIELDS, resumeUnitId, unitsDoneOf } from '../utils/learning-api.js';
|
|
19
19
|
import { dayOf, minutesOf, missingWord, stateWord, t, unitKindWord } from '../utils/learning-words.js';
|
|
20
|
+
const TICK_MUTATION = `mutation ($enrollmentId: String!, $unitId: String!, $seconds: Int!, $position: Int!) {
|
|
21
|
+
recordLearningTick(enrollmentId: $enrollmentId, unitId: $unitId, seconds: $seconds, position: $position) {
|
|
22
|
+
enrollment { id status }
|
|
23
|
+
progress { ${PROGRESS_FIELDS} }
|
|
24
|
+
}
|
|
25
|
+
}`;
|
|
20
26
|
/**
|
|
21
27
|
* 과정 화면 (ADR-0194 decisions 3-9) - one enrolment: the units in order with the one open played by `ox-lesson`, whose
|
|
22
28
|
* ticks go to the server as they come (the server counts the time); the quiz drawn by the server and scored there; the
|
|
@@ -29,16 +35,13 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
29
35
|
this.unitId = '';
|
|
30
36
|
this.agreed = false;
|
|
31
37
|
this.tickError = '';
|
|
38
|
+
/* While one lesson leaves for another, nothing is shown - the leaving one's last tick is answered first. */
|
|
39
|
+
this.switching = false;
|
|
32
40
|
this.read = new QueryController(this, gql `query ($id: String!) { myEnrollment(id: $id) { ${LEARNING_FIELDS} } }`, {
|
|
33
41
|
variables: () => (this.enrollmentId ? { id: this.enrollmentId } : undefined),
|
|
34
42
|
keepPrevious: true
|
|
35
43
|
});
|
|
36
|
-
this.tickSave = new MutationController(this, gql
|
|
37
|
-
recordLearningTick(enrollmentId: $enrollmentId, unitId: $unitId, seconds: $seconds, position: $position) {
|
|
38
|
-
enrollment { id status }
|
|
39
|
-
progress { learnedSeconds watchedSeconds positionSeconds doneAt }
|
|
40
|
-
}
|
|
41
|
-
}`, { showsError: true });
|
|
44
|
+
this.tickSave = new MutationController(this, gql(TICK_MUTATION), { showsError: true });
|
|
42
45
|
this.quizStart = new MutationController(this, gql `mutation ($enrollmentId: String!) { startQuizAttempt(enrollmentId: $enrollmentId) { attemptId questions { id kind text choices { id text } } } }`, {
|
|
43
46
|
showsError: true
|
|
44
47
|
});
|
|
@@ -47,8 +50,13 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
47
50
|
showsError: true
|
|
48
51
|
});
|
|
49
52
|
this.acknowledgement = new MutationController(this, gql `mutation ($id: String!) { acknowledgeEnrollment(id: $id) { id status } }`, { refetch: () => [this.read], showsError: true });
|
|
50
|
-
/*
|
|
53
|
+
/*
|
|
54
|
+
* Ticks go one after another - the leaving one too - so the server takes them in the order they were looked at: a
|
|
55
|
+
* stop right after a start is not dropped while the start is out, and a page left on is never overtaken by an older one.
|
|
56
|
+
*/
|
|
51
57
|
this.ticks = Promise.resolve();
|
|
58
|
+
/** Where each lesson starts - the server's last answer for its unit, else what the page read (LessonStarts). */
|
|
59
|
+
this.starts = new LessonStarts();
|
|
52
60
|
}
|
|
53
61
|
static { this.styles = [
|
|
54
62
|
DocumentPageStyles,
|
|
@@ -154,6 +162,12 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
154
162
|
margin: 0;
|
|
155
163
|
line-height: 1.6;
|
|
156
164
|
}
|
|
165
|
+
.saved {
|
|
166
|
+
margin: 0;
|
|
167
|
+
min-height: 1.4em;
|
|
168
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
169
|
+
font-size: var(--ui-t-small, 12px);
|
|
170
|
+
}
|
|
157
171
|
.result {
|
|
158
172
|
margin: 0;
|
|
159
173
|
font-weight: 600;
|
|
@@ -181,9 +195,8 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
181
195
|
updated() {
|
|
182
196
|
const one = this.enrollment;
|
|
183
197
|
if (one && !this.unitId && one.units?.length) {
|
|
184
|
-
/* Where the learner
|
|
185
|
-
|
|
186
|
-
this.unitId = next.unit.id;
|
|
198
|
+
/* Where the learner was - the unit learned last (resumeUnitId). */
|
|
199
|
+
this.unitId = resumeUnitId(one);
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
202
|
render() {
|
|
@@ -242,7 +255,9 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
242
255
|
}
|
|
243
256
|
renderLesson(one, row) {
|
|
244
257
|
const unit = row.unit;
|
|
245
|
-
|
|
258
|
+
if (this.switching)
|
|
259
|
+
return html `<h3>${unit.name}</h3>`;
|
|
260
|
+
const progress = unit.kind === 'LINK' ? {} : this.starts.startOf(one.id, unit.id, row.progress);
|
|
246
261
|
if (unit.kind === 'LINK')
|
|
247
262
|
return html `<h3>${unit.name}</h3>
|
|
248
263
|
<p class="page-section-note">${t('text.learning-link-not-counted')}</p>
|
|
@@ -258,14 +273,19 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
258
273
|
.src=${unit.file?.fullpath}
|
|
259
274
|
.body=${unit.body ?? ''}
|
|
260
275
|
.label=${unit.name}
|
|
261
|
-
.watched=${progress.watchedSeconds ?? 0}
|
|
262
|
-
.position=${progress.positionSeconds ?? 0}
|
|
276
|
+
.watched=${unit.kind === 'PDF' ? (progress.pagesRead ?? 0) : (progress.watchedSeconds ?? 0)}
|
|
277
|
+
.position=${unit.kind === 'PDF' ? (progress.pageAt ?? 0) : (progress.positionSeconds ?? 0)}
|
|
278
|
+
.pages=${unit.pageCount ?? 0}
|
|
279
|
+
.pageSeconds=${unit.secondsPerPage ?? undefined}
|
|
280
|
+
.pageLooked=${unit.kind === 'PDF' ? pageLookedOf(progress) : 0}
|
|
263
281
|
.requiredSeconds=${unit.minSeconds ?? 60}
|
|
264
282
|
.learnedSeconds=${progress.learnedSeconds ?? 0}
|
|
265
|
-
.done=${!!progress.doneAt || one.status === 'COMPLETED'}
|
|
283
|
+
.done=${!!row.progress?.doneAt || !!progress.doneAt || one.status === 'COMPLETED'}
|
|
266
284
|
@lesson-tick=${(e) => this.tick(one.id, unit.id, e.detail)}
|
|
267
285
|
></ox-lesson>`)}
|
|
268
|
-
${this.tickError
|
|
286
|
+
${this.tickError
|
|
287
|
+
? html `<div class="error" role="alert">${this.tickError}</div>`
|
|
288
|
+
: html `<p class="saved" role="status">${this.savedAt ? t('text.learning-saved-at', { time: this.savedAt.toLocaleTimeString(i18next.language) }) : ''}</p>`}`;
|
|
269
289
|
}
|
|
270
290
|
renderQuiz(one) {
|
|
271
291
|
const completed = one.status === 'COMPLETED';
|
|
@@ -301,26 +321,73 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
301
321
|
${this.acknowledgement.error ? html `<div class="error" role="alert">${this.errorText(this.acknowledgement.error)}</div>` : nothing}`}
|
|
302
322
|
</section>`;
|
|
303
323
|
}
|
|
304
|
-
|
|
305
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Opens a unit. The lesson shown leaves first and its last tick is answered, so the unit opened - the same one coming
|
|
326
|
+
* back too - starts from what the server counted, not from what the page read when it opened (2026-11-08).
|
|
327
|
+
*/
|
|
328
|
+
async open(unitId) {
|
|
329
|
+
if (unitId === this.unitId || this.switching)
|
|
330
|
+
return;
|
|
306
331
|
this.tickError = '';
|
|
332
|
+
this.switching = true;
|
|
333
|
+
await this.updateComplete;
|
|
334
|
+
await this.ticks;
|
|
335
|
+
this.starts.reopen();
|
|
336
|
+
this.unitId = unitId;
|
|
337
|
+
this.switching = false;
|
|
338
|
+
}
|
|
339
|
+
/** Keeps the server's answer for a unit - the next time it opens, it starts there. */
|
|
340
|
+
keep(enrollmentId, unitId, answer) {
|
|
341
|
+
this.starts.keep(enrollmentId, unitId, answer?.progress);
|
|
342
|
+
this.savedAt = new Date();
|
|
307
343
|
}
|
|
308
344
|
tick(enrollmentId, unitId, detail) {
|
|
345
|
+
const variables = { enrollmentId, unitId, seconds: Math.round(detail.seconds), position: Math.floor(detail.position) };
|
|
346
|
+
/*
|
|
347
|
+
* Leaving - the tab hidden, closed or reloaded - the last piece goes out on a request the browser finishes after the
|
|
348
|
+
* page is gone (keepalive). Through the usual queue it was dropped with the page, and up to a tick was lost.
|
|
349
|
+
*/
|
|
350
|
+
if (document.visibilityState === 'hidden') {
|
|
351
|
+
this.ticks = this.ticks.then(() => this.tickLeaving(variables));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
309
354
|
this.ticks = this.ticks.then(async () => {
|
|
310
355
|
const before = this.enrollment;
|
|
311
|
-
const answer = await this.tickSave.run(
|
|
356
|
+
const answer = await this.tickSave.run(variables);
|
|
312
357
|
if (!answer) {
|
|
313
358
|
this.tickError = this.errorText(this.tickSave.error);
|
|
314
359
|
await this.read.refetch();
|
|
315
360
|
return;
|
|
316
361
|
}
|
|
317
362
|
this.tickError = '';
|
|
363
|
+
this.keep(enrollmentId, unitId, answer.recordLearningTick);
|
|
318
364
|
const row = before?.units?.find((one) => one.unit.id === unitId);
|
|
319
365
|
const newlyDone = !!answer.recordLearningTick.progress.doneAt && !row?.progress?.doneAt;
|
|
320
366
|
if (newlyDone || answer.recordLearningTick.enrollment.status !== before?.status)
|
|
321
367
|
await this.read.refetch();
|
|
322
368
|
});
|
|
323
369
|
}
|
|
370
|
+
/*
|
|
371
|
+
* The leaving tick goes after the ticks before it, on a request the browser finishes after the page is gone
|
|
372
|
+
* (keepalive). Only when the page is closed while an earlier tick is still out is it not sent - the order is kept.
|
|
373
|
+
*/
|
|
374
|
+
async tickLeaving(variables) {
|
|
375
|
+
await fetch('/graphql', {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
keepalive: true,
|
|
378
|
+
credentials: 'include',
|
|
379
|
+
headers: { 'Content-Type': 'application/json', 'Apollo-Require-Preflight': 'true' },
|
|
380
|
+
body: JSON.stringify({ query: TICK_MUTATION, variables })
|
|
381
|
+
})
|
|
382
|
+
.then(response => response.json())
|
|
383
|
+
.then(answer => {
|
|
384
|
+
if (answer?.data?.recordLearningTick)
|
|
385
|
+
this.keep(variables.enrollmentId, variables.unitId, answer.data.recordLearningTick);
|
|
386
|
+
else
|
|
387
|
+
this.tickError = answer?.errors?.[0]?.message ?? '';
|
|
388
|
+
})
|
|
389
|
+
.catch(error => (this.tickError = error?.message ?? String(error)));
|
|
390
|
+
}
|
|
324
391
|
async startQuiz(enrollmentId) {
|
|
325
392
|
this.result = undefined;
|
|
326
393
|
const answer = await this.quizStart.run({ enrollmentId });
|
|
@@ -364,6 +431,14 @@ __decorate([
|
|
|
364
431
|
state(),
|
|
365
432
|
__metadata("design:type", Object)
|
|
366
433
|
], LearningView.prototype, "tickError", void 0);
|
|
434
|
+
__decorate([
|
|
435
|
+
state(),
|
|
436
|
+
__metadata("design:type", Object)
|
|
437
|
+
], LearningView.prototype, "switching", void 0);
|
|
438
|
+
__decorate([
|
|
439
|
+
state(),
|
|
440
|
+
__metadata("design:type", Date)
|
|
441
|
+
], LearningView.prototype, "savedAt", void 0);
|
|
367
442
|
LearningView = __decorate([
|
|
368
443
|
customElement('learning-view')
|
|
369
444
|
], LearningView);
|