ywana-core8 0.0.571 → 0.0.572

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.
@@ -0,0 +1,680 @@
1
+ import equal from 'deep-equal'
2
+ import React, { Fragment, useContext, useEffect, useMemo, useRef, useState } from 'react'
3
+ import { Accordion, Button, DataTable, DropDown, Header, Icon, MenuIcon, MenuItem, Text } from '../html'
4
+ import { HTTPClient, Session } from '../http'
5
+ import { PageContext, SiteContext } from '../site'
6
+ import { ContentEditor, GroupedContentEditor } from './ContentEditor'
7
+ import { CHECK, Content, TYPES } from './ContentType'
8
+ import { ContentViewer } from './ContentViewer'
9
+ import { CreateContentDialog } from './CreateContentDialog'
10
+ import { QUERY } from './squema'
11
+ import "./TablePage.css"
12
+
13
+ /**
14
+ * Table Page
15
+ */
16
+ export const TablePage2 = (props) => {
17
+
18
+ const site = useContext(SiteContext)
19
+ const { id = "table",
20
+ icon, title, name,
21
+ schema, url, field, host, params = "",
22
+ autosave = true, delay = 1000,
23
+ editable,
24
+ actions = [], dev = false, tableActions, selectionActions = [],
25
+ canFilter = false, canAdd = true, canDelete = true, canEdit = true,
26
+ canQuery = false, urlQuery, user,
27
+ groupBy, validator, scenario,
28
+ formFilter, tableFilter, editorFilter = false,
29
+ tableClassName,
30
+ children
31
+ } = props
32
+
33
+ const [pageContext, setPageContext] = useContext(PageContext)
34
+ const context = TableContext(url, field, host, urlQuery, params)
35
+ const { selected } = pageContext
36
+ const timer = useRef(null)
37
+ const [form, setForm] = useState(selected)
38
+
39
+ useEffect(async () => {
40
+ await context.load()
41
+ if (canQuery) await context.loadQueries(user)
42
+ setPageContext(context)
43
+ }, [])
44
+
45
+ useEffect(() => {
46
+ setForm(selected)
47
+ }, [selected])
48
+
49
+ useEffect(() => {
50
+ if (autosave) {
51
+ clearTimeout(timer.current)
52
+ timer.current = setTimeout(() => {
53
+ if (canSave()) save()
54
+ }, delay)
55
+ }
56
+ }, [form])
57
+
58
+ async function reload() {
59
+ await pageContext.load()
60
+ setPageContext(Object.assign({}, pageContext))
61
+ }
62
+
63
+ function add() {
64
+ const onOK = async (form) => {
65
+ await pageContext.create(form);
66
+ setPageContext(Object.assign({}, pageContext))
67
+ }
68
+ site.openDialog(<CreateContentDialog label={`Crear ${name}`} type={schema} onOK={onOK} validator={validator} filter={formFilter} />);
69
+ }
70
+
71
+ function saveQuery(filters) {
72
+ const onOK = async (form) => {
73
+ form.filters = filters
74
+ form.layout = {}
75
+ form.user = user
76
+ form.url = url
77
+ await pageContext.createQuery(form, user)
78
+ setPageContext(Object.assign({}, pageContext))
79
+ }
80
+ site.openDialog(<CreateContentDialog label="Nueva query" type={QUERY} onOK={onOK} />)
81
+ }
82
+
83
+ function change(next) {
84
+ setForm(next)
85
+ }
86
+
87
+ function canSave() {
88
+ const can = !equal(form, selected)
89
+ return can
90
+ }
91
+
92
+ async function save() {
93
+ await pageContext.update(form)
94
+ setPageContext(Object.assign({}, pageContext))
95
+ }
96
+
97
+ async function playScenario() {
98
+ const promises1 = pageContext.all.map(async item => await pageContext.remove(item.id))
99
+ Promise.all(promises1).then(async () => {
100
+ const promises2 = scenario.map(async (item) => await pageContext.create(item))
101
+ Promise.all(promises2).then(async () => {
102
+ await pageContext.load()
103
+ setPageContext(Object.assign({}, pageContext))
104
+ })
105
+ })
106
+ }
107
+
108
+ function closeAside() {
109
+ pageContext.select(null)
110
+ setPageContext(Object.assign({}, pageContext))
111
+ }
112
+
113
+ function renderAside() {
114
+ const rowSelected = selected && form
115
+ const rowChecked = pageContext.checked && pageContext.checked.size > 0
116
+ if (rowSelected || rowChecked) {
117
+ return (
118
+ <aside className="table-page">
119
+ {rowSelected ? <TableRowEditor content={new Content(schema, form)} filter={editorFilter} onChange={change} onClose={closeAside} editable={canEdit} /> : null}
120
+ {rowChecked ? <TableSelector schema={schema} actions={selectionActions} /> : null}
121
+ </aside>
122
+ )
123
+ }
124
+ return null;
125
+ }
126
+
127
+ function renderActions() {
128
+ return actions.map(element => {
129
+ const action = () => element.props.action(pageContext)
130
+ const clone = React.cloneElement(element, { action })
131
+ return clone
132
+ })
133
+ }
134
+
135
+ return (
136
+ <Fragment>
137
+ <Header className="table-page" title={<Text>{title}</Text>}>
138
+ {canAdd ? <Button icon="add" label="Añadir" action={add} raised /> : null}
139
+ &nbsp;
140
+ <Button icon="refresh" label="Reload" action={reload} />
141
+ {dev ? (
142
+ <MenuIcon align="alignRight">
143
+ <MenuItem label="Cargar Escenario 1" onSelect={playScenario} />
144
+ </MenuIcon>
145
+ ) : null}
146
+ {renderActions()}
147
+ </Header>
148
+ {canQuery || canFilter ? (
149
+ <menu className="table-page">
150
+ {canQuery ? <TableQueries schema={schema} url={url} user={user} /> : null}
151
+ {canFilter ? <TableFilters schema={schema} onSave={canQuery ? saveQuery : null} /> : null}
152
+ </menu>
153
+ ) : null}
154
+ <main key={id} className="table-page">
155
+ <TableEditor icon={icon} title={name} schema={schema} delay={delay} editable={editable} groupBy={groupBy} filter={tableFilter} actions={tableActions} canDelete={canDelete} className={tableClassName} />
156
+ {children ? <article>{children}</article> : null}
157
+ </main>
158
+ {renderAside()}
159
+ </Fragment>
160
+ )
161
+ }
162
+
163
+ /**
164
+ * TableRowEditor
165
+ */
166
+ const TableRowEditor = (props) => {
167
+ const { name, content, filter, editable, onChange, onClose } = props
168
+ return (
169
+ <div className="table-row-editor">
170
+ <Header icon="local_offer" title={name || "Propiedades"}>
171
+ <Icon icon="close" clickable action={onClose} />
172
+ </Header>
173
+ <main>
174
+ {editable ? <ContentEditor content={content} onChange={onChange} filter={filter} /> : <ContentViewer content={content} />}
175
+ </main>
176
+ </div>
177
+ )
178
+ }
179
+
180
+ /**
181
+ * Table Selector
182
+ */
183
+ const TableSelector = (props) => {
184
+
185
+ const { schema, actions = [] } = props
186
+ const [pageContext, setPageContext] = useContext(PageContext)
187
+ const { all, checked } = pageContext
188
+
189
+ const count = `${checked.size}/${all.length}`
190
+
191
+ const columns = Object.keys(schema)
192
+ .filter(key => schema[key].selectorColumn === true)
193
+ .map(key => {
194
+ const field = schema[key]
195
+ return { id: field.id, label: field.label }
196
+ })
197
+
198
+ const rows = all.filter(item => checked.has(item.id))
199
+ const table = {
200
+ columns,
201
+ rows: rows || []
202
+ }
203
+ const buttons = actions.map(({ label, action, validate }) => {
204
+ return <Button
205
+ label={label} raised
206
+ action={() => action(checked, pageContext, async () => {
207
+ await pageContext.load()
208
+ setPageContext(Object.assign({}, pageContext))
209
+ }, rows)}
210
+ disabled={!validate(checked, rows)}
211
+ />
212
+ })
213
+
214
+ return (
215
+ <div className="table-selector">
216
+ <Header icon="checklist_rtl" title="Selección">
217
+ <span className="size">{count}</span>
218
+ </Header>
219
+ <main>
220
+ <DataTable {...table} />
221
+ </main>
222
+ <footer>
223
+ {buttons}
224
+ </footer>
225
+ </div>
226
+ )
227
+ }
228
+
229
+ /**
230
+ * Table Queries
231
+ */
232
+ const TableQueries = (props) => {
233
+
234
+ const [pageContext, setPageContext] = useContext(PageContext)
235
+ const { url, user } = props
236
+ const { queries = [] } = pageContext
237
+ const [selected, setSelected] = useState()
238
+
239
+ function select(query) {
240
+ setSelected(query.id)
241
+ pageContext.changeFilters(query.filters)
242
+ setPageContext(Object.assign({}, pageContext))
243
+ }
244
+
245
+ async function remove(id) {
246
+ await pageContext.removeQuery(id, user)
247
+ setPageContext(Object.assign({}, pageContext))
248
+ }
249
+
250
+ return (
251
+ <Fragment>
252
+ <Header className="table-queries" title={<Text>Queries</Text>} />
253
+ <main className="table-queries">
254
+ {queries ? queries
255
+ .filter(query => query.url === url)
256
+ .map(query => {
257
+ const style = selected === query.id ? "selected" : ""
258
+ return (
259
+ <div className={`table-queries-item ${style}`} onClick={() => select(query)}>
260
+ <Icon icon="star" size="small" />
261
+ <label>{query.name}</label>
262
+ <Icon icon="close" clickable size="small" action={() => remove(query.id)} />
263
+ </div>
264
+ )
265
+ }) : <div>...empty...</div>}
266
+ </main>
267
+ </Fragment>
268
+ )
269
+ }
270
+
271
+ /**
272
+ * Table Filters
273
+ */
274
+ const TableFilters = (props) => {
275
+
276
+ const [pageContext, setPageContext] = useContext(PageContext)
277
+ const { filters } = pageContext
278
+ const { schema, onSave } = props
279
+ const [form, setForm] = useState({})
280
+
281
+ const filterSchema = useMemo(() => {
282
+ const filterSchema = Object.assign({}, schema)
283
+ for (var key in filterSchema) {
284
+ if (filterSchema[key].filter === false) {
285
+ delete filterSchema[key]
286
+ } else {
287
+ if (filterSchema[key].type === TYPES.ENTITY) {
288
+ const fs = filterSchema[key].item
289
+ for (var key in fs) {
290
+ if (fs[key].filter === false) delete fs[key]
291
+ }
292
+ }
293
+ }
294
+ }
295
+ //Object.values(filterSchema).forEach(field => field.section = null)
296
+ delete filterSchema.flows
297
+ return filterSchema
298
+ }, [schema])
299
+
300
+ useEffect(() => {
301
+ if (filters) setForm(filters)
302
+ }, [filters])
303
+
304
+ useEffect(() => {
305
+ reload()
306
+ }, [form])
307
+
308
+ async function change(next) {
309
+ setForm(next)
310
+ }
311
+
312
+ async function reload() {
313
+ await pageContext.load(form)
314
+ setPageContext(Object.assign({}, pageContext))
315
+ }
316
+
317
+ function clear() {
318
+ change({})
319
+ }
320
+
321
+ function save() {
322
+ if (onSave) {
323
+ onSave(form)
324
+ }
325
+ }
326
+
327
+ const content = new Content(filterSchema, form)
328
+ return (
329
+ <Fragment>
330
+ <Header className="table-filters" title={<Text>Filters</Text>} >
331
+ <Icon icon="filter_list_off" size="small" clickable action={clear} />
332
+ {onSave ? <Icon icon="save" size="small" clickable action={save} /> : null}
333
+ </Header>
334
+ <main className="table-filters">
335
+ <ContentEditor content={content} onChange={change} />
336
+ </main>
337
+ </Fragment>
338
+ )
339
+ }
340
+
341
+ /**
342
+ * Table Editor
343
+ */
344
+ export const TableEditor = (props) => {
345
+
346
+ const site = useContext(SiteContext)
347
+ const [pageContext, setPageContext] = useContext(PageContext)
348
+ const { all = [], filters } = pageContext
349
+ const { icon, title, schema, editable, canDelete, filter, actions, className } = props
350
+ const [groupBy, setGroupBy] = useState(props.groupBy)
351
+
352
+ function changeGroup(id, value) {
353
+ setGroupBy(value)
354
+ }
355
+
356
+ async function remove(id) {
357
+ const confirm = await site.confirm("Are you sure ?")
358
+ if (confirm) {
359
+ await pageContext.remove(id)
360
+ pageContext.clear()
361
+ setPageContext(Object.assign({}, pageContext))
362
+ }
363
+ }
364
+
365
+ function change(rowID, cellID, value) {
366
+ const row = all.find(r => r.id === rowID)
367
+ const next = Object.assign({}, row, { [cellID]: value })
368
+ delete next.actions
369
+ pageContext.update(next)
370
+ setPageContext(Object.assign({}, pageContext))
371
+ }
372
+
373
+ function clear() {
374
+ pageContext.clear()
375
+ setPageContext(Object.assign({}, pageContext))
376
+ }
377
+
378
+ async function select(row, event) {
379
+ clear()
380
+ await pageContext.select(row.id)
381
+ setPageContext(Object.assign({}, pageContext))
382
+ }
383
+
384
+ async function check(ids, value) {
385
+ pageContext.check(ids, value)
386
+ setPageContext(Object.assign({}, pageContext))
387
+ }
388
+
389
+ async function checkOne(id, field, value) {
390
+ check([id], value)
391
+ }
392
+
393
+ function run(action, item) {
394
+ action.action(item.id, pageContext, async () => {
395
+ await pageContext.load()
396
+ setPageContext(Object.assign({}, pageContext))
397
+ })
398
+ }
399
+
400
+ function buildGroupOptions(schema) {
401
+ return Object.values(schema)
402
+ .filter(field => field.grouper === true)
403
+ .map(field => ({ label: field.label, value: field.id }))
404
+ }
405
+
406
+ function renderGroupLabel(groupName) {
407
+ const grouper = schema[groupBy]
408
+ if (!groupName || !grouper) return ""
409
+ if (grouper.options) {
410
+ const options = CHECK['isFunction'](grouper.options) ? grouper.options() : grouper.options
411
+ const option = options.find(option => option.value === groupName)
412
+ return option ? option.label : groupName
413
+ } else {
414
+ return groupName
415
+ }
416
+ }
417
+
418
+ const table = useMemo(() => {
419
+ return {
420
+ columns: Object.values(schema).filter(field => field.column === true).map(field => {
421
+ let options = field.options;
422
+ if (options && typeof (options) == 'function') {
423
+ options = options()
424
+ }
425
+ return {
426
+ id: field.id,
427
+ label: field.label,
428
+ type: field.type,
429
+ format: field.format,
430
+ item: field.item ? field.item : [],
431
+ onChange: field.id === "checked" ? checkOne : field.editable ? change : null, /* checked has it´s own handler */
432
+ options
433
+ }
434
+ }),
435
+ rows: []
436
+ }
437
+ }, [schema])
438
+
439
+ const items = filter ? filter(all) : all
440
+
441
+ if (items.length === 0) return (
442
+ <div className='empty-message'>
443
+ <Icon icon="search_off" />
444
+ <Text>No Result Found</Text>
445
+ </div>
446
+ )
447
+
448
+ const groups = items.reduce((groups, item) => {
449
+ const groupName = item[groupBy]
450
+ if (!groups[groupName]) groups[groupName] = []
451
+ groups[groupName].push(item)
452
+ return groups
453
+ }, {})
454
+
455
+ const sections = Object.keys(groups).map(groupName => {
456
+
457
+ const rows = groups[groupName].map(item => {
458
+ item.checked = pageContext.checked ? pageContext.checked.has(item.id) : false
459
+ item.actions = actions ? actions.map(action => {
460
+ return action.filter ?
461
+ action.filter(item) ? <Icon icon={action.icon} clickable size="small" action={() => run(action, item)} /> : null
462
+ : <Icon icon={action.icon} clickable size="small" action={() => run(action, item)} />
463
+ }) : []
464
+ if (canDelete) item.actions.push(<Icon icon="delete" size="small" clickable action={() => remove(item.id)} />)
465
+ return item
466
+ })
467
+
468
+ table.rows = rows
469
+
470
+ return ({
471
+ id: groupName,
472
+ checked: false,
473
+ title: renderGroupLabel(groupName),
474
+ info: groups[groupName].length,
475
+ open: true,
476
+ children: <DataTable {...table} onRowSelection={select} editable={editable} onCheckAll={check} className={className} />
477
+ })
478
+ })
479
+
480
+ return (
481
+ <Fragment>
482
+ <Header icon={icon} title={<Text>{title}</Text>}>
483
+ {groupBy ? <DropDown id="groupBy" label="Agrupar Por" value={groupBy} options={buildGroupOptions(schema)} onChange={changeGroup} /> : null}
484
+ </Header>
485
+ <main className="table-editor">
486
+ <Accordion sections={sections} />
487
+ </main>
488
+ </Fragment>
489
+ )
490
+ }
491
+
492
+ /**
493
+ * Table Context
494
+ */
495
+ const TableContext = (url, field, host, urlQuery, params) => {
496
+
497
+ const API = TableAPI(url, host, params)
498
+
499
+ return {
500
+
501
+ all: [],
502
+ checked: new Set([]),
503
+ selected: null,
504
+ filters: {},
505
+ queries: [],
506
+
507
+ async load(filter) {
508
+ try {
509
+ const filters = filter ? Object.keys(filter).reduce((filters, key) => {
510
+ const field = filter[key];
511
+
512
+ if (field) {
513
+ if (CHECK['isObject'](field)) {
514
+ Object.keys(field).forEach(key2 => {
515
+ const subfield = field[key2]
516
+ if (subfield) filters[`${key}.${key2}`] = subfield
517
+ })
518
+ } else {
519
+ filters[key] = field;
520
+ }
521
+ }
522
+
523
+ return filters;
524
+ }, {}) : []
525
+ const data = await API.all(filters);
526
+ this.all = field ? data[field] : data;
527
+ } catch (error) {
528
+ console.log(error)
529
+ }
530
+ return
531
+ },
532
+
533
+ check(ids, isChecked = true) {
534
+ if (isChecked) {
535
+ ids.forEach(id => this.checked.add(id))
536
+ } else {
537
+ ids.forEach(id => this.checked.delete(id))
538
+ }
539
+ },
540
+
541
+ select(id) {
542
+ const result = this.all.find(item => item.id === id);
543
+ this.selected = result;
544
+ },
545
+
546
+ clear() {
547
+ this.selected = null
548
+ },
549
+
550
+ async create(form) {
551
+ try {
552
+ await API.create(form);
553
+ await this.load();
554
+ } catch (error) {
555
+ console.log(error)
556
+ }
557
+ return
558
+ },
559
+
560
+ async update(form) {
561
+ try {
562
+ await API.update(form)
563
+ await this.load()
564
+ } catch (error) {
565
+ console.log(error)
566
+ }
567
+ return
568
+ },
569
+
570
+ async remove(id) {
571
+ try {
572
+ await API.remove(id)
573
+ await this.load()
574
+ } catch (error) {
575
+ console.log(error)
576
+ }
577
+ return
578
+ },
579
+
580
+ changeFilters(filters) {
581
+ this.filters = filters
582
+ },
583
+
584
+ async loadQueries(user) {
585
+ try {
586
+ this.queries = await API.queries(user, urlQuery)
587
+ } catch (error) {
588
+ console.log(error)
589
+ }
590
+ },
591
+
592
+ async createQuery(query, user) {
593
+ try {
594
+ await API.createQuery(query, urlQuery)
595
+ await this.loadQueries(user)
596
+ } catch (error) {
597
+ console.log(error)
598
+ }
599
+ },
600
+
601
+ async removeQuery(id, user) {
602
+ try {
603
+ await API.removeQuery(id, urlQuery)
604
+ await this.loadQueries(user)
605
+ } catch (error) {
606
+ console.log(error)
607
+ }
608
+ return
609
+ }
610
+
611
+ }
612
+ }
613
+
614
+ /**
615
+ * table API
616
+ */
617
+ const TableAPI = (url, host, params = "") => {
618
+
619
+ const http = HTTPClient(host || window.API || process.env.REACT_APP_API, Session);
620
+
621
+ return {
622
+ all(filters) {
623
+ let queryParams = "?" + params
624
+ if (filters) {
625
+ const filterQuery = Object.keys(filters).reduce((query, key) => {
626
+ const value = filters[key]
627
+ if (typeof (value) === 'boolean') {
628
+ return query.concat(`${key}=${value}&`)
629
+ } else if (Array.isArray(value)) {
630
+ const param = value.length === 0 ? '' : value.reduce((param, item) => {
631
+ param = param.concat(`${key}=${item}&`)
632
+ return param
633
+ }, "")
634
+ return query.concat(param)
635
+ } else {
636
+ return query.concat(`${key}=%${filters[key]}%&`)
637
+ }
638
+ }, "")
639
+ queryParams = queryParams.concat(filterQuery)
640
+ }
641
+ return http.GET(url + queryParams)
642
+ },
643
+
644
+ find(id) {
645
+ return http.GET(`${url}/${id}`)
646
+ },
647
+
648
+ create(form) {
649
+ const body = JSON.stringify(form)
650
+ return http.POST(url, body)
651
+ },
652
+
653
+ update(form) {
654
+ const body = JSON.stringify(form)
655
+ return http.PUT(`${url}/${form.id}`, body)
656
+ },
657
+
658
+ remove(id) {
659
+ return http.DELETE(`${url}/${id}`)
660
+ },
661
+
662
+ queries(user, url2) {
663
+ let url3 = url2 ? url2 : url
664
+ url3 = url3 + "queries"
665
+ if (user) url3 = url3 + `?user=${user}`
666
+ return http.GET(url3)
667
+ },
668
+
669
+ createQuery(form, url2) {
670
+ const url3 = url2 ? url2 : url
671
+ const body = JSON.stringify(form)
672
+ return http.POST(`${url3}queries`, body)
673
+ },
674
+
675
+ removeQuery(id, url2) {
676
+ const url3 = url2 ? url2 : url
677
+ return http.DELETE(`${url3}queries/${id}`)
678
+ },
679
+ }
680
+ }
@@ -5,4 +5,5 @@ export { CreateContentDialog } from './CreateContentDialog'
5
5
  export { EditContentDialog } from './EditContentDialog'
6
6
  export { CollectionPage, CollectionContext, CollectionTree, CollectionFilters } from './CollectionPage'
7
7
  export { TablePage, TableEditor } from './TablePage'
8
+ export { TablePage2 } from './TablePage2'
8
9
  export { TabbedTablePage } from './TabbedTablePage'