@things-factory/calendar 8.0.0-beta.9 → 8.0.2

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/bootstrap.ts +1 -0
  2. package/client/index.ts +0 -0
  3. package/client/pages/attendee/attendee-importer.ts +87 -0
  4. package/client/pages/attendee/attendee-list-page.ts +324 -0
  5. package/client/pages/calendar/calendar-importer.ts +87 -0
  6. package/client/pages/calendar/calendar-list-page.ts +325 -0
  7. package/client/pages/calendar/calendar-page.ts +128 -0
  8. package/client/pages/event/event-importer.ts +87 -0
  9. package/client/pages/event/event-list-page.ts +324 -0
  10. package/client/route.ts +19 -0
  11. package/client/tsconfig.json +13 -0
  12. package/dist-client/tsconfig.tsbuildinfo +1 -1
  13. package/dist-server/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +7 -7
  15. package/server/controllers/index.ts +0 -0
  16. package/server/index.ts +4 -0
  17. package/server/middlewares/index.ts +3 -0
  18. package/server/migrations/index.ts +9 -0
  19. package/server/routes.ts +28 -0
  20. package/server/service/attendee/attendee-mutation.ts +122 -0
  21. package/server/service/attendee/attendee-query.ts +31 -0
  22. package/server/service/attendee/attendee-type.ts +44 -0
  23. package/server/service/attendee/attendee.ts +37 -0
  24. package/server/service/attendee/index.ts +7 -0
  25. package/server/service/calendar/calendar-mutation.ts +133 -0
  26. package/server/service/calendar/calendar-query.ts +48 -0
  27. package/server/service/calendar/calendar-type.ts +55 -0
  28. package/server/service/calendar/calendar.ts +82 -0
  29. package/server/service/calendar/index.ts +7 -0
  30. package/server/service/event/event-mutation.ts +125 -0
  31. package/server/service/event/event-query.ts +38 -0
  32. package/server/service/event/event-type.ts +61 -0
  33. package/server/service/event/event.ts +85 -0
  34. package/server/service/event/index.ts +7 -0
  35. package/server/service/index.ts +32 -0
  36. package/server/tsconfig.json +10 -0
