@lblod/ember-rdfa-editor-lblod-plugins 38.3.0 → 39.0.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @lblod/ember-rdfa-editor-lblod-plugins
2
2
 
3
+ ## 39.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#682](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/682) [`370378d`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/370378d9718bf1d757e5026134eecce579280503) Thanks [@elpoelma](https://github.com/elpoelma)! - LMB-plugin: breaking change to the `LMBPluginConfig` interface:
8
+ the `defaultAdminUnit` now expects an object of the shape
9
+
10
+ ```ts
11
+ {
12
+ uri: string;
13
+ label: string;
14
+ }
15
+ ```
16
+
17
+ The administrative unit input field of the `lmb-plugin` `SearchModal` component is now implement through a dynamic `PowerSelect` component rather than a free-text input field.
18
+
19
+ ### Minor Changes
20
+
21
+ - [#682](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/682) [`370378d`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/370378d9718bf1d757e5026134eecce579280503) Thanks [@elpoelma](https://github.com/elpoelma)! - LMB-plugin: improve SPARQL query performance
22
+
23
+ ### Patch Changes
24
+
25
+ - [#679](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/679) [`e08902b`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/e08902b088d4e649abe0f4396341e581d8949fa2) Thanks [@lagartoverde](https://github.com/lagartoverde)! - Fix floating window position on date variable
26
+
3
27
  ## 38.3.0
4
28
 
5
29
  ### Minor Changes
@@ -15,6 +15,10 @@ import { service } from '@ember/service';
15
15
  import IntlService from 'ember-intl/services/intl';
16
16
  import { Person } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/variable-plugin/variables';
17
17
  import { createPersonVariable } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/variable-plugin/actions/create-person-variable';
18
+ import AuButton from '@appuniversum/ember-appuniversum/components/au-button';
19
+ import SearchModal from './search-modal';
20
+ import { on } from '@ember/modifier';
21
+ import t from 'ember-intl/helpers/t';
18
22
 
19
23
  interface Args {
20
24
  controller: SayController;
@@ -27,7 +31,10 @@ export default class LmbPluginInsertComponent extends Component<Args> {
27
31
  AddIcon = AddIcon;
28
32
 
29
33
  get showModal() {
30
- return getLmbModalsPluginState(this.controller.mainEditorState)?.modalOpen;
34
+ return (
35
+ getLmbModalsPluginState(this.controller.mainEditorState)?.modalOpen ??
36
+ false
37
+ );
31
38
  }
32
39
 
33
40
  get controller() {
@@ -66,4 +73,24 @@ export default class LmbPluginInsertComponent extends Component<Args> {
66
73
  view: this.controller.mainEditorView,
67
74
  });
68
75
  }
76
+
77
+ <template>
78
+ <li class='au-c-list__item'>
79
+ <AuButton
80
+ @icon={{this.AddIcon}}
81
+ @iconAlignment='left'
82
+ @skin='link'
83
+ {{on 'click' this.openModal}}
84
+ >
85
+ {{t 'lmb-plugin.insert.title'}}
86
+ </AuButton>
87
+ </li>
88
+
89
+ <SearchModal
90
+ @open={{this.showModal}}
91
+ @closeModal={{this.closeModal}}
92
+ @config={{@config}}
93
+ @onInsert={{this.onInsert}}
94
+ />
95
+ </template>
69
96
  }
@@ -0,0 +1,85 @@
1
+ import type { TOC } from '@ember/component/template-only';
2
+ import AuTable from '@appuniversum/ember-appuniversum/components/au-table';
3
+ import SortableTableHeader from '@lblod/ember-rdfa-editor-lblod-plugins/components/common/sort/sortable-table-header';
4
+ import { SearchSort } from './search-modal';
5
+ import t from 'ember-intl/helpers/t';
6
+ import Electee from '@lblod/ember-rdfa-editor-lblod-plugins/models/electee';
7
+ import AuButton from '@appuniversum/ember-appuniversum/components/au-button';
8
+ import { fn } from '@ember/helper';
9
+ import { on } from '@ember/modifier';
10
+ import AuLoader from '@appuniversum/ember-appuniversum/components/au-loader';
11
+ import { TaskInstance } from 'reactiveweb/ember-concurrency';
12
+
13
+ interface Signature {
14
+ Args: {
15
+ sort: SearchSort;
16
+ setSort: (sort: SearchSort) => void;
17
+ services: TaskInstance<{ results: Electee[]; totalCount: number }>;
18
+ insert: (electee: Electee) => unknown;
19
+ };
20
+ }
21
+
22
+ const List: TOC<Signature> = <template>
23
+ <AuTable>
24
+ <:header>
25
+ <tr class='au-c-data-table__header-title'>
26
+ <th>
27
+ <SortableTableHeader
28
+ @field='fullName'
29
+ @label={{t 'lmb-plugin.modal.fields.name'}}
30
+ @sort={{@sort}}
31
+ @setSort={{@setSort}}
32
+ />
33
+ </th>
34
+ <th>
35
+ <SortableTableHeader
36
+ @field='kandidatenlijst'
37
+ @label={{t 'lmb-plugin.modal.fields.kandidatenlijst'}}
38
+ @sort={{@sort}}
39
+ @setSort={{@setSort}}
40
+ />
41
+ </th>
42
+ <th />
43
+ </tr>
44
+ </:header>
45
+ <:body>
46
+ {{#if @services.isRunning}}
47
+ <tr>
48
+ <td colspan='100%'>
49
+ <AuLoader @centered={{true}}>
50
+ {{t 'common.search.loading'}}
51
+ </AuLoader>
52
+ </td>
53
+ </tr>
54
+ {{else}}
55
+ {{#if @services.value.totalCount}}
56
+ {{#each @services.value.results as |row|}}
57
+ <tr>
58
+ <td>{{row.fullName}}</td>
59
+ <td>
60
+ {{#if row.kandidatenlijst}}
61
+ {{row.kandidatenlijst}}
62
+ {{else}}
63
+ <span class='au-u-italic'>{{t 'common.not-applicable'}}</span>
64
+ {{/if}}
65
+ </td>
66
+ <td class='au-u-text-center'>
67
+ <AuButton {{on 'click' (fn @insert row)}}>
68
+ {{t 'lmb-plugin.modal.insert'}}
69
+ </AuButton>
70
+ </td>
71
+ </tr>
72
+ {{/each}}
73
+ {{else}}
74
+ <tr>
75
+ <td colspan='100%'>
76
+ {{t 'common.search.no-results'}}
77
+ </td>
78
+ </tr>
79
+ {{/if}}
80
+ {{/if}}
81
+ </:body>
82
+ </AuTable>
83
+ </template>;
84
+
85
+ export default List;
@@ -0,0 +1,347 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { assert } from '@ember/debug';
4
+ import { action } from '@ember/object';
5
+ import { restartableTask, timeout } from 'ember-concurrency';
6
+ import { task as trackedTask } from 'reactiveweb/ember-concurrency';
7
+ import { LmbPluginConfig } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/lmb-plugin';
8
+
9
+ import Electee from '@lblod/ember-rdfa-editor-lblod-plugins/models/electee';
10
+ import {
11
+ FetchMandateesArgs,
12
+ fetchAdministrativeUnits,
13
+ fetchElectees,
14
+ } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/lmb-plugin/utils/fetchElectees';
15
+ import {
16
+ BESTUURSPERIODES,
17
+ BestuursperiodeLabel,
18
+ BestuursperiodeURI,
19
+ } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
20
+ import { isSome } from '@lblod/ember-rdfa-editor/utils/_private/option';
21
+ import type { AdministrativeUnit } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/worship-plugin';
22
+ import { localCopy } from 'tracked-toolbox';
23
+ import AuMainContainer from '@appuniversum/ember-appuniversum/components/au-main-container';
24
+ import AuModal from '@appuniversum/ember-appuniversum/components/au-modal';
25
+ import AuHeading from '@appuniversum/ember-appuniversum/components/au-heading';
26
+ import AuLabel from '@appuniversum/ember-appuniversum/components/au-label';
27
+ import AuNativeInput from '../au-native-input';
28
+ import PowerSelect from 'ember-power-select/components/power-select';
29
+ import perform from 'ember-concurrency/helpers/perform';
30
+ import List from './list';
31
+ import pagination from '@lblod/ember-rdfa-editor-lblod-plugins/helpers/pagination';
32
+ import PaginationView from '../pagination/pagination-view';
33
+ import { not } from 'ember-truth-helpers';
34
+ import t from 'ember-intl/helpers/t';
35
+ import AlertLoadError from '../common/search/alert-load-error';
36
+ import { on } from '@ember/modifier';
37
+ export type SearchSort = [keyof Electee, 'ASC' | 'DESC'] | false;
38
+
39
+ interface Signature {
40
+ Args: {
41
+ config: LmbPluginConfig;
42
+ open: boolean;
43
+ closeModal: () => void;
44
+ onInsert: (electee: Electee) => void;
45
+ };
46
+ }
47
+
48
+ interface AdminPeriodOption {
49
+ label: BestuursperiodeLabel;
50
+ uri: BestuursperiodeURI;
51
+ }
52
+
53
+ export default class SearchModal extends Component<Signature> {
54
+ // Display
55
+ @tracked error: unknown;
56
+ @tracked inputSearchText: string | null = null;
57
+ @tracked sort: SearchSort = false;
58
+
59
+ // Pagination
60
+ @tracked pageNumber = 0;
61
+ @tracked pageSize = 20;
62
+ @tracked totalCount = 0;
63
+
64
+ // Admin periods
65
+ @tracked selectedAdminPeriod: AdminPeriodOption;
66
+ adminPeriods: AdminPeriodOption[];
67
+ // Admin units
68
+ @localCopy('args.config.defaultAdminUnit')
69
+ selectedAdministrativeUnit?: AdministrativeUnit;
70
+ // tracks whether the user has just typed a character
71
+ // doesn't need to be reactive
72
+ typing = false;
73
+ constructor(owner: unknown, args: Signature['Args']) {
74
+ super(owner, args);
75
+ this.adminPeriods = Object.entries(BESTUURSPERIODES).map(
76
+ ([key, value]: [BestuursperiodeLabel, BestuursperiodeURI]) => ({
77
+ label: key,
78
+ uri: value,
79
+ }),
80
+ );
81
+ this.selectedAdminPeriod =
82
+ this.adminPeriods.find(
83
+ (entry) =>
84
+ isSome(args.config.defaultPeriod) &&
85
+ entry.label === args.config.defaultPeriod,
86
+ ) ?? this.adminPeriods[this.adminPeriods.length - 1];
87
+ }
88
+
89
+ get config() {
90
+ return this.args.config;
91
+ }
92
+ selectAdminPeriod = (value: AdminPeriodOption) => {
93
+ this.selectedAdminPeriod = value;
94
+ this.pageNumber = 0;
95
+ };
96
+
97
+ @action
98
+ async closeModal() {
99
+ this.typing = false;
100
+ this.inputSearchText = null;
101
+ this.sort = false;
102
+ await this.servicesResource.cancel();
103
+ this.args.closeModal();
104
+ }
105
+
106
+ search = restartableTask(
107
+ async ({
108
+ endpoint,
109
+ searchString,
110
+ page,
111
+ pageSize,
112
+ sort,
113
+ period,
114
+ administrativeUnit,
115
+ }: FetchMandateesArgs) => {
116
+ // debounce, but only when the input fields are being used
117
+ if (this.typing) {
118
+ this.typing = false;
119
+ await timeout(250);
120
+ }
121
+
122
+ if (!this.args.open) {
123
+ return {
124
+ results: [],
125
+ totalCount: 0,
126
+ };
127
+ }
128
+
129
+ const abortController = new AbortController();
130
+
131
+ try {
132
+ const result = await fetchElectees({
133
+ endpoint,
134
+ searchString,
135
+ page,
136
+ pageSize,
137
+ sort,
138
+ period,
139
+ administrativeUnit,
140
+ abortSignal: abortController.signal,
141
+ });
142
+ const { count, electees } = result;
143
+
144
+ return {
145
+ results: electees,
146
+ totalCount: count,
147
+ };
148
+ } catch (err) {
149
+ console.error('Got an error fetching electees', err);
150
+ this.error = err;
151
+ } finally {
152
+ abortController.abort();
153
+ }
154
+ return {
155
+ results: [],
156
+ totalCount: 0,
157
+ };
158
+ },
159
+ );
160
+
161
+ servicesResource = trackedTask<{ results: Electee[]; totalCount: number }>(
162
+ this,
163
+ this.search,
164
+ () => [
165
+ {
166
+ endpoint: this.args.config.endpoint,
167
+ searchString: this.inputSearchText ?? '',
168
+ sort: this.sort,
169
+ page: this.pageNumber,
170
+ pageSize: this.pageSize,
171
+ open: this.args.open,
172
+ period: this.selectedAdminPeriod.uri,
173
+ administrativeUnit: this.selectedAdministrativeUnit,
174
+ } satisfies Partial<FetchMandateesArgs> & { open: boolean },
175
+ ],
176
+ );
177
+
178
+ searchAdministrativeUnits = restartableTask(async (search: string) => {
179
+ await timeout(200);
180
+
181
+ const abortController = new AbortController();
182
+ try {
183
+ const units = await fetchAdministrativeUnits({
184
+ endpoint: this.config.endpoint,
185
+ searchString: search,
186
+ lmbPeriod: this.selectedAdminPeriod.uri,
187
+ abortSignal: abortController.signal,
188
+ });
189
+
190
+ return units;
191
+ } catch (err) {
192
+ // ember-power-select doesn't seem to have a way to display errors.
193
+ console.error(
194
+ 'Error occurred when searching for administrative units',
195
+ err,
196
+ );
197
+ // We just re-throw to keep TS happy, ember-concurrency just swallows it.
198
+ throw err;
199
+ } finally {
200
+ abortController.abort();
201
+ }
202
+ });
203
+
204
+ selectAdministrativeUnit = (administrativeUnit: AdministrativeUnit) => {
205
+ this.selectedAdministrativeUnit = administrativeUnit;
206
+ this.pageNumber = 0;
207
+ };
208
+
209
+ @action
210
+ setSort(sort: SearchSort) {
211
+ this.sort = sort;
212
+ }
213
+ @action
214
+ setInputSearchText(event: InputEvent) {
215
+ assert(
216
+ 'inputSearchText must be bound to an input element',
217
+ event.target instanceof HTMLInputElement,
218
+ );
219
+ this.typing = true;
220
+
221
+ this.inputSearchText = event.target.value;
222
+ this.pageNumber = 0;
223
+ }
224
+ @action
225
+ previousPage() {
226
+ --this.pageNumber;
227
+ }
228
+
229
+ @action
230
+ nextPage() {
231
+ ++this.pageNumber;
232
+ }
233
+ @action
234
+ async onInsert(electee: Electee) {
235
+ this.args.onInsert(electee);
236
+ await this.closeModal();
237
+ }
238
+
239
+ <template>
240
+ <AuModal
241
+ class='worship-modal'
242
+ @modalOpen={{@open}}
243
+ @closeModal={{this.closeModal}}
244
+ @title={{t 'lmb-plugin.modal.title'}}
245
+ @size='large'
246
+ @padding='none'
247
+ as |modal|
248
+ >
249
+ <modal.Body>
250
+ <AuMainContainer class='worship-modal--main-container' as |mc|>
251
+ <mc.sidebar>
252
+ <div class='au-c-sidebar'>
253
+ <div class='au-c-sidebar__content au-u-padding'>
254
+ <AuHeading
255
+ @level='3'
256
+ @skin='4'
257
+ class='au-u-padding-bottom-small'
258
+ >
259
+ {{t 'lmb-plugin.modal.search.title'}}
260
+ </AuHeading>
261
+ <AuLabel class='au-margin-bottom-small' for='searchTerm'>
262
+ {{t 'lmb-plugin.modal.fields.name'}}
263
+ </AuLabel>
264
+ <AuNativeInput
265
+ @type='text'
266
+ @width='block'
267
+ id='searchTerm'
268
+ value={{this.inputSearchText}}
269
+ placeholder={{t 'lmb-plugin.modal.nameSearch.placeholder'}}
270
+ {{on 'input' this.setInputSearchText}}
271
+ />
272
+ <AuLabel class='au-margin-bottom-small' for='periodSelect'>
273
+ {{t 'lmb-plugin.modal.fields.period'}}
274
+ </AuLabel>
275
+ <PowerSelect
276
+ @allowClear={{false}}
277
+ @searchEnabled={{false}}
278
+ @options={{this.adminPeriods}}
279
+ @selected={{this.selectedAdminPeriod}}
280
+ @onChange={{this.selectAdminPeriod}}
281
+ as |period|
282
+ >
283
+ {{period.label}}
284
+ </PowerSelect>
285
+
286
+ <AuLabel class='au-margin-bottom-small' for='searchAdminUnit'>
287
+ {{t 'lmb-plugin.modal.fields.adminUnit'}}
288
+ </AuLabel>
289
+ <PowerSelect
290
+ @allowClear={{true}}
291
+ @searchEnabled={{true}}
292
+ @search={{perform this.searchAdministrativeUnits}}
293
+ @selected={{this.selectedAdministrativeUnit}}
294
+ @onChange={{this.selectAdministrativeUnit}}
295
+ @loadingMessage={{t 'common.search.loading'}}
296
+ @placeholder={{t
297
+ 'lmb-plugin.modal.adminUnitSearch.placeholder'
298
+ }}
299
+ @searchMessage={{t 'common.search.type-to-search'}}
300
+ as |unit|
301
+ >
302
+ {{unit.label}}
303
+ </PowerSelect>
304
+
305
+ </div>
306
+ </div>
307
+ </mc.sidebar>
308
+ <mc.content @scroll={{true}}>
309
+ <div class='worship-modal--list-container'>
310
+ {{#if this.error}}
311
+ <AlertLoadError @error={{this.error}} />
312
+ {{else}}
313
+ <List
314
+ @services={{this.servicesResource}}
315
+ @sort={{this.sort}}
316
+ @setSort={{this.setSort}}
317
+ @insert={{this.onInsert}}
318
+ />
319
+ {{/if}}
320
+ </div>
321
+ {{#if this.servicesResource.value.totalCount}}
322
+ {{#let
323
+ (pagination
324
+ page=this.pageNumber
325
+ pageSize=this.pageSize
326
+ count=this.servicesResource.value.totalCount
327
+ )
328
+ as |pg|
329
+ }}
330
+ <PaginationView
331
+ @totalCount={{pg.count}}
332
+ @rangeStart={{pg.pageStart}}
333
+ @rangeEnd={{pg.pageEnd}}
334
+ @onNextPage={{this.nextPage}}
335
+ @onPreviousPage={{this.previousPage}}
336
+ @isFirstPage={{not pg.hasPreviousPage}}
337
+ @isLastPage={{not pg.hasNextPage}}
338
+ />
339
+ {{/let}}
340
+ {{/if}}
341
+
342
+ </mc.content>
343
+ </AuMainContainer>
344
+ </modal.Body>
345
+ </AuModal>
346
+ </template>
347
+ }
@@ -36,7 +36,7 @@ export default class FloatingDateContainer extends Component<Signature> {
36
36
 
37
37
  return NodeSelection.create(
38
38
  this.editorState.doc,
39
- this.selectedDateNode.pos - 1,
39
+ this.selectedDateNode.pos,
40
40
  );
41
41
  }
42
42
 
@@ -8,11 +8,12 @@ import {
8
8
  EditorView,
9
9
  Command,
10
10
  } from '@lblod/ember-rdfa-editor';
11
+ import type { AdministrativeUnit } from '../worship-plugin';
11
12
 
12
13
  export type LmbPluginConfig = {
13
14
  endpoint: string;
14
15
  defaultPeriod?: BestuursperiodeLabel;
15
- defaultAdminUnit?: string;
16
+ defaultAdminUnit?: AdministrativeUnit;
16
17
  };
17
18
 
18
19
  export function createPersonNode(controller: SayController, person: Person) {
@@ -4,26 +4,30 @@ import { BESTUURSPERIODES } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/c
4
4
  import {
5
5
  executeQuery,
6
6
  sparqlEscapeString,
7
+ sparqlEscapeUri,
7
8
  } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/sparql-helpers';
9
+ import { AdministrativeUnit } from '../../worship-plugin';
8
10
 
9
11
  export type FetchMandateesArgs = {
10
12
  endpoint: string;
11
13
  searchString: string;
12
- adminUnitSearch: string;
14
+ administrativeUnit?: AdministrativeUnit;
13
15
  page: number;
14
16
  pageSize: number;
15
17
  sort: SearchSort;
16
18
  period: (typeof BESTUURSPERIODES)[keyof typeof BESTUURSPERIODES];
19
+ abortSignal?: AbortSignal;
17
20
  };
18
21
 
19
22
  export async function countElectees({
20
23
  endpoint,
21
24
  searchString,
22
- adminUnitSearch,
25
+ administrativeUnit,
23
26
  period,
27
+ abortSignal,
24
28
  }: Pick<
25
29
  FetchMandateesArgs,
26
- 'searchString' | 'endpoint' | 'period' | 'adminUnitSearch'
30
+ 'searchString' | 'endpoint' | 'period' | 'administrativeUnit' | 'abortSignal'
27
31
  >) {
28
32
  const query = /* sparql */ `
29
33
  PREFIX besluit: <http://data.vlaanderen.be/ns/besluit#>
@@ -36,15 +40,22 @@ export async function countElectees({
36
40
  PREFIX org: <http://www.w3.org/ns/org#>
37
41
 
38
42
  SELECT (COUNT(DISTINCT ?person) as ?count) WHERE {
39
- ?person a person:Person;
40
- foaf:familyName ?lastName;
41
- persoon:gebruikteVoornaam ?firstName.
43
+ ?person
44
+ a person:Person;
45
+ foaf:familyName ?lastName;
46
+ persoon:gebruikteVoornaam ?firstName.
47
+
48
+ ?bestuursorgaanIT lmb:heeftBestuursperiode ${sparqlEscapeUri(period)}.
49
+ ${
50
+ administrativeUnit
51
+ ? `?bestuursorgaanIT mandaat:isTijdspecialisatieVan/besluit:bestuurt ${sparqlEscapeUri(administrativeUnit.uri)}.`
52
+ : ''
53
+ }
42
54
 
43
- ?bestuursorgaanIT lmb:heeftBestuursperiode <${period}>;
44
- mandaat:isTijdspecialisatieVan/besluit:bestuurt/skos:prefLabel ?adminUnitName.
45
55
  {
46
56
  ?verkiezing mandaat:steltSamen ?bestuursorgaanIT.
47
57
  ?kandidatenlijst mandaat:behoortTot ?verkiezing.
58
+ ?kandidatenlijst skos:prefLabel ?kandidatenlijstLabel.
48
59
 
49
60
  ?verkiezingsresultaat mandaat:isResultaatVoor ?kandidatenlijst.
50
61
  ?verkiezingsresultaat mandaat:isResultaatVan ?person.
@@ -55,15 +66,33 @@ export async function countElectees({
55
66
  org:holds ?mandaat;
56
67
  mandaat:isBestuurlijkeAliasVan ?person.
57
68
  ?bestuursorgaanIT org:hasPost ?mandaat.
58
- }
59
69
 
60
- ${adminUnitSearch.length ? `FILTER(contains(lcase(?adminUnitName), lcase(${sparqlEscapeString(adminUnitSearch)}) )).` : ''}
61
- ${searchString.length ? `FILTER(contains(lcase(concat(?firstName, " ", ?lastName)), lcase(${sparqlEscapeString(searchString)}) )).` : ''}
70
+ FILTER NOT EXISTS {
71
+ ?_bestuursorgaanIT lmb:heeftBestuursperiode <${period}>.
72
+ ?verkiezing mandaat:steltSamen ?_bestuursorgaanIT.
73
+ ?kandidatenlijst mandaat:behoortTot ?verkiezing.
74
+ ?kandidatenlijst skos:prefLabel ?kandidatenlijstLabel.
75
+
76
+ ?verkiezingsresultaat mandaat:isResultaatVoor ?kandidatenlijst.
77
+ ?verkiezingsresultaat mandaat:isResultaatVan ?person.
78
+ }
79
+ }
80
+
81
+
82
+ ${
83
+ searchString.length
84
+ ? `
85
+ BIND(CONCAT(?firstName, " ", ?lastName) AS ?name)
86
+ FILTER(contains(lcase(?name), lcase(${sparqlEscapeString(searchString)}) )).`
87
+ : ''
88
+ }
89
+
62
90
  }
63
91
  `;
64
92
  const response = await executeQuery({
65
93
  query,
66
94
  endpoint,
95
+ abortSignal,
67
96
  });
68
97
  return Number(response.results.bindings[0].count.value);
69
98
  }
@@ -73,15 +102,17 @@ export async function fetchElectees({
73
102
  page,
74
103
  pageSize,
75
104
  searchString,
76
- adminUnitSearch,
105
+ administrativeUnit,
77
106
  sort,
78
107
  period,
108
+ abortSignal,
79
109
  }: FetchMandateesArgs) {
80
110
  const count = await countElectees({
81
111
  endpoint,
82
112
  searchString,
83
113
  period,
84
- adminUnitSearch,
114
+ administrativeUnit,
115
+ abortSignal,
85
116
  });
86
117
  let sortString = '?lastName ?firstName';
87
118
  if (sort) {
@@ -116,12 +147,15 @@ export async function fetchElectees({
116
147
 
117
148
  SELECT DISTINCT ?person ?firstName ?lastName ?kandidatenlijstLabel WHERE {
118
149
  ?person a person:Person;
119
- foaf:familyName ?lastName;
120
- persoon:gebruikteVoornaam ?firstName.
121
-
150
+ foaf:familyName ?lastName;
151
+ persoon:gebruikteVoornaam ?firstName.
122
152
 
123
- ?bestuursorgaanIT lmb:heeftBestuursperiode <${period}>;
124
- mandaat:isTijdspecialisatieVan/besluit:bestuurt/skos:prefLabel ?adminUnitName.
153
+ ?bestuursorgaanIT lmb:heeftBestuursperiode <${period}>.
154
+ ${
155
+ administrativeUnit
156
+ ? `?bestuursorgaanIT mandaat:isTijdspecialisatieVan/besluit:bestuurt ${sparqlEscapeUri(administrativeUnit.uri)}.`
157
+ : ''
158
+ }
125
159
  {
126
160
  ?verkiezing mandaat:steltSamen ?bestuursorgaanIT.
127
161
  ?kandidatenlijst mandaat:behoortTot ?verkiezing.
@@ -148,8 +182,13 @@ export async function fetchElectees({
148
182
  }
149
183
  }
150
184
 
151
- ${adminUnitSearch.length ? `FILTER(contains(lcase(?adminUnitName), lcase(${sparqlEscapeString(adminUnitSearch)}) )).` : ''}
152
- ${searchString.length ? `FILTER(contains(lcase(concat(?firstName, " ", ?lastName)), lcase(${sparqlEscapeString(searchString)}) )).` : ''}
185
+ ${
186
+ searchString.length
187
+ ? `
188
+ BIND(CONCAT(?firstName, " ", ?lastName) AS ?name)
189
+ FILTER(contains(lcase(?name), lcase(${sparqlEscapeString(searchString)}) )).`
190
+ : ''
191
+ }
153
192
  }
154
193
  ORDER BY ${sortString}
155
194
  LIMIT ${pageSize} OFFSET ${page * pageSize}
@@ -157,7 +196,80 @@ export async function fetchElectees({
157
196
  const response = await executeQuery({
158
197
  query,
159
198
  endpoint,
199
+ abortSignal,
160
200
  });
161
201
  const electees = response.results.bindings.map(Electee.fromBinding);
162
202
  return { electees, count };
163
203
  }
204
+
205
+ type FetchAdministrativeUnitsArgs = {
206
+ endpoint: string;
207
+ searchString?: string;
208
+ lmbPeriod?: (typeof BESTUURSPERIODES)[keyof typeof BESTUURSPERIODES];
209
+ classificationCodes?: string[];
210
+ limit?: number;
211
+ abortSignal?: AbortSignal;
212
+ };
213
+
214
+ /**
215
+ * Function which fetches an alphabetically ordered series of administrative units (the top-level ones, not the tijdspecialisatie ones)
216
+ * Additionally allows to filter by period, to only return administrative units which have a tijdsspecialisatie in the given period.
217
+ */
218
+ export async function fetchAdministrativeUnits({
219
+ endpoint,
220
+ searchString,
221
+ lmbPeriod,
222
+ classificationCodes,
223
+ limit,
224
+ abortSignal,
225
+ }: FetchAdministrativeUnitsArgs) {
226
+ const query = /* sparql */ `
227
+ PREFIX besluit: <http://data.vlaanderen.be/ns/besluit#>
228
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
229
+ PREFIX mandaat: <http://data.vlaanderen.be/ns/mandaat#>
230
+ PREFIX lmb: <http://lblod.data.gift/vocabularies/lmb/>
231
+
232
+ SELECT DISTINCT ?uri ?label WHERE {
233
+ ?uri
234
+ a besluit:Bestuurseenheid;
235
+ skos:prefLabel ?bestuurseenheidLabel;
236
+ besluit:classificatie ?classificatie.
237
+
238
+ ?classificatie skos:prefLabel ?classificatieLabel.
239
+ BIND(CONCAT(?classificatieLabel, " ", ?bestuurseenheidLabel) AS ?label)
240
+
241
+ ${
242
+ classificationCodes?.length
243
+ ? `
244
+ VALUES ?classificatie {
245
+ ${classificationCodes.map(sparqlEscapeUri).join(`\n`)}
246
+ }
247
+ `
248
+ : ''
249
+ }
250
+ ${
251
+ lmbPeriod
252
+ ? `
253
+ ?bestuursorgaanIT mandaat:isTijdspecialisatieVan/besluit:bestuurt ?uri.
254
+ ?bestuursorgaanIT lmb:heeftBestuursperiode <${lmbPeriod}>.
255
+ `
256
+ : ''
257
+ }
258
+ ${searchString?.length ? `FILTER(contains(lcase(?label), lcase(${sparqlEscapeString(searchString)}) )).` : ''}
259
+ }
260
+ ORDER BY ?label
261
+ ${limit ? `LIMIT ${limit}` : ''}
262
+ `;
263
+ const response = await executeQuery({
264
+ query,
265
+ endpoint,
266
+ abortSignal,
267
+ });
268
+ const administrativeUnits = response.results.bindings.map<AdministrativeUnit>(
269
+ (binding) => ({
270
+ uri: binding.uri.value,
271
+ label: binding.label.value,
272
+ }),
273
+ );
274
+ return administrativeUnits;
275
+ }
@@ -75,7 +75,7 @@ function getAsyncSearchActionsGetter(args: LmbPluginConfig) {
75
75
  page: 0,
76
76
  pageSize: SEARCH_PAGE_SIZE,
77
77
  searchString: searchQuery,
78
- adminUnitSearch: args.defaultAdminUnit ?? '',
78
+ administrativeUnit: args.defaultAdminUnit,
79
79
  sort: false,
80
80
  period:
81
81
  args.defaultPeriod ?? Object.values(BESTUURSPERIODES).at(-1) ?? '',
@@ -11,7 +11,7 @@ interface Args {
11
11
  export default class LmbPluginInsertComponent extends Component<Args> {
12
12
  intl: IntlService;
13
13
  AddIcon: import("@ember/component/template-only").TOC<import("@appuniversum/ember-appuniversum/components/icons/add").AddIconSignature>;
14
- get showModal(): boolean | undefined;
14
+ get showModal(): boolean;
15
15
  get controller(): SayController;
16
16
  openModal(): void;
17
17
  closeModal(): void;
@@ -1 +1,17 @@
1
- export {};
1
+ import type { TOC } from '@ember/component/template-only';
2
+ import { SearchSort } from './search-modal';
3
+ import Electee from '@lblod/ember-rdfa-editor-lblod-plugins/models/electee';
4
+ import { TaskInstance } from 'reactiveweb/ember-concurrency';
5
+ interface Signature {
6
+ Args: {
7
+ sort: SearchSort;
8
+ setSort: (sort: SearchSort) => void;
9
+ services: TaskInstance<{
10
+ results: Electee[];
11
+ totalCount: number;
12
+ }>;
13
+ insert: (electee: Electee) => unknown;
14
+ };
15
+ }
16
+ declare const List: TOC<Signature>;
17
+ export default List;
@@ -3,18 +3,21 @@ import { LmbPluginConfig } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/
3
3
  import Electee from '@lblod/ember-rdfa-editor-lblod-plugins/models/electee';
4
4
  import { FetchMandateesArgs } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/lmb-plugin/utils/fetchElectees';
5
5
  import { BestuursperiodeLabel, BestuursperiodeURI } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
6
+ import type { AdministrativeUnit } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/worship-plugin';
6
7
  export type SearchSort = [keyof Electee, 'ASC' | 'DESC'] | false;
7
- interface Args {
8
- config: LmbPluginConfig;
9
- open: boolean;
10
- closeModal: () => void;
11
- onInsert: (electee: Electee) => void;
8
+ interface Signature {
9
+ Args: {
10
+ config: LmbPluginConfig;
11
+ open: boolean;
12
+ closeModal: () => void;
13
+ onInsert: (electee: Electee) => void;
14
+ };
12
15
  }
13
16
  interface AdminPeriodOption {
14
17
  label: BestuursperiodeLabel;
15
18
  uri: BestuursperiodeURI;
16
19
  }
17
- export default class LmbPluginSearchModalComponent extends Component<Args> {
20
+ export default class SearchModal extends Component<Signature> {
18
21
  error: unknown;
19
22
  inputSearchText: string | null;
20
23
  sort: SearchSort;
@@ -23,18 +26,22 @@ export default class LmbPluginSearchModalComponent extends Component<Args> {
23
26
  totalCount: number;
24
27
  selectedAdminPeriod: AdminPeriodOption;
25
28
  adminPeriods: AdminPeriodOption[];
26
- adminUnitSearch: string;
29
+ selectedAdministrativeUnit?: AdministrativeUnit;
27
30
  typing: boolean;
28
- constructor(owner: unknown, args: Args);
31
+ constructor(owner: unknown, args: Signature['Args']);
29
32
  get config(): LmbPluginConfig;
30
33
  selectAdminPeriod: (value: AdminPeriodOption) => void;
31
- setAdminUnitSearch: (event: InputEvent) => void;
32
34
  closeModal(): Promise<void>;
33
- search: import("ember-concurrency").TaskForAsyncTaskFunction<unknown, ({ endpoint, searchString, page, pageSize, sort, period, adminUnitSearch, }: FetchMandateesArgs) => Promise<{
35
+ search: import("ember-concurrency").TaskForAsyncTaskFunction<unknown, ({ endpoint, searchString, page, pageSize, sort, period, administrativeUnit, }: FetchMandateesArgs) => Promise<{
34
36
  results: Electee[];
35
37
  totalCount: number;
36
38
  }>>;
37
- servicesResource: import("reactiveweb/ember-concurrency").TaskInstance<unknown>;
39
+ servicesResource: import("reactiveweb/ember-concurrency").TaskInstance<{
40
+ results: Electee[];
41
+ totalCount: number;
42
+ }>;
43
+ searchAdministrativeUnits: import("ember-concurrency").TaskForAsyncTaskFunction<unknown, (search: string) => Promise<AdministrativeUnit[]>>;
44
+ selectAdministrativeUnit: (administrativeUnit: AdministrativeUnit) => void;
38
45
  setSort(sort: SearchSort): void;
39
46
  setInputSearchText(event: InputEvent): void;
40
47
  previousPage(): void;
@@ -2,10 +2,11 @@ import { SayController } from '@lblod/ember-rdfa-editor';
2
2
  import { BestuursperiodeLabel } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
3
3
  import { Person } from '../variable-plugin/variables';
4
4
  import { ProsePlugin, PluginKey, EditorState, EditorView, Command } from '@lblod/ember-rdfa-editor';
5
+ import type { AdministrativeUnit } from '../worship-plugin';
5
6
  export type LmbPluginConfig = {
6
7
  endpoint: string;
7
8
  defaultPeriod?: BestuursperiodeLabel;
8
- defaultAdminUnit?: string;
9
+ defaultAdminUnit?: AdministrativeUnit;
9
10
  };
10
11
  export declare function createPersonNode(controller: SayController, person: Person): import("prosemirror-model").Node;
11
12
  type PluginState = {
@@ -1,17 +1,33 @@
1
1
  import { SearchSort } from '@lblod/ember-rdfa-editor-lblod-plugins/components/lmb-plugin/search-modal';
2
2
  import Electee from '@lblod/ember-rdfa-editor-lblod-plugins/models/electee';
3
3
  import { BESTUURSPERIODES } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
4
+ import { AdministrativeUnit } from '../../worship-plugin';
4
5
  export type FetchMandateesArgs = {
5
6
  endpoint: string;
6
7
  searchString: string;
7
- adminUnitSearch: string;
8
+ administrativeUnit?: AdministrativeUnit;
8
9
  page: number;
9
10
  pageSize: number;
10
11
  sort: SearchSort;
11
12
  period: (typeof BESTUURSPERIODES)[keyof typeof BESTUURSPERIODES];
13
+ abortSignal?: AbortSignal;
12
14
  };
13
- export declare function countElectees({ endpoint, searchString, adminUnitSearch, period, }: Pick<FetchMandateesArgs, 'searchString' | 'endpoint' | 'period' | 'adminUnitSearch'>): Promise<number>;
14
- export declare function fetchElectees({ endpoint, page, pageSize, searchString, adminUnitSearch, sort, period, }: FetchMandateesArgs): Promise<{
15
+ export declare function countElectees({ endpoint, searchString, administrativeUnit, period, abortSignal, }: Pick<FetchMandateesArgs, 'searchString' | 'endpoint' | 'period' | 'administrativeUnit' | 'abortSignal'>): Promise<number>;
16
+ export declare function fetchElectees({ endpoint, page, pageSize, searchString, administrativeUnit, sort, period, abortSignal, }: FetchMandateesArgs): Promise<{
15
17
  electees: Electee[];
16
18
  count: number;
17
19
  }>;
20
+ type FetchAdministrativeUnitsArgs = {
21
+ endpoint: string;
22
+ searchString?: string;
23
+ lmbPeriod?: (typeof BESTUURSPERIODES)[keyof typeof BESTUURSPERIODES];
24
+ classificationCodes?: string[];
25
+ limit?: number;
26
+ abortSignal?: AbortSignal;
27
+ };
28
+ /**
29
+ * Function which fetches an alphabetically ordered series of administrative units (the top-level ones, not the tijdspecialisatie ones)
30
+ * Additionally allows to filter by period, to only return administrative units which have a tijdsspecialisatie in the given period.
31
+ */
32
+ export declare function fetchAdministrativeUnits({ endpoint, searchString, lmbPeriod, classificationCodes, limit, abortSignal, }: FetchAdministrativeUnitsArgs): Promise<AdministrativeUnit[]>;
33
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lblod/ember-rdfa-editor-lblod-plugins",
3
- "version": "38.3.0",
3
+ "version": "39.0.0",
4
4
  "description": "Ember addon providing lblod specific plugins for the ember-rdfa-editor",
5
5
  "keywords": [
6
6
  "ember-addon",
@@ -7,6 +7,7 @@ common:
7
7
  error-title: Error while loading results
8
8
  error-intro: 'An error occurred while loading results'
9
9
  error-outro: In case this problem keeps occurring, please contact
10
+ type-to-search: Type to search
10
11
  sort:
11
12
  sort: Sort
12
13
  asc: Sort ascending
@@ -7,6 +7,7 @@ common:
7
7
  error-title: Probleem bij het laden
8
8
  error-intro: 'Er is een fout opgetreden tijdens het laden van de resultaten:'
9
9
  error-outro: Moest dit probleem aanhouden, neem contact op met
10
+ type-to-search: Typ om te zoeken
10
11
  sort:
11
12
  sort: Sorteren
12
13
  asc: Oplopend sorteren
@@ -1,18 +0,0 @@
1
- {{! @glint-nocheck: not typesafe yet }}
2
- <li class='au-c-list__item'>
3
- <AuButton
4
- @icon={{this.AddIcon}}
5
- @iconAlignment='left'
6
- @skin='link'
7
- {{on 'click' this.openModal}}
8
- >
9
- {{t 'lmb-plugin.insert.title'}}
10
- </AuButton>
11
- </li>
12
-
13
- <LmbPlugin::SearchModal
14
- @open={{this.showModal}}
15
- @closeModal={{this.closeModal}}
16
- @config={{@config}}
17
- @onInsert={{this.onInsert}}
18
- />
@@ -1,60 +0,0 @@
1
- <AuTable>
2
- <:header>
3
- <tr class='au-c-data-table__header-title'>
4
- <th>
5
- <Common::Sort::SortableTableHeader
6
- @field='fullName'
7
- @label={{t 'lmb-plugin.modal.fields.name'}}
8
- @sort={{@sort}}
9
- @setSort={{@setSort}}
10
- />
11
- </th>
12
- <th>
13
- <Common::Sort::SortableTableHeader
14
- @field='kandidatenlijst'
15
- @label={{t 'lmb-plugin.modal.fields.kandidatenlijst'}}
16
- @sort={{@sort}}
17
- @setSort={{@setSort}}
18
- />
19
- </th>
20
- <th />
21
- </tr>
22
- </:header>
23
- <:body>
24
- {{#if @services.isRunning}}
25
- <tr>
26
- <td colspan='100%'>
27
- <AuLoader @centered={{true}}>
28
- {{t 'common.search.loading'}}
29
- </AuLoader>
30
- </td>
31
- </tr>
32
- {{else}}
33
- {{#if @services.value.totalCount}}
34
- {{#each @services.value.results as |row|}}
35
- <tr>
36
- <td>{{row.fullName}}</td>
37
- <td>
38
- {{#if row.kandidatenlijst}}
39
- {{row.kandidatenlijst}}
40
- {{else}}
41
- <span class='au-u-italic'>{{t 'common.not-applicable'}}</span>
42
- {{/if}}
43
- </td>
44
- <td class='au-u-text-center'>
45
- <AuButton {{on 'click' (fn @insert row)}}>
46
- {{t 'lmb-plugin.modal.insert'}}
47
- </AuButton>
48
- </td>
49
- </tr>
50
- {{/each}}
51
- {{else}}
52
- <tr>
53
- <td colspan='100%'>
54
- {{t 'common.search.no-results'}}
55
- </td>
56
- </tr>
57
- {{/if}}
58
- {{/if}}
59
- </:body>
60
- </AuTable>
@@ -1,100 +0,0 @@
1
- {{! @glint-nocheck: not typesafe yet }}
2
- <AuModal
3
- class='worship-modal'
4
- @modalOpen={{@open}}
5
- @closeModal={{this.closeModal}}
6
- @title={{t 'lmb-plugin.modal.title'}}
7
- @size='large'
8
- @padding='none'
9
- as |modal|
10
- >
11
- <modal.Body>
12
- <AuMainContainer
13
- @scroll={{true}}
14
- class='worship-modal--main-container'
15
- as |mc|
16
- >
17
- <mc.sidebar>
18
- <div class='au-c-sidebar'>
19
- <div class='au-c-sidebar__content au-u-padding'>
20
- <AuHeading @level='3' @skin='4' class='au-u-padding-bottom-small'>
21
- {{t 'lmb-plugin.modal.search.title'}}
22
- </AuHeading>
23
- <AuLabel class='au-margin-bottom-small' for='searchTerm'>
24
- {{t 'lmb-plugin.modal.fields.name'}}
25
- </AuLabel>
26
- <AuNativeInput
27
- @type='text'
28
- @width='block'
29
- id='searchTerm'
30
- value={{this.searchText}}
31
- placeholder={{t 'lmb-plugin.modal.nameSearch.placeholder'}}
32
- {{on 'input' this.setInputSearchText}}
33
- />
34
- <AuLabel class='au-margin-bottom-small' for='periodSelect'>
35
- {{t 'lmb-plugin.modal.fields.period'}}
36
- </AuLabel>
37
- <PowerSelect
38
- @allowClear={{false}}
39
- @searchEnabled={{false}}
40
- @options={{this.adminPeriods}}
41
- @selected={{this.selectedAdminPeriod}}
42
- @onChange={{this.selectAdminPeriod}}
43
- as |period|
44
- >
45
- {{period.label}}
46
- </PowerSelect>
47
-
48
- <AuLabel class='au-margin-bottom-small' for='searchAdminUnit'>
49
- {{t 'lmb-plugin.modal.fields.adminUnit'}}
50
- </AuLabel>
51
- <AuNativeInput
52
- @type='text'
53
- @width='block'
54
- id='searchAdminUnit'
55
- value={{this.adminUnitSearch}}
56
- placeholder={{t 'lmb-plugin.modal.adminUnitSearch.placeholder'}}
57
- {{on 'input' this.setAdminUnitSearch}}
58
- />
59
-
60
- </div>
61
- </div>
62
- </mc.sidebar>
63
- <mc.content @scroll={{true}}>
64
- <div class='worship-modal--list-container'>
65
- {{#if this.error}}
66
- <Common::Search::AlertLoadError @error={{this.error}} />
67
- {{else}}
68
- <LmbPlugin::List
69
- @services={{this.servicesResource}}
70
- @sort={{this.sort}}
71
- @setSort={{this.setSort}}
72
- @insert={{this.onInsert}}
73
- />
74
- {{/if}}
75
- </div>
76
- {{#if this.servicesResource.value.totalCount}}
77
- {{#let
78
- (pagination
79
- page=this.pageNumber
80
- pageSize=this.pageSize
81
- count=this.servicesResource.value.totalCount
82
- )
83
- as |pg|
84
- }}
85
- <Pagination::PaginationView
86
- @totalCount={{pg.count}}
87
- @rangeStart={{pg.pageStart}}
88
- @rangeEnd={{pg.pageEnd}}
89
- @onNextPage={{this.nextPage}}
90
- @onPreviousPage={{this.previousPage}}
91
- @isFirstPage={{not pg.hasPreviousPage}}
92
- @isLastPage={{not pg.hasNextPage}}
93
- />
94
- {{/let}}
95
- {{/if}}
96
-
97
- </mc.content>
98
- </AuMainContainer>
99
- </modal.Body>
100
- </AuModal>
@@ -1,189 +0,0 @@
1
- import Component from '@glimmer/component';
2
- import { tracked } from '@glimmer/tracking';
3
- import { assert } from '@ember/debug';
4
- import { action } from '@ember/object';
5
- import { restartableTask, timeout } from 'ember-concurrency';
6
- import { task as trackedTask } from 'reactiveweb/ember-concurrency';
7
- import { LmbPluginConfig } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/lmb-plugin';
8
-
9
- import Electee from '@lblod/ember-rdfa-editor-lblod-plugins/models/electee';
10
- import {
11
- FetchMandateesArgs,
12
- fetchElectees,
13
- } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/lmb-plugin/utils/fetchElectees';
14
- import {
15
- BESTUURSPERIODES,
16
- BestuursperiodeLabel,
17
- BestuursperiodeURI,
18
- } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
19
- import { isSome } from '@lblod/ember-rdfa-editor/utils/_private/option';
20
- export type SearchSort = [keyof Electee, 'ASC' | 'DESC'] | false;
21
-
22
- interface Args {
23
- config: LmbPluginConfig;
24
- open: boolean;
25
- closeModal: () => void;
26
- onInsert: (electee: Electee) => void;
27
- }
28
- interface AdminPeriodOption {
29
- label: BestuursperiodeLabel;
30
- uri: BestuursperiodeURI;
31
- }
32
-
33
- export default class LmbPluginSearchModalComponent extends Component<Args> {
34
- // Display
35
- @tracked error: unknown;
36
- @tracked inputSearchText: string | null = null;
37
- @tracked sort: SearchSort = false;
38
-
39
- // Pagination
40
- @tracked pageNumber = 0;
41
- @tracked pageSize = 20;
42
- @tracked totalCount = 0;
43
-
44
- // Admin periods
45
- @tracked selectedAdminPeriod: AdminPeriodOption;
46
- adminPeriods: AdminPeriodOption[];
47
- // Admin units
48
- @tracked adminUnitSearch: string;
49
- // tracks whether the user has just typed a character
50
- // doesn't need to be reactive
51
- typing = false;
52
- constructor(owner: unknown, args: Args) {
53
- super(owner, args);
54
- this.adminPeriods = Object.entries(BESTUURSPERIODES).map(
55
- ([key, value]: [BestuursperiodeLabel, BestuursperiodeURI]) => ({
56
- label: key,
57
- uri: value,
58
- }),
59
- );
60
- this.selectedAdminPeriod =
61
- this.adminPeriods.find(
62
- (entry) =>
63
- isSome(args.config.defaultPeriod) &&
64
- entry.label === args.config.defaultPeriod,
65
- ) ?? this.adminPeriods[this.adminPeriods.length - 1];
66
- this.adminUnitSearch = args.config.defaultAdminUnit ?? '';
67
- }
68
-
69
- get config() {
70
- return this.args.config;
71
- }
72
- selectAdminPeriod = (value: AdminPeriodOption) => {
73
- this.selectedAdminPeriod = value;
74
- this.pageNumber = 0;
75
- };
76
- setAdminUnitSearch = (event: InputEvent) => {
77
- this.typing = true;
78
- assert(
79
- 'setAdminUnitSearch must be bound to an input element',
80
- event.target instanceof HTMLInputElement,
81
- );
82
- this.adminUnitSearch = event.target.value;
83
- this.pageNumber = 0;
84
- };
85
-
86
- @action
87
- async closeModal() {
88
- this.typing = false;
89
- this.inputSearchText = null;
90
- this.sort = false;
91
- await this.servicesResource.cancel();
92
- this.args.closeModal();
93
- }
94
-
95
- // TODO Either make this a trackedFunction or do filtering on the query and correctly pass an
96
- // AbortController
97
- search = restartableTask(
98
- async ({
99
- endpoint,
100
- searchString,
101
- page,
102
- pageSize,
103
- sort,
104
- period,
105
- adminUnitSearch,
106
- }: FetchMandateesArgs) => {
107
- // debounce, but only when the input fields are being used
108
- if (this.typing) {
109
- this.typing = false;
110
- await timeout(250);
111
- }
112
-
113
- if (!this.args.open) {
114
- return {
115
- results: [],
116
- totalCount: 0,
117
- };
118
- }
119
-
120
- try {
121
- const result = await fetchElectees({
122
- endpoint,
123
- searchString,
124
- page,
125
- pageSize,
126
- sort,
127
- period,
128
- adminUnitSearch,
129
- });
130
- const { count, electees } = result;
131
-
132
- return {
133
- results: electees,
134
- totalCount: count,
135
- };
136
- } catch (err) {
137
- console.error('Got an error fetching electees', err);
138
- this.error = err;
139
- }
140
- return {
141
- results: [],
142
- totalCount: 0,
143
- };
144
- },
145
- );
146
-
147
- servicesResource = trackedTask(this, this.search, () => [
148
- {
149
- endpoint: this.args.config.endpoint,
150
- searchString: this.inputSearchText ?? '',
151
- sort: this.sort,
152
- page: this.pageNumber,
153
- pageSize: this.pageSize,
154
- open: this.args.open,
155
- period: this.selectedAdminPeriod.uri,
156
- adminUnitSearch: this.adminUnitSearch,
157
- } satisfies Partial<FetchMandateesArgs> & { open: boolean },
158
- ]);
159
-
160
- @action
161
- setSort(sort: SearchSort) {
162
- this.sort = sort;
163
- }
164
- @action
165
- setInputSearchText(event: InputEvent) {
166
- assert(
167
- 'inputSearchText must be bound to an input element',
168
- event.target instanceof HTMLInputElement,
169
- );
170
- this.typing = true;
171
-
172
- this.inputSearchText = event.target.value;
173
- this.pageNumber = 0;
174
- }
175
- @action
176
- previousPage() {
177
- --this.pageNumber;
178
- }
179
-
180
- @action
181
- nextPage() {
182
- ++this.pageNumber;
183
- }
184
- @action
185
- async onInsert(electee: Electee) {
186
- this.args.onInsert(electee);
187
- await this.closeModal();
188
- }
189
- }