Skip to main content

ai_grid 0.2.0 copy "ai_grid: ^0.2.0" to clipboard
ai_grid: ^0.2.0 copied to clipboard

An intelligent DataGrid for Flutter with native AI assistant and MCP skill control.

example/lib/main.dart

import 'dart:convert';
import 'package:material_ui/material_ui.dart';
import 'package:flutter/services.dart';
import 'package:ai_grid/ai_grid.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Automatically loads root/local .env if present
  await AiGridEnvLoader.load();
  runApp(const AiGridExampleApp());
}

class AiGridExampleApp extends StatefulWidget {
  const AiGridExampleApp({super.key});

  @override
  State<AiGridExampleApp> createState() => _AiGridExampleAppState();
}

class _AiGridExampleAppState extends State<AiGridExampleApp> {
  ThemeMode _themeMode = ThemeMode.light;

  void _toggleTheme() {
    setState(() {
      _themeMode =
          _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AI DataGrid Demo - Superstore Dataset',
      debugShowCheckedModeBanner: false,
      themeMode: _themeMode,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF0078D4),
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF0078D4),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: SuperstoreGridPage(
        isDarkMode: _themeMode == ThemeMode.dark,
        onToggleTheme: _toggleTheme,
      ),
    );
  }
}

class SuperstoreGridPage extends StatefulWidget {
  final bool isDarkMode;
  final VoidCallback onToggleTheme;

  const SuperstoreGridPage({
    super.key,
    required this.isDarkMode,
    required this.onToggleTheme,
  });

  @override
  State<SuperstoreGridPage> createState() => _SuperstoreGridPageState();
}

class _SuperstoreGridPageState extends State<SuperstoreGridPage> {
  LocalAiGridDataSource? _dataSource;
  late final AiGridController _controller;
  AiGridConfig? _aiConfig;
  bool _isLoading = true;
  String? _errorMessage;
  AiGridEditMode _editMode = AiGridEditMode.row;

