ywana-core8 0.0.469 → 0.0.472

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