@proteinjs/db-ui 1.0.3

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 (60) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/dist/generated/index.d.ts +15 -0
  4. package/dist/generated/index.d.ts.map +1 -0
  5. package/dist/generated/index.js +53 -0
  6. package/dist/generated/index.js.map +1 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +21 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/src/form/RecordForm.d.ts +8 -0
  12. package/dist/src/form/RecordForm.d.ts.map +1 -0
  13. package/dist/src/form/RecordForm.js +232 -0
  14. package/dist/src/form/RecordForm.js.map +1 -0
  15. package/dist/src/form/RecordFormCustomization.d.ts +10 -0
  16. package/dist/src/form/RecordFormCustomization.d.ts.map +1 -0
  17. package/dist/src/form/RecordFormCustomization.js +25 -0
  18. package/dist/src/form/RecordFormCustomization.js.map +1 -0
  19. package/dist/src/form/customizations/MigrationRecordFormCustomization.d.ts +8 -0
  20. package/dist/src/form/customizations/MigrationRecordFormCustomization.d.ts.map +1 -0
  21. package/dist/src/form/customizations/MigrationRecordFormCustomization.js +106 -0
  22. package/dist/src/form/customizations/MigrationRecordFormCustomization.js.map +1 -0
  23. package/dist/src/pages/RecordFormPage.d.ts +5 -0
  24. package/dist/src/pages/RecordFormPage.d.ts.map +1 -0
  25. package/dist/src/pages/RecordFormPage.js +140 -0
  26. package/dist/src/pages/RecordFormPage.js.map +1 -0
  27. package/dist/src/pages/RecordTablePage.d.ts +6 -0
  28. package/dist/src/pages/RecordTablePage.d.ts.map +1 -0
  29. package/dist/src/pages/RecordTablePage.js +77 -0
  30. package/dist/src/pages/RecordTablePage.js.map +1 -0
  31. package/dist/src/pages/TablesPage.d.ts +3 -0
  32. package/dist/src/pages/TablesPage.d.ts.map +1 -0
  33. package/dist/src/pages/TablesPage.js +93 -0
  34. package/dist/src/pages/TablesPage.js.map +1 -0
  35. package/dist/src/pages/UuidGeneratorPage.d.ts +3 -0
  36. package/dist/src/pages/UuidGeneratorPage.d.ts.map +1 -0
  37. package/dist/src/pages/UuidGeneratorPage.js +93 -0
  38. package/dist/src/pages/UuidGeneratorPage.js.map +1 -0
  39. package/dist/src/table/QueryTableLoader.d.ts +10 -0
  40. package/dist/src/table/QueryTableLoader.d.ts.map +1 -0
  41. package/dist/src/table/QueryTableLoader.js +71 -0
  42. package/dist/src/table/QueryTableLoader.js.map +1 -0
  43. package/dist/src/table/RecordTable.d.ts +15 -0
  44. package/dist/src/table/RecordTable.d.ts.map +1 -0
  45. package/dist/src/table/RecordTable.js +138 -0
  46. package/dist/src/table/RecordTable.js.map +1 -0
  47. package/generated/index.ts +49 -0
  48. package/index.ts +4 -0
  49. package/jest.config.js +18 -0
  50. package/package.json +51 -0
  51. package/src/form/RecordForm.tsx +191 -0
  52. package/src/form/RecordFormCustomization.tsx +20 -0
  53. package/src/form/customizations/MigrationRecordFormCustomization.tsx +29 -0
  54. package/src/pages/RecordFormPage.tsx +81 -0
  55. package/src/pages/RecordTablePage.tsx +56 -0
  56. package/src/pages/TablesPage.tsx +48 -0
  57. package/src/pages/UuidGeneratorPage.tsx +37 -0
  58. package/src/table/QueryTableLoader.ts +23 -0
  59. package/src/table/RecordTable.tsx +112 -0
  60. package/tsconfig.json +18 -0
