feat: add URL deep-linking for DataGrids
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m5s

- Add PageUrlState utility for URL ↔ state serialization
- Add column `id` field for unique column identification in URLs
- Update idSelector to return String for URL compatibility
- All DataGrid pages now support URL params: search, sort, order, id
- Browser URL updates via replaceState (no GoRouter rebuilds)
- Add FilterPanelSemantics for filter panel semantic IDs
- Add TESTING.md documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-05 13:59:08 +01:00
co-authored by Claude Opus 4.5
parent a6c51f757a
commit c494ace5d8
19 changed files with 952 additions and 28 deletions
@@ -17,11 +17,15 @@ class ControlRoomPage extends ConsumerWidget {
const ControlRoomPage({
super.key,
this.nav = ControlRoomNav.containers,
this.routerState,
});
/// The current nav item to display.
final ControlRoomNav nav;
/// Router state for URL deep-linking (query params).
final GoRouterState? routerState;
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
@@ -53,7 +57,7 @@ class ControlRoomPage extends ConsumerWidget {
),
// Section content
Expanded(
child: _SectionContent(nav: nav),
child: _SectionContent(nav: nav, routerState: routerState),
),
],
),
@@ -63,15 +67,16 @@ class ControlRoomPage extends ConsumerWidget {
/// Renders content for the selected nav item.
class _SectionContent extends ConsumerWidget {
const _SectionContent({required this.nav});
const _SectionContent({required this.nav, this.routerState});
final ControlRoomNav nav;
final GoRouterState? routerState;
@override
Widget build(BuildContext context, WidgetRef ref) {
return switch (nav) {
ControlRoomNav.containers => const _ContainersSection(),
ControlRoomNav.proxyHosts => const ProxyHostsPage(),
ControlRoomNav.containers => _ContainersSection(routerState: routerState),
ControlRoomNav.proxyHosts => ProxyHostsPage(routerState: routerState),
_ => _PlaceholderSection(nav: nav),
};
}
@@ -119,7 +124,9 @@ class _PlaceholderSection extends StatelessWidget {
/// Containers section with optional stack filter.
class _ContainersSection extends ConsumerWidget {
const _ContainersSection();
const _ContainersSection({this.routerState});
final GoRouterState? routerState;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -129,7 +136,7 @@ class _ContainersSection extends ConsumerWidget {
return Row(
children: [
// Stacks filter panel
const _StacksFilterPanel(),
_StacksFilterPanel(routerState: routerState),
// Divider
VerticalDivider(
width: 1,
@@ -139,7 +146,7 @@ class _ContainersSection extends ConsumerWidget {
// Main content - containers list or stack detail
Expanded(
child: selectedStack == null
? const ContainersListPage()
? ContainersListPage(routerState: routerState)
: StackDetailPage(stackId: selectedStack),
),
],
@@ -149,7 +156,9 @@ class _ContainersSection extends ConsumerWidget {
/// Stacks filter panel for Containers section (includes "All Containers" option).
class _StacksFilterPanel extends ConsumerWidget {
const _StacksFilterPanel();
const _StacksFilterPanel({this.routerState});
final GoRouterState? routerState;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -207,6 +216,7 @@ class _StacksFilterPanel extends ConsumerWidget {
data: (stacks) => _StacksList(
stacks: stacks,
selectedStackId: selectedStack,
routerState: routerState,
onStackSelected: (id) =>
ref.read(selectedStackProvider.notifier).select(id),
onStackAction: (id, action) =>
@@ -281,12 +291,14 @@ class _StacksList extends StatefulWidget {
required this.selectedStackId,
required this.onStackSelected,
required this.onStackAction,
this.routerState,
});
final List<Stack> stacks;
final String? selectedStackId;
final void Function(String) onStackSelected;
final void Function(String, String) onStackAction;
final GoRouterState? routerState;
@override
State<_StacksList> createState() => _StacksListState();