ywana-core8 0.0.571 → 0.0.574

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