Skip to content

Commit 85a15cd

Browse files
authored
feat: ag-grid table implementation
* Add ag-grid library * Add ag-grid stellar CSS theme * Create DataGrid component using ag-grid * Create DataGridActions component for common custom actions across tables * Use DataGrid for ActivityTable component * Use DataGrid for Constraints component table * Use DataGrid for SchedulingGoals component table
1 parent 020d711 commit 85a15cd

13 files changed

+735
-80
lines changed

e2e-tests/fixtures/Constraints.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ export class Constraints {
110110
this.inputConstraintSummary = page.locator('textarea[name="constraint-summary"]');
111111
this.page = page;
112112
this.saveButton = page.locator(`button:has-text("Save")`);
113-
this.tableRow = page.locator(`tr:has-text("${this.constraintName}")`);
113+
this.tableRow = page.locator(`.ag-row:has-text("${this.constraintName}")`);
114114
this.tableRowDeleteButton = page.locator(
115-
`tr:has-text("${this.constraintName}") >> button[aria-label="Delete Constraint"]`,
115+
`.ag-row:has-text("${this.constraintName}") >> button[aria-label="Delete Constraint"]`,
116116
);
117117
}
118118
}

e2e-tests/fixtures/SchedulingGoals.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ export class SchedulingGoals {
107107
this.newButton = page.locator(`button:has-text("New")`);
108108
this.page = page;
109109
this.saveButton = page.locator(`button:has-text("Save")`);
110-
this.tableRow = page.locator(`tr:has-text("${this.goalName}")`);
111-
this.tableRowDeleteButton = page.locator(`tr:has-text("${this.goalName}") >> button[aria-label="Delete Goal"]`);
110+
this.tableRow = page.locator(`.ag-row:has-text("${this.goalName}")`);
111+
this.tableRowDeleteButton = page.locator(
112+
`.ag-row:has-text("${this.goalName}") >> button[aria-label="Delete Goal"]`,
113+
);
112114
}
113115
}

package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@nasa-jpl/stellar": "^1.0.3",
3939
"@sveltejs/adapter-node": "1.0.0-next.82",
4040
"@sveltejs/kit": "1.0.0-next.385",
41+
"ag-grid-community": "^28.0.2",
4142
"bootstrap": "^5.2.0",
4243
"bootstrap-icons": "^1.9.1",
4344
"cookie": "^0.5.0",
+17-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<svelte:options immutable={true} />
22

33
<script lang="ts">
4-
import { activities, selectedActivityId } from '../../stores/activities';
4+
import { activities, selectedActivityId, selectedActivityIds } from '../../stores/activities';
55
import { view } from '../../stores/views';
6-
import Table from '../ui/Table.svelte';
6+
import DataGrid from '../ui/DataGrid.svelte';
77
88
export let activityTableId: number;
99
@@ -12,10 +12,20 @@
1212
$: activityTable = $view?.definition.plan.activityTables.find(table => table.id === activityTableId);
1313
</script>
1414

15-
<Table
16-
columnDefs={activityTable?.columnDefs}
15+
<DataGrid
16+
columnDefs={Object.keys(activityTable?.columnDefs).map(columnKey => {
17+
const columnDef = activityTable?.columnDefs[columnKey];
18+
return {
19+
field: columnDef.field,
20+
filter: 'agTextColumnFilter',
21+
floatingFilter: true,
22+
headerName: columnDef.name,
23+
resizable: true,
24+
sortable: columnDef.sortable,
25+
};
26+
})}
27+
selectedRowIds={$selectedActivityIds}
28+
rowSelection="single"
1729
rowData={$activities}
18-
rowSelectionMode="single"
19-
selectedRowId={$selectedActivityId}
20-
on:rowClick={({ detail }) => ($selectedActivityId = detail.id)}
30+
on:rowSelected={({ detail }) => ($selectedActivityId = detail.data.id)}
2131
/>

src/components/constraints/Constraints.svelte

