@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.
- 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 -5
- package/dist-client/pages/learning-course-list.js +54 -16
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +31 -16
- package/dist-client/pages/learning-enrollment-status.js +104 -117
- 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 +2 -0
- package/dist-server/service/course/course-query.js +27 -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-mutation.d.ts +1 -1
- package/dist-server/service/enrollment/enrollment-mutation.js +6 -6
- package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-query.d.ts +8 -4
- package/dist-server/service/enrollment/enrollment-query.js +47 -36
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.d.ts +4 -3
- package/dist-server/service/enrollment/enrollment-service.js +41 -18
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +42 -18
- package/dist-server/service/enrollment/enrollment-status.js +143 -42
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +2 -8
- package/dist-server/service/enrollment/enrollment-type.js +20 -42
- 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/index.d.ts +3 -3
- package/dist-server/service/enrollment/index.js +3 -3
- package/dist-server/service/enrollment/index.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 +40 -11
- package/dist-server/service/enrollment/progress-rules.js +54 -13
- 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 +2 -2
- package/dist-server/service/index.js +2 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/translations/en.json +42 -9
- package/translations/es.json +42 -9
- package/translations/ja.json +42 -9
- package/translations/ko.json +42 -9
- package/translations/ms.json +42 -9
- 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 {
|
|
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
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
|
|
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 ($
|
|
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
|
-
|
|
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 };
|
|
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.
|
|
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
|
-
|
|
90
|
-
return this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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: {
|
|
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, {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
|
195
|
-
if (!
|
|
196
|
-
return notify({ level: 'warn', message: t('text.learning-pick-
|
|
197
|
-
const reason = await OxPrompt.prompt({ title: t('
|
|
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
|
-
|
|
201
|
-
|
|
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":";AAAA,OAAO,iCAAiC,CAAA;AACxC,OAAO,6BAA6B,CAAA;AACpC,OAAO,iCAAiC,CAAA;AAExC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,4BAA4B,CAAA;AAEnF;;;;;GAKG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAyB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAAlE;;QA8BY,aAAQ,GAAG,EAAE,CAAA;QACb,gBAAW,GAAG,EAAE,CAAA;QAMzB,YAAO,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,GAAG,CAAA,8EAA8E,EAAE;YAC7H,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;SACjD,CAAC,CAAA;QACM,SAAI,GAAG,IAAI,eAAe,CAChC,IAAI,EACJ,GAAG,CAAA,yFAAyF,iBAAiB,gEAAgE,EAC7K,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CACvG,CAAA;QACO,aAAQ,GAAG,IAAI,eAAe,CACpC,IAAI,EACJ,GAAG,CAAA,gRAAgR,EACnR,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CACxG,CAAA;QACO,aAAQ,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,8DAA8D,CAAC,CAAA;QAC1G,cAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,iGAAiG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IA6IxL,CAAC;aA/LQ,WAAM,GAAG;QACd,qBAAqB;QACrB,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;KAqBF;KACF,AA3BY,CA2BZ;IAyBD,IAAY,MAAM;QAChB,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IAC5H,CAAC;IAED,IAAY,IAAI;QACd,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IACrI,CAAC;IAED,IAAI,OAAO;QACT,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAA;QACpE,OAAO;YACL,KAAK,EAAE,CAAC,CAAC,kCAAkC,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,OAAO;gBACd,CAAC,CAAC;oBACE,OAAO,EAAE;wBACP,EAAE,KAAK,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE;wBACjG,EAAE,KAAK,EAAE,CAAC,CAAC,mCAAmC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE;wBAC7F,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,8BAA8B,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBACnM;oBACD,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kCAAkC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE;iBACzK;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAA;IACH,CAAC;IAED,IAAY,UAAU;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAA;IAChD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAc;QAC5C,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAM;QACxB,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAA;YACxC,IAAI,MAAM,IAAI,MAAM,KAAK,IAAI,CAAC,QAAQ;gBAAE,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAA;QAChE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3G,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC9C,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;IAEO,QAAQ,CAAC,GAAQ;QACvB,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAA;QAC9B,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE;YAC1B,UAAU,EAAE,GAAG,CAAC,cAAc,IAAI,EAAE;YACpC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE;YAC9B,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;YACpB,KAAK,EAAE,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,KAAK,EAAE;YACtC,OAAO,EAAE,CAAC,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI;YACtE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI;YACtB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC;YACjC,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,EAAE;YACpC,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,KAAK,aAAa;SAC5D,CAAA;IACH,CAAC;IAEO,QAAQ,CAAC,OAAgB;QAC/B,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,MAAc,EAAE,KAAU,EAAE,QAAa,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAA;QAC9J,OAAO;YACL,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE;YAC7E,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC9B,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,6BAA6B,CAAC,EAAE;YACtD,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE;YACjE,OAAO,EAAE;gBACP,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpF,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,GAAG,CAAC;gBAChD,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,2BAA2B,CAAC,EAAE,GAAG,CAAC;gBACzD,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,4BAA4B,CAAC,EAAE,GAAG,CAAC;gBACtD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,sBAAsB,CAAC,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,uBAAuB,CAAC,EAAE,GAAG,CAAC;gBAChD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,2BAA2B,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;gBACvE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,sBAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjK,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,6BAA6B,CAAC,EAAE,GAAG,CAAC;gBAC5D,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,+BAA+B,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aACvF;SACF,CAAA;IACH,CAAC;IAED,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAA;QACjC,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,yBAAyB,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACrI,MAAM,OAAO,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,0BAA0B,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,CAAA;QACrF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;QACrE,OAAO,IAAI,CAAA,qCAAqC,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,YAAY,IAAI,CAAC,WAAW,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE;;;YAGlJ,IAAI,CAAC,OAAO;YACZ,CAAC,CAAC,IAAI,CAAA,qBAAqB,IAAI,CAAC,QAAQ,eAAe,CAAC,CAAC,4BAA4B,CAAC,YAAY,CAAC,CAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;qCACxI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,IAAI,EAAE;+DAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA,qBAAqB,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,QAAQ,CAAC;2BAC3G;YACf,CAAC,CAAC,OAAO;8BACS,IAAI,CAAC,WAAW,eAAe,CAAC,CAAC,sBAAsB,CAAC,YAAY,CAAC,CAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;iCAC/H,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO;+CAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA,qBAAqB,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,QAAQ,CAAC;;YAE/G,OAAO;YACP,CAAC,CAAC,IAAI,CAAA;wBACM,CAAC,CAAC,2BAA2B,CAAC,YAAY,OAAO,CAAC,KAAK;wBACvD,CAAC,CAAC,8BAA8B,CAAC,YAAY,OAAO,CAAC,QAAQ;wBAC7D,CAAC,CAAC,iCAAiC,CAAC,YAAY,OAAO,CAAC,UAAU;wBAClE,CAAC,CAAC,+BAA+B,CAAC,YAAY,OAAO,CAAC,SAAS;wBAC/D,CAAC,CAAC,6BAA6B,CAAC,YAAY,OAAO,CAAC,OAAO;qBAC9D;YACT,CAAC,CAAC,OAAO;;;gBAGL,CAAA;IACd,CAAC;IAEO,IAAI,CAAC,KAAyB,EAAE,KAAa;QACnD,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAA;QACxE,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAA;QAC7E,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;IAEO,YAAY;QAClB,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACpE,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAA;QACxF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;QAC7C,IAAI,IAAI;YAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/F,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAA;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAA;QAChF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,mCAAmC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,8BAA8B,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjJ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;YAAE,OAAM;QACrD,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAA;IAChI,CAAC;;AAjKgB;IAAhB,KAAK,EAAE;;0DAAsB;AACb;IAAhB,KAAK,EAAE;;6DAAyB;AAEhB;IAAhB,KAAK,EAAE;;yDAA0B;AACjB;IAAhB,KAAK,EAAE;;6DAAyB;AACN;IAA1B,KAAK,CAAC,UAAU,CAAC;8BAAiB,SAAS;uDAAA;AAnCjC,wBAAwB;IADpC,aAAa,CAAC,4BAA4B,CAAC;GAC/B,wBAAwB,CAgMpC","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":";AAAA,OAAO,wCAAwC,CAAA;AAC/C,OAAO,iCAAiC,CAAA;AAExC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE/D,OAAO,EAAE,SAAS,EAAe,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9F,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,4BAA4B,CAAA;AAEnF,mGAAmG;AACnG,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,KAAK,WAAW,CAAC,CAAA;AAEzE,MAAM,eAAe,GAAG,wHAAwH,CAAA;AAEhJ;;;;;;GAMG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAyB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAAlE;;QAQL,8DAA8D;QACtD,YAAO,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,GAAG,CAAA,8EAA8E,EAAE;YAC7H,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;SACjD,CAAC,CAAA;QACM,aAAQ,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,8DAA8D,CAAC,CAAA;QAC1G,cAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,wGAAwG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;IAkKrL,CAAC;aA9KQ,WAAM,GAAG,CAAC,qBAAqB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,CAAC,AAAlF,CAAkF;IAc/F,sFAAsF;IACtF,IAAY,cAAc;QACxB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,KAAK,CAAA;QAChF,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACrG,CAAC;IAED,IAAI,OAAO;QACT,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAA;QAClC,OAAO;YACL,KAAK,EAAE,CAAC,CAAC,kCAAkC,CAAC;YAC5C,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE,EAAE;YAC9G,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE;YACtD,GAAG,CAAC,IAAI,CAAC,OAAO;gBACd,CAAC,CAAC;oBACE,OAAO,EAAE;wBACP,EAAE,KAAK,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE;wBACjG,EAAE,KAAK,EAAE,CAAC,CAAC,mCAAmC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE;wBAC7F,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,8BAA8B,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBACnM;oBACD,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kCAAkC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE;iBACzK;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,WAAW;YACrB,CAAC,CAAC,IAAI,CAAA;;;kBAGM,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;oBAChC,IAAI,CAAC,WAAW;0BACV,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;0BAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;kCACpB,IAAI,CAAC,MAAM;;;;;oBAKzB;YACd,CAAC,CAAC,IAAI,CAAA,EAAE,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3G,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC9C,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;IAED,6EAA6E;IACrE,KAAK,CAAC,aAAa;QACzB,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACtH,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,0BAA0B,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IACxK,CAAC;IAEO,QAAQ,CAAC,GAAQ;QACvB,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAA;QAC9B,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE;YAC1B,UAAU,EAAE,GAAG,CAAC,cAAc,IAAI,EAAE;YACpC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE;YAC9B,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;YACpB,KAAK,EAAE,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,KAAK,EAAE;YACtC,OAAO,EAAE,CAAC,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI;YACtE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI;YACtB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC;YACjC,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,EAAE;YACpC,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,KAAK,aAAa;SAC5D,CAAA;IACH,CAAC;IAEO,QAAQ,CAAC,OAAgB;QAC/B,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,MAAc,EAAE,KAAU,EAAE,QAAa,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAChM,OAAO;YACL,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE;YAC7E,UAAU,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE;YACjD,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,6BAA6B,CAAC,EAAE;YACtD,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE;YACjE,OAAO,EAAE;gBACP,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpF,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,GAAG,CAAC;gBAChD,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,2BAA2B,CAAC,EAAE,GAAG,CAAC;gBACzD,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,4BAA4B,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1J,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,sBAAsB,CAAC,EAAE,GAAG,EAAE;oBAC9C,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;oBACzD,4GAA4G;oBAC5G,MAAM,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE;iBAC9I,CAAC;gBACF,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,uBAAuB,CAAC,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACpE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,2BAA2B,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;gBACnF,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,wBAAwB,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,sBAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzL,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,6BAA6B,CAAC,EAAE,GAAG,CAAC;gBAC5D,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,+BAA+B,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aACvF;YACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;SAC7B,CAAA;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,GAAG,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAe;QACpG,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,sBAAsB,CAAA;QACvE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;uBAEO,IAAI;sBACL,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,eAAe;;;;OAIjE;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE;YAClG,WAAW,EAAE,UAAU;YACvB,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;SACjE,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACrD,gFAAgF;QAChF,IAAI,CAAC,aAAa,EAAE,CAAA;QACpB,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAA;IAC/H,CAAC;IAED,mHAAmH;IACnH,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAe;QAC9D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;OAIT;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE;YAC/G,WAAW,EAAE,UAAU;YACvB,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;SACjE,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACrD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,GAAqC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IAClJ,CAAC;IAEO,YAAY;QAClB,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACpE,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAA;QACxF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;QAC7C,IAAI,IAAI;YAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/F,CAAC;IAED,8FAA8F;IACtF,KAAK,CAAC,MAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAClH,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAA;QAC/F,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,8BAA8B,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACxK,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;YAAE,OAAM;QACrD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAC7E,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;YAC3F,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;IACH,CAAC;;AA1KgB;IAAhB,KAAK,EAAE;;yDAA0B;AACjB;IAAhB,KAAK,EAAE;;6DAAyB;AACN;IAA1B,KAAK,CAAC,UAAU,CAAC;8BAAiB,SAAS;IAE5C,8DAA8D;;uDAFlB;AANjC,wBAAwB;IADpC,aAAa,CAAC,4BAA4B,CAAC;GAC/B,wBAAwB,CA+KpC","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;
|