  @override
  void initState() {
    super.initState();
    _controller = AiGridController();

    // Configure LLM from .env or fallback
    final llmConfig = AiGridEnvLoader.createLlmConfigFromEnv();
    _aiConfig = AiGridConfig(llmConfig: llmConfig);

    // Configure DevExtreme-style editing features
    _controller.editingOptions = AiGridEditingOptions(
      enabled: true,
      mode: _editMode,
      allowAdding: true,
      allowUpdating: true,
      allowDeleting: true,
      actionColumnPosition: AiGridActionColumnPosition.right,
      formLayout: const AiGridFormLayout.grid(columns: 2),
      insertRowPosition: AiGridInsertRowPosition.top,
      popupTitle: 'Superstore Order Details',
      afterSave: (row) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Order ${row['OrderID'] ?? ''} saved successfully!'),
              duration: const Duration(seconds: 2),
              behavior: SnackBarBehavior.floating,
              backgroundColor: Colors.green.shade700,
            ),
          );
        }
      },
      afterDelete: (row) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Order ${row['OrderID'] ?? ''} deleted.'),
              duration: const Duration(seconds: 2),
              behavior: SnackBarBehavior.floating,
            ),
          );
        }
      },
    );

    _loadSuperstoreJson();
  }

  Future<void> _loadSuperstoreJson() async {
    try {
      final jsonString =
          await rootBundle.loadString('assets/superstore_sales_1000.json');
      final List<dynamic> decoded = jsonDecode(jsonString);
      final records = decoded.cast<Map<String, dynamic>>();

      // Extract distinct sorted values for select/dropdown fields
      final shipModes = records
          .map((r) => r['ShipMode']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final segments = records
          .map((r) => r['Segment']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final countries = records
          .map((r) => r['Country']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final cities = records
          .map((r) => r['City']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final states = records
          .map((r) => r['State']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final regions = records
          .map((r) => r['Region']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final categories = records
          .map((r) => r['Category']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();
      final subCategories = records
          .map((r) => r['SubCategory']?.toString())
          .whereType<String>()
          .where((s) => s.isNotEmpty)
          .toSet()
          .toList()
        ..sort();

      final columns = [
        const AiGridColumn(
          field: 'OrderID',
          title: 'Order ID',
          type: AiGridColumnType.text,
          aggregateType: AiGridAggregateType.count,
          numberFormat: '#,###',
          width: 140,
          editable: true,
          required: true,
          editorIndex: 0,
        ),
        const AiGridColumn(
          field: 'OrderDate',
          title: 'Order Date',
          type: AiGridColumnType.date,
          editorType: AiGridEditorType.date,
          width: 140,
          editable: true,
          editorIndex: 1,
        ),
        const AiGridColumn(
          field: 'ShipDate',
          title: 'Ship Date',
          type: AiGridColumnType.date,
          editorType: AiGridEditorType.date,
          width: 140,
          editable: true,
          editorIndex: 2,
        ),
        AiGridColumn(
          field: 'ShipMode',
          title: 'Ship Mode',
          type: AiGridColumnType.select,
          selectOptions: shipModes,
          width: 130,
          editable: true,
          editorIndex: 3,
        ),
        const AiGridColumn(
          field: 'CustomerID',
          title: 'Customer ID',
          type: AiGridColumnType.text,
          width: 120,
          editable: true,
          editorIndex: 4,
        ),
        const AiGridColumn(
          field: 'CustomerName',
          title: 'Customer Name',
          type: AiGridColumnType.text,
          width: 160,
          editable: true,
          editorIndex: 5,
        ),
        AiGridColumn(
          field: 'Segment',
          title: 'Segment',
          type: AiGridColumnType.select,
          selectOptions: segments,
          width: 120,
          editable: true,
          editorIndex: 6,
        ),
        AiGridColumn(
          field: 'Country',
          title: 'Country',
          type: AiGridColumnType.select,
          selectOptions: countries,
          width: 120,
          editable: true,
          editorIndex: 7,
        ),
        AiGridColumn(
          field: 'City',
          title: 'City',
          type: AiGridColumnType.select,
          selectOptions: cities,
          width: 140,
          editable: true,
          editorIndex: 8,
        ),
        AiGridColumn(
          field: 'State',
          title: 'State',
          type: AiGridColumnType.select,
          selectOptions: states,
          width: 130,
          editable: true,
          editorIndex: 9,
        ),
        const AiGridColumn(
          field: 'PostalCode',
          title: 'Postal Code',
          type: AiGridColumnType.number,
          width: 110,
          editable: true,
          editorIndex: 10,
        ),
        AiGridColumn(
          field: 'Region',
          title: 'Region',
          type: AiGridColumnType.select,
          selectOptions: regions,
          width: 110,
          editable: true,
          editorIndex: 11,
        ),
        const AiGridColumn(
          field: 'ProductID',
          title: 'Product ID',
          type: AiGridColumnType.text,
          width: 150,
          editable: true,
          editorIndex: 12,
        ),
        AiGridColumn(
          field: 'Category',
          title: 'Category',
          type: AiGridColumnType.select,
          selectOptions: categories,
          width: 130,
          editable: true,
          editorIndex: 13,
        ),
        AiGridColumn(
          field: 'SubCategory',
          title: 'Sub-Category',
          type: AiGridColumnType.select,
          selectOptions: subCategories,
          width: 140,
          editable: true,
          editorIndex: 14,
        ),
        const AiGridColumn(
          field: 'ProductName',
          title: 'Product Name',
          type: AiGridColumnType.text,
          width: 260,
          editable: true,
          editorIndex: 15,
        ),
        const AiGridColumn(
          field: 'Sales',
          title: 'Sales',
          type: AiGridColumnType.currency,
          aggregateType: AiGridAggregateType.sum,
          width: 130,
          editable: false,
        ),
        const AiGridColumn(
          field: 'Quantity',
          title: 'Quantity',
          type: AiGridColumnType.number,
          aggregateType: AiGridAggregateType.sum,
          width: 100,
          editable: true,
          editorIndex: 16,
        ),
        const AiGridColumn(
          field: 'Discount',
          title: 'Discount',
          type: AiGridColumnType.number,
          aggregateType: AiGridAggregateType.average,
          width: 110,
          editable: true,
          editorIndex: 17,
        ),
        const AiGridColumn(
          field: 'Profit',
          title: 'Profit',
          type: AiGridColumnType.currency,
          aggregateType: AiGridAggregateType.sum,
          width: 130,
          editable: false,
        ),
      ];

      final ds = LocalAiGridDataSource(
        columns: columns,
        initialData: records,
      );

      if (mounted) {
        setState(() {
          _dataSource = ds;
          _isLoading = false;
        });
      }
    } catch (e, stack) {
      debugPrint('Error loading Superstore dataset: $e\n$stack');
      if (mounted) {
        setState(() {
          _errorMessage = e.toString();
          _isLoading = false;
        });
      }
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    _dataSource?.dispose();
    super.dispose();
  }

  Future<void> _exportSettingsToJsonFile() async {
    try {
      final jsonStr = _controller.exportSettingsJson();
      final bytes = Uint8List.fromList(utf8.encode(jsonStr));
      final fileName = 'grid_settings_${DateTime.now().millisecondsSinceEpoch}';
      final savedPath = await FileSaver.instance.saveFile(
        name: fileName,
        bytes: bytes,
        fileExtension: 'json',
        mimeType: MimeType.json,
      );
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Row(
              children: [
                const Icon(Icons.check_circle, color: Colors.white, size: 20),
                const SizedBox(width: 8),
                Expanded(
                  child: Text(
                    'Settings exported to JSON! ${savedPath.isNotEmpty ? "($savedPath)" : ""}',
                  ),
                ),
              ],
            ),
            backgroundColor: Colors.green.shade700,
            behavior: SnackBarBehavior.floating,
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Failed to export settings: $e'),
            backgroundColor: Colors.red.shade700,
            behavior: SnackBarBehavior.floating,
          ),
        );
      }
    }
  }

  void _showRestoreSettingsDialog() {
    final textController = TextEditingController(text: _controller.exportSettingsJson());
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Restore Grid Layout Settings'),
        content: SizedBox(
          width: 500,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Paste or edit a valid FitGrid JSON configuration below (supports columnOrder, widths, frozen, sorts, filters, and groupColumns):',
                style: TextStyle(fontSize: 12),
              ),
              const SizedBox(height: 12),
              TextField(
                controller: textController,
                maxLines: 10,
                style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
                decoration: InputDecoration(
                  border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
                  filled: true,
                ),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('Cancel'),
          ),
          FilledButton.icon(
            icon: const Icon(Icons.restore, size: 16),
            label: const Text('Apply Layout'),
            onPressed: () {
              Navigator.pop(ctx);
              _controller.restoreSettingsJson(textController.text);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('Grid layout successfully restored!'),
                  behavior: SnackBarBehavior.floating,
                  backgroundColor: Colors.green,
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  void _showEditingEventLogDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (ctx) {
        return StatefulBuilder(
          builder: (context, setDialogState) {
            final events = _controller.editEvents.reversed.toList();
            final theme = Theme.of(context);
            final colorScheme = theme.colorScheme;

            Color getActionColor(String action) {
              switch (action) {
                case 'save':
                  return Colors.green;
                case 'startEdit':
                  return Colors.blue;
                case 'startAdd':
                  return Colors.teal;
                case 'change':
                  return Colors.purple;
                case 'delete':
                  return Colors.red;
                case 'cancel':
                  return Colors.orange;
                case 'error':
                  return Colors.redAccent;
                default:
                  return colorScheme.primary;
              }
            }

            IconData getActionIcon(String action) {
              switch (action) {
                case 'save':
                  return Icons.check_circle_outline;
                case 'startEdit':
                  return Icons.edit_outlined;
                case 'startAdd':
                  return Icons.add_circle_outline;
                case 'change':
                  return Icons.sync_alt;
                case 'delete':
                  return Icons.delete_outline;
                case 'cancel':
                  return Icons.cancel_outlined;
                case 'error':
                  return Icons.error_outline;
                default:
                  return Icons.info_outline;
              }
            }

            return AlertDialog(
              title: Row(
                children: [
                  Container(
                    padding: const EdgeInsets.all(6),
                    decoration: BoxDecoration(
                      color: colorScheme.primaryContainer,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Icon(Icons.receipt_long, size: 20, color: colorScheme.onPrimaryContainer),
                  ),
                  const SizedBox(width: 10),
                  const Text('Editing Event Log', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                  const Spacer(),
                  Container(
                    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                    decoration: BoxDecoration(
                      color: colorScheme.surfaceContainerHighest,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(
                      '${events.length} events',
                      style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant),
                    ),
                  ),
                ],
              ),
              content: SizedBox(
                width: 620,
                height: 460,
                child: events.isEmpty
                    ? Center(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Icon(Icons.history_outlined, size: 48, color: colorScheme.outlineVariant),
                            const SizedBox(height: 12),
                            Text(
                              'No editing events recorded yet',
                              style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant),
                            ),
                            const SizedBox(height: 6),
                            Text(
                              'Edit a row, change cell values, or delete/add records to see live event logs here.',
                              style: TextStyle(fontSize: 12, color: colorScheme.outline),
                              textAlign: TextAlign.center,
                            ),
                          ],
                        ),
                      )
                    : ListView.separated(
                        itemCount: events.length,
                        separatorBuilder: (_, _) => const Divider(height: 1),
                        itemBuilder: (context, index) {
                          final event = events[index];
                          final actionColor = getActionColor(event.action);
                          final timeStr =
                              '${event.timestamp.hour.toString().padLeft(2, '0')}:${event.timestamp.minute.toString().padLeft(2, '0')}:${event.timestamp.second.toString().padLeft(2, '0')}';

                          return Padding(
                            padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
                            child: Row(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Container(
                                  padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
                                  decoration: BoxDecoration(
                                    color: actionColor.withValues(alpha: 0.12),
                                    borderRadius: BorderRadius.circular(6),
                                    border: Border.all(color: actionColor.withValues(alpha: 0.4)),
                                  ),
                                  child: Row(
                                    mainAxisSize: MainAxisSize.min,
                                    children: [
                                      Icon(getActionIcon(event.action), size: 12, color: actionColor),
                                      const SizedBox(width: 4),
                                      Text(
                                        event.action.toUpperCase(),
                                        style: TextStyle(
                                          fontSize: 10,
                                          fontWeight: FontWeight.bold,
                                          color: actionColor,
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                                const SizedBox(width: 10),
                                Expanded(
                                  child: Column(
                                    crossAxisAlignment: CrossAxisAlignment.start,
                                    children: [
                                      Text(
                                        event.message,
                                        style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
                                      ),
                                      if (event.data != null && event.data!.isNotEmpty) ...[
                                        const SizedBox(height: 4),
                                        Container(
                                          padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
                                          decoration: BoxDecoration(
                                            color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.4),
                                            borderRadius: BorderRadius.circular(4),
                                          ),
                                          child: Text(
                                            event.data!.entries
                                                .take(4)
                                                .map((e) => '${e.key}: ${e.value}')
                                                .join(' • '),
                                            style: TextStyle(
                                              fontSize: 10,
                                              fontFamily: 'monospace',
                                              color: colorScheme.onSurfaceVariant,
                                            ),
                                            maxLines: 2,
                                            overflow: TextOverflow.ellipsis,
                                          ),
                                        ),
                                      ],
                                    ],
                                  ),
                                ),
                                const SizedBox(width: 8),
                                Text(
                                  timeStr,
                                  style: TextStyle(
                                    fontSize: 11,
                                    color: colorScheme.outline,
                                    fontFamily: 'monospace',
                                  ),
                                ),
                              ],
                            ),
                          );
                        },
                      ),
              ),
              actions: [
                if (events.isNotEmpty)
                  TextButton.icon(
                    icon: const Icon(Icons.delete_sweep_outlined, size: 16),
                    label: const Text('Clear Log'),
                    onPressed: () {
                      _controller.clearEditEvents();
                      setDialogState(() {});
                    },
                  ),
                FilledButton(
                  onPressed: () => Navigator.pop(ctx),
                  child: const Text('Close'),
                ),
              ],
            );
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;

    return Scaffold(
      backgroundColor: colorScheme.surfaceContainerLowest,
      appBar: AppBar(
        title: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Icon(Icons.table_chart_outlined, size: 20),
            const SizedBox(width: 8),
            const Flexible(
              child: Text(
                'Superstore Sales (1,000 JSON)',
                style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
                overflow: TextOverflow.ellipsis,
              ),
            ),
            const SizedBox(width: 8),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
              decoration: BoxDecoration(
                color: colorScheme.primaryContainer,
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(
                _aiConfig?.llmConfig != null &&
                        _aiConfig!.llmConfig!.isConfigured
                    ? '${_aiConfig!.llmConfig!.provider.displayName} Active'
                    : 'Local Heuristic AI',
                style: TextStyle(
                  fontSize: 11,
                  fontWeight: FontWeight.w600,
                  color: colorScheme.onPrimaryContainer,
                ),
              ),
            ),
          ],
        ),
        backgroundColor: colorScheme.surface,
        elevation: 0,
        actions: [
          // Top bar switch among editing modes (Row, Cell, Form)
          Container(
            height: 36,
            margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
            padding: const EdgeInsets.symmetric(horizontal: 10),
            decoration: BoxDecoration(
              color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: colorScheme.outlineVariant),
            ),
            child: DropdownButtonHideUnderline(
              child: DropdownButton<AiGridEditMode>(
                value: _editMode,
                icon: const Icon(Icons.arrow_drop_down, size: 18),
                borderRadius: BorderRadius.circular(8),
                style: TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.w600,
                  color: colorScheme.onSurface,
                ),
                items: const [
                  DropdownMenuItem(
                    value: AiGridEditMode.row,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(Icons.edit_note, size: 16),
                        SizedBox(width: 6),
                        Text('Row Edit Mode'),
                      ],
                    ),
                  ),
                  DropdownMenuItem(
                    value: AiGridEditMode.cell,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(Icons.grid_on, size: 16),
                        SizedBox(width: 6),
                        Text('Cell Edit Mode'),
                      ],
                    ),
                  ),
                  DropdownMenuItem(
                    value: AiGridEditMode.form,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(Icons.dynamic_form_outlined, size: 16),
                        SizedBox(width: 6),
                        Text('Form (Popup) Mode'),
                      ],
                    ),
                  ),
                ],
                onChanged: (mode) {
                  if (mode != null) {
                    setState(() {
                      _editMode = mode;
                      _controller.editingOptions =
                          _controller.editingOptions.copyWith(mode: mode);
                    });
                  }
                },
              ),
            ),
          ),
          ListenableBuilder(
            listenable: _controller,
            builder: (context, _) {
              final count = _controller.editEvents.length;
              return IconButton(
                icon: Badge(
                  isLabelVisible: count > 0,
                  label: Text(count > 99 ? '99+' : '$count'),
                  child: const Icon(Icons.receipt_long_outlined),
                ),
                tooltip: 'Editing Events Log ($count)',
                onPressed: () => _showEditingEventLogDialog(context),
              );
            },
          ),
          PopupMenuButton<String>(
            icon: const Icon(Icons.tune_outlined),
            tooltip: 'Layout Presets & Settings',
            constraints: const BoxConstraints(minWidth: 340, maxWidth: 400),
            onSelected: (val) async {
              if (val == 'preset_grouping') {
                _controller.restoreSettings({
                  'groupColumns': ['Category', 'Region'],
                  'sort': [
                    {'column': 'Sales', 'dir': 'descending'},
                  ],
                });
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Text('Applied Preset: Grouped by Category & Region, sorted by Sales'),
                    behavior: SnackBarBehavior.floating,
                  ),
                );
              } else if (val == 'preset_financial') {
                _controller.restoreSettings({
                  'widths': {
                    'OrderID': 110.0,
                    'CustomerName': 180.0,
                    'Sales': 140.0,
                    'Profit': 140.0,
                    'Discount': 100.0,
                  },
                  'sort': [
                    {'column': 'Profit', 'dir': 'descending'},
                  ],
                  'groupColumns': <String>[],
                });
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Text('Applied Preset: Financial Focus (Custom column widths & sorted by Profit)'),
                    behavior: SnackBarBehavior.floating,
                  ),
                );
              } else if (val == 'preset_high_value') {
                _controller.restoreSettings({
                  'filters': {
                    'Sales': {'op': 'greaterThan', 'value': 500},
                  },
                  'sort': [
                    {'column': 'Sales', 'dir': 'descending'},
                  ],
                  'groupColumns': <String>[],
                });
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Text('Applied Preset: High Value Orders (Sales > \$500)'),
                    behavior: SnackBarBehavior.floating,
                  ),
                );
              } else if (val == 'export_json_file') {
                await _exportSettingsToJsonFile();
              } else if (val == 'restore_json') {
                _showRestoreSettingsDialog();
              } else if (val == 'clear_settings') {
                AiGridSettingsStorage.clear('superstore_grid_settings');
                _controller.reset();
                if (mounted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Layout settings cleared and grid reset to defaults.'),
                      behavior: SnackBarBehavior.floating,
                    ),
                  );
                }
              }
            },
            itemBuilder: (ctx) => [
              const PopupMenuItem(
                value: 'preset_grouping',
                child: Row(
                  children: [
                    Icon(Icons.layers_outlined, size: 18),
                    SizedBox(width: 8),
                    Flexible(child: Text('Preset: Group by Category & Region')),
                  ],
                ),
              ),
              const PopupMenuItem(
                value: 'preset_financial',
                child: Row(
                  children: [
                    Icon(Icons.attach_money, size: 18),
                    SizedBox(width: 8),
                    Flexible(child: Text('Preset: Financial Focus (Custom Widths)')),
                  ],
                ),
              ),
              const PopupMenuItem(
                value: 'preset_high_value',
                child: Row(
                  children: [
                    Icon(Icons.filter_alt_outlined, size: 18),
                    SizedBox(width: 8),
                    Flexible(child: Text('Preset: High Value Sales (> \$500)')),
                  ],
                ),
              ),
              const PopupMenuDivider(),
              const PopupMenuItem(
                value: 'export_json_file',
                child: Row(
                  children: [
                    Icon(Icons.file_download_outlined, size: 18),
                    SizedBox(width: 8),
                    Flexible(child: Text('Export Layout (FitGrid JSON)')),
                  ],
                ),
              ),
              const PopupMenuItem(
                value: 'restore_json',
                child: Row(
                  children: [
                    Icon(Icons.file_upload_outlined, size: 18),
                    SizedBox(width: 8),
                    Flexible(child: Text('Restore Layout from JSON')),
                  ],
                ),
              ),
              const PopupMenuDivider(),
              const PopupMenuItem(
                value: 'clear_settings',
                child: Row(
                  children: [
                    Icon(Icons.restore_outlined, size: 18, color: Colors.orange),
                    SizedBox(width: 8),
                    Flexible(child: Text('Reset Layout to Defaults')),
                  ],
                ),
              ),
            ],
          ),
          IconButton(
            icon: Icon(
              widget.isDarkMode ? Icons.light_mode : Icons.dark_mode,
            ),
            tooltip: widget.isDarkMode
                ? 'Switch to Light Theme'
                : 'Switch to Dark Theme',
            onPressed: widget.onToggleTheme,
          ),
          IconButton(
            icon: const Icon(Icons.schema_outlined),
            tooltip: 'Inspect MCP Skill Schema',
            onPressed: () {
              final schema = _controller.getSchemaSummary();
              showDialog(
                context: context,
                builder: (ctx) => AlertDialog(
                  title: const Text('MCP Grid Skill Schema'),
                  content: SingleChildScrollView(
                    child: Text(schema.toString()),
                  ),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(ctx),
                      child: const Text('Close'),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
      body: _isLoading
          ? const Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  CircularProgressIndicator(),
                  SizedBox(height: 16),
                  Text('Loading Superstore dataset from JSON assets...'),
                ],
              ),
            )
          : _errorMessage != null
              ? Center(
                  child: Text(
                    'Failed to load JSON dataset: $_errorMessage',
                    style: TextStyle(color: colorScheme.error),
                  ),
                )
              : Padding(
                  padding: EdgeInsets.all(
                    MediaQuery.of(context).size.width < 600 ? 0.0 : 12.0,
                  ),
                  child: AiGrid(
                    dataSource: _dataSource!,
                    controller: _controller,
                    aiConfig: _aiConfig,
                    editingOptions: _controller.editingOptions,
                    enableAiAssistant: true,
                    enablePagination: false,
                    autoSave: true,
                    persistenceKey: 'superstore_grid_settings',
                  ),
                ),
    );
  }
}
1
likes
0
points
189
downloads

Publisher

verified publishertealkit.dev

Weekly Downloads

An intelligent DataGrid for Flutter with native AI assistant and MCP skill control.

Repository (GitHub)
View/report issues

Topics

#datagrid #ai #mcp #table #agent

License

unknown (license)

Dependencies

dart_mcp_core, draggable_overlay_window, file_saver, flutter, intl, material_ui, shared_preferences

More

Packages that depend on ai_grid