@things-factory/organization 7.0.0-alpha.1 → 7.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/client/pages/department/department-list-page.ts +308 -196
  2. package/client/pages/department/department-tree-page.ts +153 -403
  3. package/client/route.ts +4 -4
  4. package/client/types/department.ts +0 -8
  5. package/dist-client/pages/department/department-list-page.d.ts +12 -20
  6. package/dist-client/pages/department/department-list-page.js +308 -182
  7. package/dist-client/pages/department/department-list-page.js.map +1 -1
  8. package/dist-client/pages/department/department-tree-page.d.ts +10 -12
  9. package/dist-client/pages/department/department-tree-page.js +142 -382
  10. package/dist-client/pages/department/department-tree-page.js.map +1 -1
  11. package/dist-client/route.d.ts +1 -1
  12. package/dist-client/route.js +3 -3
  13. package/dist-client/route.js.map +1 -1
  14. package/dist-client/tsconfig.tsbuildinfo +1 -1
  15. package/dist-client/types/department.d.ts +0 -6
  16. package/dist-client/types/department.js +0 -5
  17. package/dist-client/types/department.js.map +1 -1
  18. package/dist-server/service/department/department-history.js +4 -15
  19. package/dist-server/service/department/department-history.js.map +1 -1
  20. package/dist-server/service/department/department-query.js +15 -10
  21. package/dist-server/service/department/department-query.js.map +1 -1
  22. package/dist-server/service/department/department-type.js +0 -8
  23. package/dist-server/service/department/department-type.js.map +1 -1
  24. package/dist-server/service/department/department.js +1 -15
  25. package/dist-server/service/department/department.js.map +1 -1
  26. package/dist-server/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +2 -2
  28. package/server/service/department/department-history.ts +7 -31
  29. package/server/service/department/department-query.ts +13 -10
  30. package/server/service/department/department-type.ts +1 -7
  31. package/server/service/department/department.ts +0 -15
  32. package/translations/en.json +3 -0
  33. package/translations/ja.json +3 -0
  34. package/translations/ko.json +3 -0
  35. package/translations/ms.json +3 -0
  36. package/translations/zh.json +3 -0
@@ -8,6 +8,10 @@ import { customElement, property, query, state } from 'lit/decorators.js'
8
8
  import { ScopedElementsMixin } from '@open-wc/scoped-elements'
9
9
  import { client } from '@operato/graphql'
10
10
  import { i18next, localize } from '@operato/i18n'
11
+ import { isMobileDevice } from '@operato/utils'
12
+ import { DataGrist, FetchOption } from '@operato/data-grist'
13
+ import { notify } from '@operato/layout'
14
+ import { OxPrompt } from '@operato/popup/ox-prompt.js'
11
15
 
12
16
  import { connect } from 'pwa-helpers/connect-mixin'
13
17
  import gql from 'graphql-tag'
@@ -15,10 +19,8 @@ import gql from 'graphql-tag'
15
19
  import { DepartmentImporter } from './department-importer'
16
20
  import { Department } from '../../types/department'
17
21
 
18
- import { DepartmentView } from '../../component/department-view'
19
-
20
22
  const departmentFragment = gql`
21
- fragment Department_department on Department {
23
+ fragment departmentFragment on Department {
22
24
  id
23
25
  controlNo
24
26
  name
@@ -32,7 +34,6 @@ const departmentFragment = gql`
32
34
  email
33
35
  }
34
36
  active
35
- state
36
37
  picture
37
38
 