@@ -0,0 +1,56 @@
1
+ import React from 'react'
2
+ import { FormPage, Page, PageComponentProps } from '@proteinjs/ui'
3
+ import { tableByName, Table } from '@proteinjs/db'
4
+ import { RecordTable } from '../table/RecordTable'
5
+
6
+ export const recordTablePage: Page = {
7
+ name: 'Record Table',
8
+ path: 'record/table',
9
+ auth: {
10
+ allUsers: true,
11
+ },
12
+ component: ({...props}) => (
13
+ <FormPage>
14
+ <DynamicRecordTable {...props} />
15
+ </FormPage>
16
+ )
17
+ }
18
+
19
+ export const recordTableLink = (table: Table<any>) => {
20
+ return `/${recordTablePage.path}?name=${table.name}`;
21
+ }
22
+
23
+ export const recordTableLinkByName = (tableName: string) => {
24
+ return `/${recordTablePage.path}?name=${tableName}`;
25
+ }
26
+
27
+ const DynamicRecordTable = ({ urlParams }: PageComponentProps) => {
28
+ function Table() {
29
+ const tableName = urlParams['name'];
30
+ let table;
31
+ let error;
32
+ if (tableName) {
33
+ try {
34
+ table = tableByName(tableName);
35
+ } catch (error) {
36
+ error = `Table not accessible in UI: ${tableName}`;
37
+ }
38
+ } else {
39
+ error = `Table not provided via the 'name' url param`;
40
+ }
41
+
42
+ if (!table) {
43
+ return <div>{ error }</div>;
44
+ }
45
+
46
+ return (
47
+ <RecordTable
48
+ table={table}
49
+ />
50
+ );
51
+ }
52
+
53
+ return (
54
+ <Table />
55
+ );
56
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react'
2
+ import { FormPage, Page, TableLoader, Table as TableComponent, RowWindow } from '@proteinjs/ui'
3
+ import { getTables, getDbService, Table } from '@proteinjs/db'
4
+ import { recordTableLinkByName } from './RecordTablePage'
5
+
6
+ export const tablesPage: Page = {
7
+ name: 'Tables',
8
+ path: 'tables',
9
+ component: () => (
10
+ <FormPage>
11
+ <Tables />
12
+ </FormPage>
13
+ )
14
+ }
15
+
16
+ type TableSummary = {
17
+ name: string,
18
+ rowCount: number,
19
+ }
20
+
21
+ class TableSummaryLoader implements TableLoader<TableSummary> {
22
+ constructor(private tables: Table<any>[]) {}
23
+
24
+ async load(startIndex: number, endIndex: number) {
25
+ const page: RowWindow<TableSummary> = { rows: [], totalCount: this.tables.length };
26
+ const tables = this.tables.slice(startIndex, endIndex);
27
+ const dbService = getDbService();
28
+ for (let table of tables) {
29
+ const rowCount = await dbService.getRowCount(table);
30
+ page.rows.push({ name: table.name, rowCount });
31
+ }
32
+
33
+ return page;
34
+ }
35
+ }
36
+
37
+ const Tables = () => {
38
+ return (
39
+ <TableComponent
40
+ title='Tables'
41
+ columns={['name', 'rowCount']}
42
+ tableLoader={new TableSummaryLoader(getTables())}
43
+ rowOnClickRedirectUrl={async (row: TableSummary) => {
44
+ return recordTableLinkByName(row.name);
45
+ }}
46
+ />
47
+ );
48
+ }
@@ -0,0 +1,37 @@
1
+ import React from 'react'
2
+ import { FormPage, Page, Form, Fields, textField, FormButtons } from '@proteinjs/ui'
3
+ import { v1 as uuidv1 } from 'uuid'
4
+
5
+ export const uuidGeneratorPage: Page = {
6
+ name: 'Uuid Generator',
7
+ path: 'uuid-generator',
8
+ component: () => (
9
+ <FormPage>
10
+ <Form<UuidFields, typeof buttons>
11
+ name='Uuid Generator'
12
+ createFields={() => new UuidFields()}
13
+ fieldLayout={['uuid']}
14
+ buttons={buttons}
15
+ />
16
+ </FormPage>
17
+ )
18
+ }
19
+
20
+ class UuidFields extends Fields {
21
+ uuid = textField<UuidFields>({
22
+ name: 'uuid'
23
+ });
24
+ }
25
+
26
+ const buttons: FormButtons<UuidFields> = {
27
+ generate: {
28
+ name: 'Generate',
29
+ style: {
30
+ color: 'primary',
31
+ variant: 'contained',
32
+ },
33
+ onClick: async (fields: UuidFields, buttons: FormButtons<UuidFields>) => {
34
+ fields.uuid.field.value = uuidv1().split('-').join('');
35
+ },
36
+ },
37
+ };
@@ -0,0 +1,23 @@
1
+ import { Query, QueryBuilderFactory, Record, SortCriteria, Table, getDb } from '@proteinjs/db'
2
+ import { RowWindow, TableLoader } from '@proteinjs/ui'
3
+
4
+ export class QueryTableLoader<T extends Record> implements TableLoader<T> {
5
+ constructor(
6
+ private table: Table<T>,
7
+ private query?: Query<T>,
8
+ private sort?: SortCriteria<T>[]
9
+ ) {}
10
+
11
+ async load(startIndex: number, endIndex: number): Promise<RowWindow<T>> {
12
+ const db = getDb();
13
+ const sort: any = this.sort ? this.sort : [{ field: 'created', desc: true }];
14
+ const qb = new QueryBuilderFactory().getQueryBuilder(this.table, this.query)
15
+ .sort(sort)
16
+ .paginate({ start: startIndex, end: endIndex })
17
+ ;
18
+ const queryPromise = db.query(this.table, qb);
19
+ const rowCountPromise = db.getRowCount(this.table, qb);
20
+ const [rows, totalCount] = await Promise.all([queryPromise, rowCountPromise]);
21
+ return { rows, totalCount };
22
+ }
23
+ }
@@ -0,0 +1,112 @@
1
+ import React from 'react'
2
+ import { Delete, Add } from '@mui/icons-material'
3
+ import S from 'string'
4
+ import { TableButton, Table as TableComponent, TableLoader, TableProps } from '@proteinjs/ui'
5
+ import { Column, QueryBuilderFactory, Record, ReferenceArrayColumn, StringColumn, Table, getDb } from '@proteinjs/db'
6
+ import { QueryTableLoader } from './QueryTableLoader'
7
+ import { newRecordFormLink, recordFormLink } from '../pages/RecordFormPage'
8
+ import { recordTableLink } from '../pages/RecordTablePage'
9
+
10
+ export type RecordTableProps<T extends Record> = {
11
+ table: Table<T>,
12
+ tableLoader?: TableLoader<T>,
13
+ title?: TableProps<T>['title'],
14
+ description?: TableProps<T>['description'],
15
+ columns?: TableProps<T>['columns'],
16
+ defaultRowsPerPage?: TableProps<T>['defaultRowsPerPage'],
17
+ buttons?: TableProps<T>['buttons'],
18
+ rowOnClickRedirectUrl?: TableProps<T>['rowOnClickRedirectUrl'],
19
+ }
20
+
21
+ function deleteButton<T extends Record>(table: Table<T>): TableButton<T> {
22
+ return {
23
+ name: `Delete selected rows`,
24
+ icon: Delete,
25
+ visibility: {
26
+ showWhenRowsSelected: true,
27
+ showWhenNoRowsSelected: false,
28
+ },
29
+ onClick: async (selectedRows, navigate) => {
30
+ const qb = new QueryBuilderFactory().getQueryBuilder(table)
31
+ .condition({ field: 'id', operator: 'IN', value: selectedRows.map(row => row.id) as T[keyof T][] })
32
+ ;
33
+ await getDb().delete(table, qb);
34
+ navigate(recordTableLink(table));
35
+ },
36
+ };
37
+ }
38
+
39
+ function createButton<T extends Record>(table: Table<T>): TableButton<T> {
40
+ return {
41
+ name: `Create ${S(table.name).humanize().s}`,
42
+ icon: Add,
43
+ visibility: {
44
+ showWhenRowsSelected: false,
45
+ showWhenNoRowsSelected: true,
46
+ },
47
+ onClick: async (selectedRows, navigate) => {
48
+ navigate(newRecordFormLink(table.name));
49
+ },
50
+ };
51
+ }
52
+
53
+ export function RecordTable<T extends Record>(props: RecordTableProps<T>) {
54
+ function defaultColumns() {
55
+ const columnProperties: (keyof T)[] = [];
56
+ if ((props.table.columns as any)['name'])
57
+ columnProperties.push('name' as keyof T);
58
+
59
+ for (let columnProperty of Object.keys(props.table.columns)) {
60
+ if (columnProperties.length >= 5)
61
+ break;
62
+
63
+ if (columnProperty == 'name' || columnProperty == 'created' || columnProperty == 'updated')
64
+ continue;
65
+
66
+ const column: Column<T, any> = (props.table.columns as any)[columnProperty];
67
+ if (column.options?.ui?.hidden)
68
+ continue;
69
+
70
+ if ((column instanceof StringColumn && column.maxLength === 'MAX') || column instanceof ReferenceArrayColumn)
71
+ continue;
72
+
73
+ columnProperties.push(columnProperty as keyof T);
74
+ }
75
+
76
+ columnProperties.push('created');
77
+ columnProperties.push('updated');
78
+
79
+ return columnProperties;
80
+ }
81
+
82
+ function defaultTableLoader() {
83
+ return new QueryTableLoader(props.table, undefined, [{ field: 'updated', desc: true }]);
84
+ }
85
+
86
+ async function defaultRowOnClickRedirectUrl(row: T) {
87
+ return recordFormLink(props.table.name, row.id);
88
+ }
89
+
90
+ function buttons() {
91
+ const buttons: TableButton<T>[] = [];
92
+ if (props.buttons)
93
+ buttons.push(...props.buttons);
94
+
95
+ buttons.push(deleteButton(props.table));
96
+ buttons.push(createButton(props.table));
97
+
98
+ return buttons;
99
+ }
100
+
101
+ return (
102
+ <TableComponent
103
+ title={props.title ? props.title : `${S(props.table.name).humanize().toString()} Table`}
104
+ description={props.description}
105
+ columns={props.columns ? props.columns : defaultColumns()}
106
+ tableLoader={props.tableLoader ? props.tableLoader : defaultTableLoader()}
107
+ rowOnClickRedirectUrl={props.rowOnClickRedirectUrl ? props.rowOnClickRedirectUrl : defaultRowOnClickRedirectUrl}
108
+ defaultRowsPerPage={props.defaultRowsPerPage}
109
+ buttons={buttons()}
110
+ />
111
+ );
112
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./",
4
+ "target": "es5",
5
+ "module": "commonjs",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist/",
10
+ "strict": true,
11
+ "noImplicitAny": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "jsx": "react"
17
+ }
18
+ }