@sneat/datagrid 0.1.1
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.
- package/eslint.config.js +7 -0
- package/ng-package.json +7 -0
- package/package.json +14 -0
- package/project.json +38 -0
- package/src/index.ts +2 -0
- package/src/lib/components/cell-popover/cell-popover.component.html +64 -0
- package/src/lib/components/cell-popover/cell-popover.component.spec.ts +168 -0
- package/src/lib/components/cell-popover/cell-popover.component.ts +62 -0
- package/src/lib/components/cell-popover/index.ts +1 -0
- package/src/lib/components/data-grid/data-grid.component.spec.ts +635 -0
- package/src/lib/components/data-grid/data-grid.component.ts +297 -0
- package/src/lib/components/data-grid/index.ts +1 -0
- package/src/lib/components/index.ts +2 -0
- package/src/lib/tabulator/index.ts +1 -0
- package/src/lib/tabulator/tabulator-options.spec.ts +142 -0
- package/src/lib/tabulator/tabulator-options.ts +37 -0
- package/src/test-setup.ts +3 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +19 -0
- package/tsconfig.lib.prod.json +7 -0
- package/tsconfig.spec.json +31 -0
- package/vite.config.mts +10 -0
package/eslint.config.js
ADDED
package/ng-package.json
ADDED
package/package.json
ADDED
package/project.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "datagrid",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"sourceRoot": "libs/datagrid/src",
|
|
6
|
+
"prefix": "sneat",
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/angular:package",
|
|
10
|
+
"outputs": [
|
|
11
|
+
"{workspaceRoot}/dist/libs/datagrid"
|
|
12
|
+
],
|
|
13
|
+
"options": {
|
|
14
|
+
"project": "libs/datagrid/ng-package.json",
|
|
15
|
+
"tsConfig": "libs/datagrid/tsconfig.lib.json"
|
|
16
|
+
},
|
|
17
|
+
"configurations": {
|
|
18
|
+
"production": {
|
|
19
|
+
"tsConfig": "libs/datagrid/tsconfig.lib.prod.json"
|
|
20
|
+
},
|
|
21
|
+
"development": {}
|
|
22
|
+
},
|
|
23
|
+
"defaultConfiguration": "production"
|
|
24
|
+
},
|
|
25
|
+
"test": {
|
|
26
|
+
"executor": "@nx/vitest:test",
|
|
27
|
+
"outputs": [
|
|
28
|
+
"{workspaceRoot}/coverage/libs/datagrid"
|
|
29
|
+
],
|
|
30
|
+
"options": {
|
|
31
|
+
"tsConfig": "libs/datagrid/tsconfig.spec.json"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"lint": {
|
|
35
|
+
"executor": "@nx/eslint:lint"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<ion-card-header>
|
|
2
|
+
<ion-card-title>Field: {{ column?.name }}</ion-card-title>
|
|
3
|
+
<ion-card-subtitle>Value: {{ value }}</ion-card-subtitle>
|
|
4
|
+
</ion-card-header>
|
|
5
|
+
<h3 class="ion-margin">
|
|
6
|
+
References:
|
|
7
|
+
@if (fk?.refTable) {
|
|
8
|
+
<a routerLink="{{ fk?.refTable?.schema }}.{{ fk?.refTable?.name }}">
|
|
9
|
+
{{ fk?.refTable?.schema }}.{{ fk?.refTable?.name }}
|
|
10
|
+
</a>
|
|
11
|
+
}
|
|
12
|
+
</h3>
|
|
13
|
+
<ion-segment [(ngModel)]="tab">
|
|
14
|
+
<ion-segment-button value="rec">
|
|
15
|
+
<ion-label>Record</ion-label>
|
|
16
|
+
</ion-segment-button>
|
|
17
|
+
<ion-segment-button value="cols">
|
|
18
|
+
<ion-label>Columns</ion-label>
|
|
19
|
+
</ion-segment-button>
|
|
20
|
+
<ion-segment-button value="refs">
|
|
21
|
+
<ion-label>References</ion-label>
|
|
22
|
+
</ion-segment-button>
|
|
23
|
+
</ion-segment>
|
|
24
|
+
|
|
25
|
+
@switch (tab) {
|
|
26
|
+
@case ("rec") {
|
|
27
|
+
<ion-list>
|
|
28
|
+
<ion-item>
|
|
29
|
+
<ion-label>Id</ion-label>
|
|
30
|
+
<ion-input
|
|
31
|
+
type="number"
|
|
32
|
+
style="text-align: right"
|
|
33
|
+
value="1"
|
|
34
|
+
readonly="true"
|
|
35
|
+
/>
|
|
36
|
+
</ion-item>
|
|
37
|
+
<ion-item>
|
|
38
|
+
<ion-label>Code</ion-label>
|
|
39
|
+
<ion-input
|
|
40
|
+
type="text"
|
|
41
|
+
style="text-align: right"
|
|
42
|
+
value="Something"
|
|
43
|
+
readonly="true"
|
|
44
|
+
/>
|
|
45
|
+
</ion-item>
|
|
46
|
+
</ion-list>
|
|
47
|
+
}
|
|
48
|
+
@case ("cols") {
|
|
49
|
+
<ion-list>
|
|
50
|
+
<ion-item>
|
|
51
|
+
<ion-label>Id</ion-label>
|
|
52
|
+
<ion-badge slot="end" color="light">
|
|
53
|
+
<ion-text color="medium">INT</ion-text>
|
|
54
|
+
</ion-badge>
|
|
55
|
+
</ion-item>
|
|
56
|
+
<ion-item>
|
|
57
|
+
<ion-label>Code</ion-label>
|
|
58
|
+
<ion-badge slot="end" color="light">
|
|
59
|
+
<ion-text color="medium">NVARCHAR</ion-text>
|
|
60
|
+
</ion-badge>
|
|
61
|
+
</ion-item>
|
|
62
|
+
</ion-list>
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
2
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
import { CellPopoverComponent } from './cell-popover.component';
|
|
5
|
+
|
|
6
|
+
describe('CellPopoverComponent', () => {
|
|
7
|
+
let component: CellPopoverComponent;
|
|
8
|
+
let fixture: ComponentFixture<CellPopoverComponent>;
|
|
9
|
+
|
|
10
|
+
beforeEach(waitForAsync(async () => {
|
|
11
|
+
await TestBed.configureTestingModule({
|
|
12
|
+
imports: [CellPopoverComponent],
|
|
13
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
14
|
+
}).compileComponents();
|
|
15
|
+
|
|
16
|
+
fixture = TestBed.createComponent(CellPopoverComponent);
|
|
17
|
+
component = fixture.componentInstance;
|
|
18
|
+
// Don't call detectChanges here - let individual tests control when it's called
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
it('should create', () => {
|
|
22
|
+
fixture.detectChanges();
|
|
23
|
+
expect(component).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Input Properties', () => {
|
|
27
|
+
it('should accept column input', () => {
|
|
28
|
+
const mockColumn = {
|
|
29
|
+
name: 'testColumn',
|
|
30
|
+
title: 'Test Column',
|
|
31
|
+
dbType: 'varchar',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
component.column = mockColumn;
|
|
35
|
+
expect(component.column).toEqual(mockColumn);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should accept value input', () => {
|
|
39
|
+
const testValue = 'Test Value';
|
|
40
|
+
component.value = testValue;
|
|
41
|
+
expect(component.value).toBe(testValue);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should accept numeric value', () => {
|
|
45
|
+
const testValue = 42;
|
|
46
|
+
component.value = testValue;
|
|
47
|
+
expect(component.value).toBe(testValue);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should accept null value', () => {
|
|
51
|
+
component.value = null;
|
|
52
|
+
expect(component.value).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should accept undefined value', () => {
|
|
56
|
+
component.value = undefined;
|
|
57
|
+
expect(component.value).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should accept foreign key input', () => {
|
|
61
|
+
const mockFk = {
|
|
62
|
+
name: 'fk_user_id',
|
|
63
|
+
columns: ['user_id'],
|
|
64
|
+
refTable: {
|
|
65
|
+
name: 'users',
|
|
66
|
+
schema: 'public',
|
|
67
|
+
catalog: 'main',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
component.fk = mockFk;
|
|
72
|
+
expect(component.fk).toEqual(mockFk);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should accept foreign key without catalog', () => {
|
|
76
|
+
const mockFk = {
|
|
77
|
+
name: 'fk_order_id',
|
|
78
|
+
columns: ['order_id'],
|
|
79
|
+
refTable: {
|
|
80
|
+
name: 'orders',
|
|
81
|
+
schema: 'public',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
component.fk = mockFk;
|
|
86
|
+
expect(component.fk).toEqual(mockFk);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Tab Property', () => {
|
|
91
|
+
it('should default tab to "rec"', () => {
|
|
92
|
+
expect(component.tab).toBe('rec');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should allow changing tab to "cols"', () => {
|
|
96
|
+
component.tab = 'cols';
|
|
97
|
+
expect(component.tab).toBe('cols');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should allow changing tab to "refs"', () => {
|
|
101
|
+
component.tab = 'refs';
|
|
102
|
+
expect(component.tab).toBe('refs');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should allow switching between tabs', () => {
|
|
106
|
+
expect(component.tab).toBe('rec');
|
|
107
|
+
|
|
108
|
+
component.tab = 'cols';
|
|
109
|
+
expect(component.tab).toBe('cols');
|
|
110
|
+
|
|
111
|
+
component.tab = 'refs';
|
|
112
|
+
expect(component.tab).toBe('refs');
|
|
113
|
+
|
|
114
|
+
component.tab = 'rec';
|
|
115
|
+
expect(component.tab).toBe('rec');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('Complex Scenarios', () => {
|
|
120
|
+
it('should handle column with all properties', () => {
|
|
121
|
+
const fullColumn = {
|
|
122
|
+
name: 'user_email',
|
|
123
|
+
title: 'User Email Address',
|
|
124
|
+
dbType: 'varchar(255)',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
component.column = fullColumn;
|
|
128
|
+
component.value = 'user@example.com';
|
|
129
|
+
|
|
130
|
+
expect(component.column.name).toBe('user_email');
|
|
131
|
+
expect(component.column.title).toBe('User Email Address');
|
|
132
|
+
expect(component.column.dbType).toBe('varchar(255)');
|
|
133
|
+
expect(component.value).toBe('user@example.com');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should handle column without title', () => {
|
|
137
|
+
const columnWithoutTitle = {
|
|
138
|
+
name: 'status',
|
|
139
|
+
dbType: 'int',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
component.column = columnWithoutTitle;
|
|
143
|
+
component.value = 1;
|
|
144
|
+
|
|
145
|
+
expect(component.column.title).toBeUndefined();
|
|
146
|
+
expect(component.column.name).toBe('status');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should handle multiple foreign key columns', () => {
|
|
150
|
+
const multiFk = {
|
|
151
|
+
name: 'fk_composite',
|
|
152
|
+
columns: ['user_id', 'account_id', 'tenant_id'],
|
|
153
|
+
refTable: {
|
|
154
|
+
name: 'user_accounts',
|
|
155
|
+
schema: 'auth',
|
|
156
|
+
catalog: 'main',
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
component.fk = multiFk;
|
|
161
|
+
|
|
162
|
+
expect(component.fk.columns.length).toBe(3);
|
|
163
|
+
expect(component.fk.columns).toContain('user_id');
|
|
164
|
+
expect(component.fk.columns).toContain('account_id');
|
|
165
|
+
expect(component.fk.columns).toContain('tenant_id');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { FormsModule } from '@angular/forms';
|
|
3
|
+
import { RouterModule } from '@angular/router';
|
|
4
|
+
import {
|
|
5
|
+
IonBadge,
|
|
6
|
+
IonCardHeader,
|
|
7
|
+
IonCardSubtitle,
|
|
8
|
+
IonCardTitle,
|
|
9
|
+
IonInput,
|
|
10
|
+
IonItem,
|
|
11
|
+
IonLabel,
|
|
12
|
+
IonList,
|
|
13
|
+
IonSegment,
|
|
14
|
+
IonSegmentButton,
|
|
15
|
+
IonText,
|
|
16
|
+
} from '@ionic/angular/standalone';
|
|
17
|
+
|
|
18
|
+
// TODO: Local minimal copies to avoid dependency on @sneat/datatug-main and break circular build deps
|
|
19
|
+
interface IRecordsetColumn {
|
|
20
|
+
name: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
dbType: string; // Using string here to avoid coupling to DbType type from datatug-main
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ITableRef {
|
|
26
|
+
name: string;
|
|
27
|
+
schema: string;
|
|
28
|
+
catalog?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface IForeignKey {
|
|
32
|
+
name: string;
|
|
33
|
+
columns: string[];
|
|
34
|
+
refTable: ITableRef;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'sneat-datatug-cell-popover',
|
|
39
|
+
templateUrl: './cell-popover.component.html',
|
|
40
|
+
imports: [
|
|
41
|
+
RouterModule,
|
|
42
|
+
FormsModule,
|
|
43
|
+
IonCardHeader,
|
|
44
|
+
IonCardTitle,
|
|
45
|
+
IonCardSubtitle,
|
|
46
|
+
IonSegment,
|
|
47
|
+
IonSegmentButton,
|
|
48
|
+
IonLabel,
|
|
49
|
+
IonList,
|
|
50
|
+
IonItem,
|
|
51
|
+
IonInput,
|
|
52
|
+
IonBadge,
|
|
53
|
+
IonText,
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
export class CellPopoverComponent {
|
|
57
|
+
@Input() column?: IRecordsetColumn;
|
|
58
|
+
@Input() value: unknown;
|
|
59
|
+
@Input() fk?: IForeignKey;
|
|
60
|
+
|
|
61
|
+
public tab: 'rec' | 'cols' | 'refs' = 'rec';
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './cell-popover.component';
|