38
39
  updater {
@@ -46,26 +47,21 @@ const departmentFragment = gql`
46
47
  @customElement('department-list-page')
47
48
  export class DepartmentListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
48
49
  static styles = [
49
- CommonHeaderStyles,
50
50
  ScrollbarStyles,
51
+ CommonHeaderStyles,
51
52
  css`
52
53
  :host {
53
54
  display: flex;
54
- flex-direction: column;
55
55
 
56
56
  width: 100%;
57
- overflow: auto;
58
- }
59
57
 
60
- ox-tree-vertical {
61
- flex: 1;
58
+ --grid-record-emphasized-background-color: #8b0000;
59
+ --grid-record-emphasized-color: #ff6b6b;
62
60
  }
63
61
 
64
- department-view {
62
+ ox-grist {
63
+ overflow-y: auto;
65
64
  flex: 1;
66
-
67
- max-width: 500px;
68
- align-self: center;
69
65
  }
70
66
  `
71
67
  ]
@@ -79,29 +75,27 @@ export class DepartmentListPage extends connect(store)(localize(i18next)(ScopedE
79
75
  @state() root?: Department
80
76
  @state() selected?: Department
81
77
 
82
- @query('department-view') departmentView!: DepartmentView
78
+ @query('ox-grist') private grist!: DataGrist
83
79
 
84
80
  get context() {
85
81
  return {
86
82
  title: i18next.t('title.department list'),
87
83
  help: 'organization/department',
88
84
  actions: [
85
+ // {
86
+ // icon: 'add',
87
+ // title: i18next.t('button.add-child-dept'),
88
+ // action: () => this.grist.addChildNodes()
89
+ // },
90
+ // {
91
+ // icon: 'add',
92
+ // title: i18next.t('button.add-sibling-dept'),
93
+ // action: () => this.grist.addSiblingNodes()
94
+ // },
89
95
  {
90
- icon: 'add',
91
- title: i18next.t('button.add'),
92
- action: this.create.bind(this)
93
- },
94
- this.selected
95
- ? {
96
- icon: 'save',
97
- title: i18next.t('button.save'),
98
- action: this.save.bind(this)
99
- }
100
- : null,
101
- {
102
- icon: 'refresh',
103
- title: i18next.t('button.reset'),
104
- action: this.reset.bind(this)
96
+ icon: 'save',
97
+ title: i18next.t('button.save'),
98
+ action: this.save.bind(this)
105
99
  },
106
100
  {
107
101
  icon: 'delete',
@@ -109,216 +103,334 @@ export class DepartmentListPage extends connect(store)(localize(i18next)(ScopedE
109
103
  action: this.delete.bind(this)
110
104
  }
111
105
  ].filter(Boolean),
112
- exportable: {
113
- name: i18next.t('title.department list'),
114
- data: this.exportHandler.bind(this)
115
- },
116
- importable: {
117
- handler: this.importHandler.bind(this)
118
- },
119
106
  toolbar: false
120
107
  }
121
108
  }
122
109
 
110
+ @property({ type: Object }) gristConfig: any
111
+
123
112
  render() {
113
+ const mode = isMobileDevice() ? 'CARD' : 'GRID'
114
+
124
115
  return html`
125
- <div class="header">
126
- <div class="title">
127
- <mwc-icon>summarize</mwc-icon>
128
- ${i18next.t('title.department list')}
116
+ <ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
117
+ <div slot="headroom" class="header">
118
+ <div class="title">
119
+ <mwc-icon>summarize</mwc-icon>
120
+ ${i18next.t('title.department list')}
121
+ </div>
122
+
123
+ <div class="filters">
124
+ <ox-filters-form class="filter" autofocus without-search></ox-filters-form>
125
+ </div>
126
+
127
+ <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
129
128
  </div>
130
-
131
- <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
132
- </div>
133
-
134
- <ox-tree-vertical
135
- .data=${this.root}
136
- .selected=${this.selected}
137
- @select=${this.onSelect.bind(this)}
138
- label-property="name"
139
- ></ox-tree-vertical>
140
-
141
- <department-view .department=${this.selected}></department-view>
129
+ </ox-grist>
142
130
  `
143
131
  }
144
132
 
145
- onSelect(e: CustomEvent) {
146
- this.selected = e.detail as Department
147
- this.updateContext()
148
- }
149
-
150
- reset() {
151
- this.departmentView.department = {}
152
- }
153
-
154
- async create() {
155
- const { id: parentId } = this.selected || {}
156
- const { controlNo, name, description, state, picture, active, manager } = this.departmentView.department
157
-
158
- var department = {
159
- controlNo,
160
- name,
161
- description,
162
- state,
163
- manager,
164
- active
165
- } as any
166
-
167
- if (picture instanceof File) {
168
- department.picture = picture
169
- }
170
-
171
- if (parentId) {
172
- department.parent = { id: parentId }
173
- }
174
-
175
- const response = await client.mutate({
176
- mutation: gql`
177
- mutation ($department: NewDepartment!) {
178
- createDepartment(department: $department) {
179
- ...Department_department
133
+ async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
134
+ const response = await client.query({
135
+ query: gql`
136
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
137
+ responses: departmentRoots(filters: $filters, pagination: $pagination, sortings: $sortings) {
138
+ total
139
+ items {
140
+ ...departmentFragment
141
+ children(filters: $filters, sortings: $sortings) {
142
+ ...departmentFragment
143
+ children(filters: $filters, sortings: $sortings) {
144
+ ...departmentFragment
145
+ children(filters: $filters, sortings: $sortings) {
146
+ ...departmentFragment
147
+ children(filters: $filters, sortings: $sortings) {
148
+ ...departmentFragment
149
+ children(filters: $filters, sortings: $sortings) {
150
+ ...departmentFragment
151
+ children(filters: $filters, sortings: $sortings) {
152
+ ...departmentFragment
153
+ children(filters: $filters, sortings: $sortings) {
154
+ ...departmentFragment
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
180
163
  }
181
164
  }
182
165
 
183
166
  ${departmentFragment}
184
167
  `,
185
168
  variables: {
186
- department
187
- },
188
- context: {
189
- hasUpload: true
169
+ filters,
170
+ pagination: { page, limit },
171
+ sortings
190
172
  }
191
173
  })
192
174
 
193
- this.selected = response.data.createDepartment
194
- this.updateContext()
195
-
196
- await this.fetch()
197
- }
198
-
199
- async save() {
200
- const { id, controlNo, name, description, state, picture, active, manager } = this.departmentView.department
201
-
202
- if (!id) {
203
- alert('Please select department first.')
204
- }
175
+ const { items: records, total } = response.data.responses
205
176
 
206
- var patch = {
207
- controlNo,
208
- name,
209
- description,
210
- state,
211
- active,
212
- manager
213
- } as any
214
-
215
- if (picture instanceof File) {
216
- patch.picture = picture
177
+ return {
178
+ total,
179
+ records
217
180
  }
181
+ }
218
182
 
219
- const response = await client.mutate({
220
- mutation: gql`
221
- mutation ($id: String!, $patch: DepartmentPatch!) {
222
- updateDepartment(id: $id, patch: $patch) {
223
- ...Department_department
183
+ async pageInitialized(lifecycle: any) {
184
+ this.gristConfig = {
185
+ pagination: { pages: [50, 100, 200] },
186
+ list: {
187
+ thumbnail: 'profile',
188
+ fields: ['controlNo', 'name'],
189
+ details: ['email', 'manager', 'updatedAt']
190
+ },
191
+ columns: [
192
+ {
193
+ type: 'gutter',
194
+ gutterName: 'dirty',
195
+ fixed: true
196
+ },
197
+ {
198
+ type: 'tree',
199
+ name: 'name',
200
+ label: true,
201
+ header: i18next.t('label.name'),
202
+ record: {
203
+ editable: true,
204
+ options: {
205
+ selectable: true
206
+ }
207
+ },
208
+ filter: 'search',
209
+ sortable: true,
210
+ width: 200,
211
+ fixed: true,
212
+ handlers: {
213
+ contextmenu: 'contextmenu-tree-mutation'
224
214
  }
215
+ },
216
+ { name: 'id', hidden: true },
217
+ {
218
+ type: 'string',
219
+ name: 'controlNo',
220
+ header: i18next.t('label.control-no'),
221
+ record: {
222
+ editable: true
223
+ },
224
+ filter: 'search',
225
+ sortable: true,
226
+ width: 110
227
+ },
228
+ {
229
+ type: 'string',
230
+ name: 'description',
231
+ header: i18next.t('label.description'),
232
+ record: {
233
+ editable: true
234
+ },
235
+ filter: 'search',
236
+ sortable: true,
237
+ width: 110
238
+ },
239
+ {
240
+ type: 'resource-object',
241
+ name: 'manager',
242
+ header: i18next.t('label.manager'),
243
+ record: {
244
+ editable: true,
245
+ options: {
246
+ title: i18next.t('title.employee list'),
247
+ queryName: 'employees',
248
+ pagination: { pages: [50, 100, 200] },
249
+ basicArgs: {
250
+ filters: [
251
+ {
252
+ name: 'active',
253
+ operator: 'eq',
254
+ value: true
255
+ }
256
+ ]
257
+ },
258
+ list: { fields: ['controlNo', 'name', 'email'] },
259
+ columns: [
260
+ { name: 'id', hidden: true },
261
+ {
262
+ name: 'controlNo',
263
+ width: 120,
264
+ header: { renderer: () => i18next.t('field.control-no') },
265
+ filter: 'search',
266
+ sortable: true
267
+ },
268
+ {
269
+ name: 'name',
270
+ width: 120,
271
+ header: { renderer: () => i18next.t('field.name') },
272
+ filter: 'search',
273
+ sortable: true
274
+ },
275
+ {
276
+ name: 'email',
277
+ width: 150,
278
+ header: { renderer: () => i18next.t('label.email') },
279
+ filter: 'search',
280
+ sortable: true
281
+ }
282
+ ],
283
+ valueField: 'id',
284
+ nameField: 'name',
285
+ descriptionField: 'controlNo'
286
+ }
287
+ },
288
+ sortable: true,
289
+ width: 120
290
+ },
291
+ {
292
+ type: 'checkbox',
293
+ name: 'active',
294
+ label: true,
295
+ header: i18next.t('field.active'),
296
+ width: 70,
297
+ record: {
298
+ align: 'center',
299
+ editable: true
300
+ },
301
+ filter: true,
302
+ sortable: true
303
+ },
304
+ {
305
+ type: 'resource-object',
306
+ name: 'updater',
307
+ header: i18next.t('field.updater'),
308
+ width: 90,
309
+ sortable: false
310
+ },
311
+ {
312
+ type: 'datetime',
313
+ name: 'updatedAt',
314
+ header: i18next.t('field.updated_at'),
315
+ width: 180,
316
+ sortable: true
317
+ },
318
+ {
319
+ type: 'image',
320
+ name: 'picture',
321
+ record: {
322
+ renderer: function (value, column, record, rowIndex, field) {
323
+ return html`<ox-pfp-view style="height:90%; width: unset; aspect-ratio: 1 / 1;" .profile=${record.picture} .name=${record.name}></ox-pfp-view>`
324
+ }
325
+ },
326
+ hidden: true
327
+ }
328
+ ],
329
+ rows: {
330
+ appendable: false,
331
+ selectable: {
332
+ multiple: true
225
333
  }
226
-
227
- ${departmentFragment}
228
- `,
229
- variables: {
230
- id,
231
- patch
232
334
  },
233
- context: {
234
- hasUpload: true
335
+ sorters: [
336
+ {
337
+ name: 'controlNo'
338
+ }
339
+ ],
340
+ tree: {
341
+ childrenProperty: 'children',
342
+ expanded: () => true
235
343
  }
236
- })
237
-
238
- this.selected = response.data.updateDepartment
344
+ }
345
+ }
239
346
 
240
- this.fetch()
347
+ async pageUpdated(changes: any, lifecycle: any) {
348
+ if (this.active) {
349
+ // do something here when this page just became as active
350
+ }
241
351
  }
242
352
 
243
353
  async delete() {
244
- if (!this.selected) {
245
- alert('select department first.')
354
+ if (!this.grist.selected || this.grist.selected.length == 0) {
355
+ await OxPrompt.open({
356
+ title: 'select department first.',
357
+ confirmButton: { text: i18next.t('button.confirm') }
358
+ })
359
+ return
246
360
  }
247
361
 
248
- const children = this.selected?.children
249
- if (children && children.length > 0) {
250
- alert('Department having children cannot be deleted.')
362
+ if (this.grist.selected.find(selected => selected.children && selected.children.length > 0)) {
363
+ await OxPrompt.open({
364
+ title: 'Department having children cannot be deleted.',
365
+ confirmButton: { text: i18next.t('button.confirm') }
366
+ })
367
+ return
251
368
  }
252
369
 
253
- if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
254
- const { id } = this.selected || {}
255
-
256
- const response = await client.mutate({
257
- mutation: gql`
258
- mutation ($id: String!) {
259
- deleteDepartment(id: $id)
260
- }
261
- `,
262
- variables: {
263
- id
264
- }
370
+ if (
371
+ await OxPrompt.open({
372
+ title: i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }),
373
+ confirmButton: { text: i18next.t('button.confirm') },
374
+ cancelButton: { text: i18next.t('button.cancel') }
265
375
  })
376
+ ) {
377
+ const ids = this.grist.selected.map(record => record.id)
378
+ if (ids && ids.length > 0) {
379
+ const response = await client.mutate({
380
+ mutation: gql`
381
+ mutation ($ids: [String!]!) {
382
+ deleteDepartments(ids: $ids)
383
+ }
384
+ `,
385
+ variables: {
386
+ ids
387
+ }
388
+ })
266
389
 
267
- this.selected = {}
268
- this.updateContext()
269
-
270
- await this.fetch()
390
+ if (!response.errors) {
391
+ this.grist.fetch()
392
+ notify({
393
+ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
394
+ })
395
+ }
396
+ }
271
397
  }
272
398
  }
273
399
 
274
- async pageInitialized(lifecycle: any) {
275
- this.fetch()
276
- }
400
+ async save() {
401
+ let patches = this.grist.dirtyRecords
402
+ if (patches && patches.length) {
403
+ patches = patches.map(patch => {
404
+ let patchField: any = patch.id ? { id: patch.id } : {}
405
+ const dirtyFields = patch.__dirtyfields__
406
+ for (let key in dirtyFields) {
407
+ patchField[key] = dirtyFields[key].after
408
+ }
409
+ patchField.parent = patch.parent
410
+ patchField.cuFlag = patch.__dirty__
277
411
 
278
- async pageUpdated(changes: any, lifecycle: any) {
279
- if (this.active) {
280
- // do something here when this page just became as active
281
- }
282
- }
412
+ return patchField
413
+ })
283
414
 
284
- async fetch() {
285
- const response = await client.query({
286
- query: gql`
287
- query {
288
- responses: departmentRoot {
289
- ...Department_department
290
- children {
291
- ...Department_department
292
- children {
293
- ...Department_department
294
- children {
295
- ...Department_department
296
- children {
297
- ...Department_department
298
- children {
299
- ...Department_department
300
- children {
301
- ...Department_department
302
- children {
303
- ...Department_department
304
- }
305
- }
306
- }
307
- }
308
- }
309
- }
415
+ const response = await client.mutate({
416
+ mutation: gql`
417
+ mutation ($patches: [DepartmentPatch!]!) {
418
+ updateMultipleDepartment(patches: $patches) {
419
+ name
310
420
  }
311
421
  }
422
+ `,
423
+ variables: {
424
+ patches
425
+ },
426
+ context: {
427
+ hasUpload: true
312
428
  }
429
+ })
313
430
 
314
- ${departmentFragment}
315
- `
316
- })
317
-
318
- this.root = response.data.responses
431
+ if (!response.errors) {
432
+ this.grist.fetch()
433
+ }
434
+ }
319
435
  }
320
-
321
- async exportHandler() {}
322
-
323
- async importHandler(records) {}
324
436
  }