@@ -0,0 +1 @@
1
+ export default function bootstrap() {}
File without changes
@@ -0,0 +1,87 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/data-grist'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html, LitElement } from 'lit'
6
+ import { property } from 'lit/decorators.js'
7
+
8
+ import { client } from '@operato/graphql'
9
+ import { i18next } from '@operato/i18n'
10
+ import { isMobileDevice } from '@operato/utils'
11
+ import { ButtonContainerStyles } from '@operato/styles'
12
+
13
+ export class AttendeeImporter extends LitElement {
14
+ static styles = [
15
+ ButtonContainerStyles,
16
+ css`
17
+ :host {
18
+ display: flex;
19
+ flex-direction: column;
20
+
21
+ background-color: var(--md-sys-color-surface);
22
+ }
23
+
24
+ ox-grist {
25
+ flex: 1;
26
+ }
27
+ `
28
+ ]
29
+
30
+ @property({ type: Array }) attendees: any[] = []
31
+ @property({ type: Object }) columns = {
32
+ list: { fields: ['name', 'description'] },
33
+ pagination: { infinite: true },
34
+ columns: [
35
+ {
36
+ type: 'string',
37
+ name: 'name',
38
+ header: i18next.t('field.name'),
39
+ width: 150
40
+ },
41
+ {
42
+ type: 'string',
43
+ name: 'description',
44
+ header: i18next.t('field.description'),
45
+ width: 200
46
+ },
47
+ {
48
+ type: 'checkbox',
49
+ name: 'active',
50
+ header: i18next.t('field.active'),
51
+ width: 60
52
+ }
53
+ ]
54
+ }
55
+
56
+ render() {
57
+ return html`
58
+ <ox-grist
59
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
60
+ .config=${this.columns}
61
+ .data=${{
62
+ records: this.attendees
63
+ }}
64
+ ></ox-grist>
65
+
66
+ <div class="footer">
67
+ <div filler></div>
68
+ <button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>
69
+ </div>
70
+ `
71
+ }
72
+
73
+ async save() {
74
+ const response = await client.mutate({
75
+ mutation: gql`
76
+ mutation importAttendees($attendees: [AttendeePatch!]!) {
77
+ importAttendees(attendees: $attendees)
78
+ }
79
+ `,
80
+ variables: { attendees: this.attendees }
81
+ })
82
+
83
+ if (response.errors?.length) return
84
+
85
+ this.dispatchEvent(new CustomEvent('imported'))
86
+ }
87
+ }
@@ -0,0 +1,324 @@
1
+ import '@operato/data-grist'
2
+
3
+ import { CommonButtonStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
4
+ import { PageView, store } from '@operato/shell'
5
+ import { css, html } from 'lit'
6
+ import { customElement, property, query } from 'lit/decorators.js'
7
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements'
8
+ import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
9
+ import { client } from '@operato/graphql'
10
+ import { i18next, localize } from '@operato/i18n'
11
+ import { notify, openPopup } from '@operato/layout'
12
+ import { isMobileDevice } from '@operato/utils'
13
+
14
+ import { connect } from 'pwa-helpers/connect-mixin'
15
+ import gql from 'graphql-tag'
16
+
17
+ import { AttendeeImporter } from './attendee-importer'
18
+
19
+ @customElement('attendee-list-page')
20
+ export class AttendeeListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
21
+ static styles = [
22
+ ScrollbarStyles,
23
+ CommonGristStyles,
24
+ CommonHeaderStyles,
25
+ css`
26
+ :host {
27
+ display: flex;
28
+
29
+ width: 100%;
30
+
31
+ --grid-record-emphasized-background-color: #8b0000;
32
+ --grid-record-emphasized-color: #ff6b6b;
33
+ }
34
+
35
+ ox-grist {
36
+ overflow-y: auto;
37
+ flex: 1;
38
+ }
39
+
40
+ ox-filters-form {
41
+ flex: 1;
42
+ }
43
+ `
44
+ ]
45
+
46
+ static get scopedElements() {
47
+ return {
48
+ 'attendee-importer': AttendeeImporter
49
+ }
50
+ }
51
+
52
+ @property({ type: Object }) gristConfig: any
53
+ @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
54
+
55
+ @query('ox-grist') private grist!: DataGrist
56
+
57
+ get context() {
58
+ return {
59
+ title: i18next.t('title.attendee list'),
60
+ search: {
61
+ handler: (search: string) => {
62
+ this.grist.searchText = search
63
+ },
64
+ value: this.grist?.searchText || '',
65
+ autofocus: true
66
+ },
67
+ filter: {
68
+ handler: () => {
69
+ this.grist.toggleHeadroom()
70
+ }
71
+ },
72
+ help: 'calendar/attendee',
73
+ actions: [
74
+ {
75
+ title: i18next.t('button.save'),
76
+ action: this._updateAttendee.bind(this),
77
+ ...CommonButtonStyles.save
78
+ },
79
+ {
80
+ title: i18next.t('button.delete'),
81
+ action: this._deleteAttendee.bind(this),
82
+ ...CommonButtonStyles.delete
83
+ }
84
+ ],
85
+ exportable: {
86
+ name: i18next.t('title.attendee list'),
87
+ data: this.exportHandler.bind(this)
88
+ },
89
+ importable: {
90
+ handler: this.importHandler.bind(this)
91
+ }
92
+ }
93
+ }
94
+
95
+ render() {
96
+ const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
97
+
98
+ return html`
99
+ <ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
100
+ <div slot="headroom" class="header">
101
+ <div class="filters">
102
+ <ox-filters-form autofocus></ox-filters-form>
103
+
104
+ <div id="modes">
105
+ <md-icon @click=${() => (this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on</md-icon>
106
+ <md-icon @click=${() => (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted</md-icon>
107
+ <md-icon @click=${() => (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps</md-icon>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </ox-grist>
112
+ `
113
+ }
114
+
115
+ async pageInitialized(lifecycle: any) {
116
+ this.gristConfig = {
117
+ list: {
118
+ fields: ['name', 'description'],
119
+ details: ['active', 'updatedAt']
120
+ },
121
+ columns: [
122
+ { type: 'gutter', gutterName: 'sequence' },
123
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
124
+ {
125
+ type: 'string',
126
+ name: 'name',
127
+ header: i18next.t('field.name'),
128
+ record: {
129
+ editable: true
130
+ },
131
+ filter: 'search',
132
+ sortable: true,
133
+ width: 150
134
+ },
135
+ {
136
+ type: 'string',
137
+ name: 'description',
138
+ header: i18next.t('field.description'),
139
+ record: {
140
+ editable: true
141
+ },
142
+ filter: 'search',
143
+ width: 200
144
+ },
145
+ {
146
+ type: 'checkbox',
147
+ name: 'active',
148
+ label: true,
149
+ header: i18next.t('field.active'),
150
+ record: {
151
+ editable: true
152
+ },
153
+ filter: true,
154
+ sortable: true,
155
+ width: 60
156
+ },
157
+ {
158
+ type: 'resource-object',
159
+ name: 'updater',
160
+ header: i18next.t('field.updater'),
161
+ record: {
162
+ editable: false
163
+ },
164
+ sortable: true,
165
+ width: 120
166
+ },
167
+ {
168
+ type: 'datetime',
169
+ name: 'updatedAt',
170
+ header: i18next.t('field.updated_at'),
171
+ record: {
172
+ editable: false
173
+ },
174
+ sortable: true,
175
+ width: 180
176
+ }
177
+ ],
178
+ rows: {
179
+ selectable: {
180
+ multiple: true
181
+ }
182
+ },
183
+ sorters: [
184
+ {
185
+ name: 'name'
186
+ }
187
+ ]
188
+ }
189
+ }
190
+
191
+ async pageUpdated(changes: any, lifecycle: any) {
192
+ if (this.active) {
193
+ // do something here when this page just became as active
194
+ }
195
+ }
196
+
197
+ async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
198
+ const response = await client.query({
199
+ query: gql`
200
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
201
+ responses: attendees(filters: $filters, pagination: $pagination, sortings: $sortings) {
202
+ items {
203
+ id
204
+ name
205
+ description
206
+ active
207
+ updater {
208
+ id
209
+ name
210
+ }
211
+ updatedAt
212
+ }
213
+ total
214
+ }
215
+ }
216
+ `,
217
+ variables: {
218
+ filters,
219
+ pagination: { page, limit },
220
+ sortings
221
+ }
222
+ })
223
+
224
+ return {
225
+ total: response.data.responses.total || 0,
226
+ records: response.data.responses.items || []
227
+ }
228
+ }
229
+
230
+ async _deleteAttendee() {
231
+ if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
232
+ const ids = this.grist.selected.map(record => record.id)
233
+ if (ids && ids.length > 0) {
234
+ const response = await client.mutate({
235
+ mutation: gql`
236
+ mutation ($ids: [String!]!) {
237
+ deleteAttendees(ids: $ids)
238
+ }
239
+ `,
240
+ variables: {
241
+ ids
242
+ }
243
+ })
244
+
245
+ if (!response.errors) {
246
+ this.grist.fetch()
247
+ notify({
248
+ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
249
+ })
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ async _updateAttendee() {
256
+ let patches = this.grist.dirtyRecords
257
+ if (patches && patches.length) {
258
+ patches = patches.map(patch => {
259
+ let patchField: any = patch.id ? { id: patch.id } : {}
260
+ const dirtyFields = patch.__dirtyfields__
261
+ for (let key in dirtyFields) {
262
+ patchField[key] = dirtyFields[key].after
263
+ }
264
+ patchField.cuFlag = patch.__dirty__
265
+
266
+ return patchField
267
+ })
268
+
269
+ const response = await client.mutate({
270
+ mutation: gql`
271
+ mutation ($patches: [AttendeePatch!]!) {
272
+ updateMultipleAttendee(patches: $patches) {
273
+ name
274
+ }
275
+ }
276
+ `,
277
+ variables: {
278
+ patches
279
+ }
280
+ })
281
+
282
+ if (!response.errors) {
283
+ this.grist.fetch()
284
+ }
285
+ }
286
+ }
287
+
288
+ async exportHandler() {
289
+ const exportTargets = this.grist.selected.length ? this.grist.selected : this.grist.dirtyData.records
290
+ const targetFieldSet = new Set(['id', 'name', 'description', 'active'])
291
+
292
+ return exportTargets.map(attendee => {
293
+ let tempObj = {}
294
+ for (const field of targetFieldSet) {
295
+ tempObj[field] = attendee[field]
296
+ }
297
+
298
+ return tempObj
299
+ })
300
+ }
301
+
302
+ async importHandler(records) {
303
+ const popup = openPopup(
304
+ html`
305
+ <attendee-importer
306
+ .attendees=${records}
307
+ @imported=${() => {
308
+ history.back()
309
+ this.grist.fetch()
310
+ }}
311
+ ></attendee-importer>
312
+ `,
313
+ {
314
+ backdrop: true,
315
+ size: 'large',
316
+ title: i18next.t('title.import attendee')
317
+ }
318
+ )
319
+
320
+ popup.onclosed = () => {
321
+ this.grist.fetch()
322
+ }
323
+ }
324
+ }
@@ -0,0 +1,87 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/data-grist'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html, LitElement } from 'lit'
6
+ import { property } from 'lit/decorators.js'
7
+
8
+ import { client } from '@operato/graphql'
9
+ import { i18next } from '@operato/i18n'
10
+ import { isMobileDevice } from '@operato/utils'
11
+ import { CommonHeaderStyles } from '@operato/styles'
12
+
13
+ export class CalendarImporter extends LitElement {
14
+ static styles = [
15
+ CommonHeaderStyles,
16
+ css`
17
+ :host {
18
+ display: flex;
19
+ flex-direction: column;
20
+
21
+ background-color: var(--md-sys-color-surface);
22
+ }
23
+
24
+ ox-grist {
25
+ flex: 1;
26
+ }
27
+ `
28
+ ]
29
+
30
+ @property({ type: Array }) calendars: any[] = []
31
+ @property({ type: Object }) columns = {
32
+ list: { fields: ['name', 'description'] },
33
+ pagination: { infinite: true },
34
+ columns: [
35
+ {
36
+ type: 'string',
37
+ name: 'name',
38
+ header: i18next.t('field.name'),
39
+ width: 150
40
+ },
41
+ {
42
+ type: 'string',
43
+ name: 'description',
44
+ header: i18next.t('field.description'),
45
+ width: 200
46
+ },
47
+ {
48
+ type: 'checkbox',
49
+ name: 'active',
50
+ header: i18next.t('field.active'),
51
+ width: 60
52
+ }
53
+ ]
54
+ }
55
+
56
+ render() {
57
+ return html`
58
+ <ox-grist
59
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
60
+ .config=${this.columns}
61
+ .data=${{
62
+ records: this.calendars
63
+ }}
64
+ ></ox-grist>
65
+
66
+ <div class="footer">
67
+ <div filler></div>
68
+ <button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>
69
+ </div>
70
+ `
71
+ }
72
+
73
+ async save() {
74
+ const response = await client.mutate({
75
+ mutation: gql`
76
+ mutation importCalendars($calendars: [CalendarPatch!]!) {
77
+ importCalendars(calendars: $calendars)
78
+ }
79
+ `,
80
+ variables: { calendars: this.calendars }
81
+ })
82
+
83
+ if (response.errors?.length) return
84
+
85
+ this.dispatchEvent(new CustomEvent('imported'))
86
+ }
87
+ }