+58-35
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,63 @@
55
import { base } from '$app/paths';
66
import { constraintsAll, constraintsColumns } from '../../stores/constraints';
77
import effects from '../../utilities/effects';
8-
import { tooltip } from '../../utilities/tooltip';
98
import Input from '../form/Input.svelte';
109
import Chip from '../ui/Chip.svelte';
1110
import CssGrid from '../ui/CssGrid.svelte';
1211
import CssGridGutter from '../ui/CssGridGutter.svelte';
12+
import DataGrid from '../ui/DataGrid.svelte';
13+
import DataGridActions from '../ui/DataGridActions.svelte';
1314
import Panel from '../ui/Panel.svelte';
14-
import Table from '../ui/Table.svelte';
1515
import ConstraintEditor from './ConstraintEditor.svelte';
1616
17+
type CellRendererParams = {
18+
deleteConstraint: (constraint: Constraint) => void;
19+
editConstraint: (constraint: Constraint) => void;
20+
};
21+
type ConstraintsCellRendererParams = ICellRendererParams & CellRendererParams;
22+
1723
export let initialPlans: PlanList[] = [];
1824
25+
const columnDefs: DataGridColumnDef[] = [
26+
{ field: 'id', headerName: 'ID', sortable: true, suppressAutoSize: true, width: 60 },
27+
{ field: 'name', headerName: 'Name', resizable: true, sortable: true },
28+
{ field: 'model_id', headerName: 'Model ID', sortable: true, width: 120 },
29+
{ field: 'plan_id', headerName: 'Plan ID', sortable: true, width: 110 },
30+
{
31+
cellRenderer: (params: ConstraintsCellRendererParams) => {
32+
const actionsDiv = document.createElement('div');
33+
actionsDiv.className = 'actions-cell';
34+
new DataGridActions({
35+
props: {
36+
deleteCallback: params.deleteConstraint,
37+
deleteTooltip: {
38+
content: 'Delete Constraint',
39+
placement: 'bottom',
40+
},
41+
editCallback: params.editConstraint,
42+
editTooltip: {
43+
content: 'Edit Constraint',
44+
placement: 'bottom',
45+
},
46+
rowData: params.data,
47+
},
48+
target: actionsDiv,
49+
});
50+
51+
return actionsDiv;
52+
},
53+
cellRendererParams: {
54+
deleteConstraint,
55+
editConstraint,
56+
} as CellRendererParams,
57+
field: 'actions',
58+
headerName: '',
59+
resizable: false,
60+
sortable: false,
61+
width: 90,
62+
},
63+
];
64+
1965
let constraintModelId: number | null = null;
2066
let filterText: string = '';
2167
let filteredConstraints: Constraint[] = [];
@@ -35,7 +81,7 @@
3581
}
3682
$: constraintModelId = getConstraintModelId(selectedConstraint);
3783
38-
async function deleteConstraint(id: number) {
84+
async function deleteConstraint({ id }: Constraint) {
3985
const success = await effects.deleteConstraint(id);
4086
4187
if (success) {
@@ -64,9 +110,11 @@
64110
return null;
65111
}
66112
67-
function toggleConstraint(event: CustomEvent<Constraint>) {
68-
const { detail: clickedConstraint } = event;
113+
function editConstraint({ id }: Constraint) {
114+
goto(`${base}/constraints/edit/${id}`);
115+
}
69116
117+
function toggleConstraint(clickedConstraint: Constraint) {
70118
if (selectedConstraint?.id === clickedConstraint.id) {
71119
selectedConstraint = null;
72120
} else {
@@ -91,37 +139,12 @@
91139

92140
<svelte:fragment slot="body">
93141
{#if filteredConstraints.length}
94-
<Table
95-
let:currentRow
96-
columnDefs={[
97-
{ field: 'id', name: 'ID', sortable: true },
98-
{ field: 'name', name: 'Name', sortable: true },
99-
{ field: 'model_id', name: 'Model ID', sortable: true },
100-
{ field: 'plan_id', name: 'Plan ID', sortable: true },
101-
]}
102-
rowActions
142+
<DataGrid
143+
{columnDefs}
103144
rowData={filteredConstraints}
104-
rowSelectionMode="single"
105-
selectedRowId={selectedConstraint?.id}
106-
on:rowClick={toggleConstraint}
107-
>
108-
<div slot="actions-cell">
109-
<button
110-
class="st-button icon"
111-
on:click|stopPropagation={() => goto(`${base}/constraints/edit/${currentRow.id}`)}
112-
use:tooltip={{ content: 'Edit Constraint', placement: 'bottom' }}
113-
>
114-
<i class="bi bi-pencil" />
115-
</button>
116-
<button
117-
class="st-button icon"
118-
on:click|stopPropagation={() => deleteConstraint(currentRow.id)}
119-
use:tooltip={{ content: 'Delete Constraint', placement: 'bottom' }}
120-
>
121-
<i class="bi bi-trash" />
122-
</button>
123-
</div>
124-
</Table>
145+
rowSelection="single"
146+
on:rowSelected={({ detail }) => toggleConstraint(detail.data)}
147+
/>
125148
{:else}
126149
No Constraints Found
127150
{/if}

src/components/scheduling/SchedulingGoals.svelte

+57-34
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,60 @@
66
import { schedulingGoals, schedulingGoalsColumns } from '../../stores/scheduling';
77
import effects from '../../utilities/effects';
88
import { compare } from '../../utilities/generic';
9-
import { tooltip } from '../../utilities/tooltip';
109
import Input from '../form/Input.svelte';
1110
import Chip from '../ui/Chip.svelte';
1211
import CssGrid from '../ui/CssGrid.svelte';
1312
import CssGridGutter from '../ui/CssGridGutter.svelte';
13+
import DataGrid from '../ui/DataGrid.svelte';
14+
import DataGridActions from '../ui/DataGridActions.svelte';
1415
import Panel from '../ui/Panel.svelte';
15-
import Table from '../ui/Table.svelte';
1616
import SchedulingGoalEditor from './SchedulingGoalEditor.svelte';
1717
18+
type CellRendererParams = {
19+
deleteGoal: (goal: SchedulingGoal) => void;
20+
editGoal: (goal: SchedulingGoal) => void;
21+
};
22+
type SchedulingGoalsCellRendererParams = ICellRendererParams & CellRendererParams;
23+
24+
const columnDefs: DataGridColumnDef[] = [
25+
{ field: 'id', headerName: 'Goal ID', sortable: true, suppressAutoSize: true, width: 100 },
26+
{ field: 'name', headerName: 'Name', resizable: true, sortable: true },
27+
{ field: 'model_id', headerName: 'Model ID', sortable: true, width: 120 },
28+
{
29+
cellRenderer: (params: SchedulingGoalsCellRendererParams) => {
30+
const actionsDiv = document.createElement('div');
31+
actionsDiv.className = 'actions-cell';
32+
new DataGridActions({
33+
props: {
34+
deleteCallback: params.deleteGoal,
35+
deleteTooltip: {
36+
content: 'Delete Goal',
37+
placement: 'bottom',
38+
},
39+
editCallback: params.editGoal,
40+
editTooltip: {
41+
content: 'Edit Goal',
42+
placement: 'bottom',
43+
},
44+
rowData: params.data,
45+
},
46+
target: actionsDiv,
47+
});
48+
49+
return actionsDiv;
50+
},
51+
cellRendererParams: {
52+
deleteGoal,
53+
editGoal,
54+
},
55+
field: 'actions',
56+
headerName: '',
57+
resizable: false,
58+
sortable: false,
59+
width: 90,
60+
},
61+
];
62+
1863
let filteredGoals: SchedulingGoal[] = [];
1964
let filterText: string = '';
2065
let selectedGoal: SchedulingGoal | null = null;
@@ -34,7 +79,7 @@
3479
}
3580
}
3681
37-
async function deleteGoal(id: number) {
82+
async function deleteGoal({ id }: SchedulingGoal) {
3883
const success = await effects.deleteSchedulingGoal(id);
3984
4085
if (success) {
@@ -46,9 +91,11 @@
4691
}
4792
}
4893
49-
function toggleGoal(event: CustomEvent<SchedulingGoal>) {
50-
const { detail: clickedGoal } = event;
94+
function editGoal({ id }: SchedulingGoal) {
95+
goto(`${base}/scheduling/goals/edit/${id}`);
96+
}
5197
98+
function toggleGoal(clickedGoal: SchedulingGoal) {
5299
if (selectedGoal?.id === clickedGoal.id) {
53100
selectedGoal = null;
54101
} else {
@@ -75,36 +122,12 @@
75122

76123
<svelte:fragment slot="body">
77124
{#if sortedGoals.length}
78-
<Table
79-
let:currentRow
80-
columnDefs={[
81-
{ field: 'id', name: 'Goal ID', sortable: true },
82-
{ field: 'name', name: 'Name', sortable: true },
83-
{ field: 'model_id', name: 'Model ID', sortable: true },
84-
]}
85-
rowActions
125+
<DataGrid
126+
{columnDefs}
86127
rowData={sortedGoals}
87-
rowSelectionMode="single"
88-
selectedRowId={selectedGoal?.id}
89-
on:rowClick={toggleGoal}
90-
>
91-
<div slot="actions-cell">
92-
<button
93-
class="st-button icon"
94-
on:click|stopPropagation={() => goto(`${base}/scheduling/goals/edit/${currentRow.id}`)}
95-
use:tooltip={{ content: 'Edit Goal', placement: 'bottom' }}
96-
>
97-
<i class="bi bi-pencil" />
98-
</button>
99-
<button
100-
class="st-button icon"
101-
on:click|stopPropagation={() => deleteGoal(currentRow.id)}
102-
use:tooltip={{ content: 'Delete Goal', placement: 'bottom' }}
103-
>
104-
<i class="bi bi-trash" />
105-
</button>
106-
</div>
107-
</Table>
128+
rowSelection="single"
129+
on:rowSelected={({ detail }) => toggleGoal(detail.data)}
130+
/>
108131
{:else}
109132
No Scheduling Goals Found
110133
{/if}

0 commit comments

Comments
 (0)