erp_data_table 0.0.7 copy "erp_data_table: ^0.0.7" to clipboard
erp_data_table: ^0.0.7 copied to clipboard

A modern ERP-style reusable Flutter data table with grouping and totals.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:erp_data_table/erp_data_table.dart';

void main() {
  runApp(const DemoApp());
}

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'ERP Data Table Example',
      theme: ThemeData.dark(),
      home: const ErpTableExampleScreen(),
    );
  }
}

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

  @override
  State<ErpTableExampleScreen> createState() => _ErpTableExampleScreenState();
}

class _ErpTableExampleScreenState extends State<ErpTableExampleScreen> {
  ErpThemeVariant _themeVariant = ErpThemeVariant.frost;

  // Selected row is now identified by MAP REFERENCE (identity), not index
  Map<String, dynamic>? _selectedRow;
  bool _isEditMode = false;
  Map<String, String> _formValues = {};

  ErpTheme get _theme => ErpTheme(_themeVariant);

  // ── Form Config ─────────────────────────────────────────────────────────────
  List<List<ErpFieldConfig>> get _formRows => [
    [
      ErpFieldConfig(
        key: 'invoiceNo',
        label: 'INVOICE NO',
        type: ErpFieldType.dropdown,
        hint: 'Select Invoice',
        required: true,
        flex: 2,
        dropdownItems: [
          'INV-001',
          'INV-002',
          'INV-003',
          'INV-004',
          'INV-005',
          'INV-006',
          'INV-007',
          'INV-008',
          'INV-009',
          'INV-010',
        ],
      ),
      ErpFieldConfig(
        key: 'cut',
        label: 'CUT',
        type: ErpFieldType.dropdown,
        hint: 'Select Cut',
        required: true,
        flex: 2,
        dropdownItems: ['62.1', '63.2', '64.0', '65.5', '66.1', '67.3'],
      ),
      ErpFieldConfig(
        key: 'date',
        label: 'DATE',
        type: ErpFieldType.date,
        hint: 'dd-mm-yyyy',
        flex: 2,
      ),
    ],
    [
      ErpFieldConfig(
        key: 'remarks',
        label: 'REMARKS',
        hint: 'Enter remarks...',
        flex: 1,
        maxLines: 1,
      ),
    ],
    [
      ErpFieldConfig(
        key: 'totalWt',
        label: 'TOTAL WT',
        type: ErpFieldType.number,
        hint: '0.00',
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'assortWt',
        label: 'ASSORT WT',
        type: ErpFieldType.number,
        hint: '0.00',
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'pendingWt',
        label: 'PENDING WT',
        type: ErpFieldType.number,
        hint: '0.00',
        readOnly: true,
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'pendingAmt',
        label: 'PENDING AMT',
        type: ErpFieldType.number,
        hint: '0.00',
        readOnly: true,
        flex: 1,
      ),
    ],
    [
      ErpFieldConfig(
        key: 'assort',
        label: 'ASSORT',
        type: ErpFieldType.dropdown,
        hint: 'Select',
        flex: 2,
        dropdownItems: ['GHAT', 'PALSA', 'BOT', 'LB', 'ROUGH', 'MIXED'],
      ),
      ErpFieldConfig(
        key: 'wt',
        label: 'WT',
        type: ErpFieldType.number,
        hint: '0.00',
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'dollar',
        label: '\$ DOLLAR',
        type: ErpFieldType.number,
        hint: '0.00',
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'rate',
        label: 'RATE',
        type: ErpFieldType.number,
        hint: '0.00',
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'less',
        label: 'LESS',
        type: ErpFieldType.number,
        hint: '0',
        flex: 1,
      ),
      ErpFieldConfig(
        key: 'amt',
        label: 'AMT',
        type: ErpFieldType.number,
        hint: '0.00',
        readOnly: true,
        flex: 2,
      ),
    ],
  ];

  // ── Table Columns ─────────────────────────────────────────────────────────
  List<ErpColumnConfig> get _tableColumns => [
    const ErpColumnConfig(
      key: 'invoiceNo',
      label: 'INVOICE NO',
      flex: 0.8,
      required: true, // cannot be removed
    ),
    const ErpColumnConfig(
      key: 'date',
      label: 'DATE',
      flex: 1.2,
      isDate: true,
      required: true, // cannot be removed
    ),
    ErpColumnConfig(
      key: 'assortWt',
      label: 'ASSORT WT',
      flex: 0.9,
      align: ColumnAlign.right,
      formatter: (v) =>
          double.tryParse(v.toString())?.toStringAsFixed(2) ?? v.toString(),
    ),
    ErpColumnConfig(
      key: 'amount',
      label: 'AMOUNT',
      flex: 1.1,
      align: ColumnAlign.right,
      formatter: (v) {
        final n = double.tryParse(v.toString());
        if (n == null) return v.toString();
        return n >= 1000000
            ? '${(n / 1000000).toStringAsFixed(2)}M'
            : n >= 1000
            ? '${(n / 1000).toStringAsFixed(1)}K'
            : n.toStringAsFixed(2);
      },
    ),
  ];

  // ── Extra available columns (shown in pool, can be dragged into table) ───
  List<ErpColumnConfig> get _extraColumns => [
    const ErpColumnConfig(key: 'cut', label: 'CUT', flex: 0.7),
    const ErpColumnConfig(key: 'assort', label: 'ASSORT', flex: 1.0),
    ErpColumnConfig(
      key: 'rate',
      label: 'RATE',
      flex: 0.8,
      align: ColumnAlign.right,
      formatter: (v) =>
          double.tryParse(v.toString())?.toStringAsFixed(2) ?? v.toString(),
    ),
  ];

  // ── Sample Data ───────────────────────────────────────────────────────────
  final List<Map<String, dynamic>> _tableData = [
    {
      'date': '23/02/2026',
      'invoiceNo': 't1',
      'assortWt': 79.03,
      'amount': 4676940.08,
    },
    {
      'date': '23/02/2026',
      'invoiceNo': 't1',
      'assortWt': 259.00,
      'amount': 15327438.70,
    },
    {
      'date': '23/02/2026',
      'invoiceNo': 't1',
      'assortWt': 150.56,
      'amount': 9191404.95,
    },
    {
      'date': '21/02/2026',
      'invoiceNo': 'testing',
      'assortWt': 12.70,
      'amount': 222308.93,
    },
    {
      'date': '21/02/2026',
      'invoiceNo': 'testing',
      'assortWt': 23.81,
      'amount': 358719.16,
    },
    {
      'date': '20/02/2026',
      'invoiceNo': 'tt',
      'assortWt': 35.00,
      'amount': 931862.40,
    },
    {
      'date': '20/02/2026',
      'invoiceNo': 'tt',
      'assortWt': 125.00,
      'amount': 3294120.00,
    },
    {
      'date': '20/02/2026',
      'invoiceNo': 'tt',
      'assortWt': 160.00,
      'amount': 3277478.40,
    },
    {
      'date': '18/02/2026',
      'invoiceNo': '65',
      'assortWt': 2674.64,
      'amount': 4111736.40,
    },
    {
      'date': '18/02/2026',
      'invoiceNo': '64',
      'assortWt': 848.34,
      'amount': 1839953.84,
    },
    {
      'date': '18/02/2026',
      'invoiceNo': '63',
      'assortWt': 751.64,
      'amount': 1553157.07,
    },
    {
      'date': '18/02/2026',
      'invoiceNo': '62',
      'assortWt': 3386.43,
      'amount': 2145082.59,
    },
    {
      'date': '18/02/2026',
      'invoiceNo': '61',
      'assortWt': 4442.87,
      'amount': 3037428.54,
    },
    {
      'date': '18/02/2026',
      'invoiceNo': '60',
      'assortWt': 3806.23,
      'amount': 2279026.44,
    },
    {
      'date': '20/01/2026',
      'invoiceNo': '59',
      'assortWt': 134.83,
      'amount': 269660.00,
    },
    {
      'date': '12/01/2026',
      'invoiceNo': '58',
      'assortWt': 3293.62,
      'amount': 2443540.50,
    },
    {
      'date': '13/02/2026',
      'invoiceNo': '57',
      'assortWt': 4643.46,
      'amount': 2619453.03,
    },
    {
      'date': '13/02/2026',
      'invoiceNo': '56',
      'assortWt': 4643.47,
      'amount': 2619453.03,
    },
    {
      'date': '01/01/2026',
      'invoiceNo': '55',
      'assortWt': 4643.47,
      'amount': 2619453.03,
    },
    {
      'date': '01/01/2026',
      'invoiceNo': '54',
      'assortWt': 961.59,
      'amount': 447570.07,
    },
    {
      'date': '01/01/2026',
      'invoiceNo': '53',
      'assortWt': 3384.75,
      'amount': 1181709.96,
    },
    {
      'date': '11/12/2025',
      'invoiceNo': '52',
      'assortWt': 2189.51,
      'amount': 390145.66,
    },
    {
      'date': '11/12/2025',
      'invoiceNo': '51',
      'assortWt': 2189.52,
      'amount': 390124.47,
    },
    {
      'date': '01/01/2026',
      'invoiceNo': '50',
      'assortWt': 4930.29,
      'amount': 2562719.40,
    },
    {
      'date': '11/12/2025',
      'invoiceNo': '49',
      'assortWt': 3856.10,
      'amount': 1106603.52,
    },
    {
      'date': '11/12/2025',
      'invoiceNo': '48',
      'assortWt': 90.56,
      'amount': 181120.00,
    },
    {
      'date': '11/12/2025',
      'invoiceNo': '47',
      'assortWt': 2547.85,
      'amount': 783822.38,
    },
    {
      'date': '03/12/2025',
      'invoiceNo': '46',
      'assortWt': 123.51,
      'amount': 988080.00,
    },
  ];

  Map<String, double> get _totals {
    double totalWt = 0, totalAmt = 0;
    for (final row in _tableData) {
      totalWt += (row['assortWt'] as num).toDouble();
      totalAmt += (row['amount'] as num).toDouble();
    }
    return {'assortWt': totalWt, 'amount': totalAmt};
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: _theme.bg,
      body: Column(
        children: [
          _buildTopBar(),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Expanded(
                    flex: 3,
                    child: SizedBox(
                      width: 900,
                      child: ErpForm(
                        title: 'ROUGH ASSORT',
                        subtitle: 'Diamond Inventory Management',
                        rows: _formRows,
                        theme: _theme,
                        initialValues: _formValues,
                        isEditMode: _isEditMode,
                        onSave: (values) {
                          setState(() {
                            _formValues = {};
                            _isEditMode = false;
                            _selectedRow = null;
                          });
                          ScaffoldMessenger.of(context).showSnackBar(
                            SnackBar(
                              content: const Text('Record saved successfully'),
                              backgroundColor: _theme.primary,
                              behavior: SnackBarBehavior.floating,
                              shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(8),
                              ),
                              margin: const EdgeInsets.all(12),
                            ),
                          );
                        },
                        onCancel: () => setState(() {
                          _formValues = {};
                          _isEditMode = false;
                          _selectedRow = null;
                        }),
                        onDelete: _isEditMode
                            ? () {
                                setState(() {
                                  _isEditMode = false;
                                  _selectedRow = null;
                                  _formValues = {};
                                });
                              }
                            : null,
                      ),
                    ),
                  ),

                  const SizedBox(width: 12),

                  // ── Right: Table Panel
                  Expanded(
                    flex: 2,
                    child: ErpDataTable(
                      token: '',
                      title: 'ASSORT RECORDS',
                      columns: _tableColumns,
                      availableExtraColumns: _extraColumns,
                      data: _tableData,
                      theme: _theme,
                      showSearch: true,
                      showFooterTotals: true,
                      totals: _totals,
                      // Pass the actual map reference for identity-based selection
                      selectedRow: _selectedRow,
                      onRowTap: (row) {
                        setState(() {
                          _selectedRow = row;
                          _isEditMode = true;
                          _formValues = {
                            'invoiceNo': row['invoiceNo'].toString(),
                            'date': row['date'].toString(),
                            'assortWt': row['assortWt'].toString(),
                            'amt': row['amount'].toString(),
                          };
                        });
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTopBar() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.06),
            blurRadius: 10,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Row(
        children: [
          // App icon
          Container(
            width: 32,
            height: 32,
            decoration: BoxDecoration(
              gradient: LinearGradient(colors: _theme.primaryGradient),
              borderRadius: BorderRadius.circular(8),
            ),
            child: const Icon(
              Icons.diamond_outlined,
              color: Colors.white,
              size: 18,
            ),
          ),
          const SizedBox(width: 10),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Jay Export',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w800,
                  color: _theme.text,
                ),
              ),
              Text(
                'Diamond ERP System',
                style: TextStyle(fontSize: 10, color: _theme.textLight),
              ),
            ],
          ),

          const SizedBox(width: 20),
          // Tabs
          ...[
            ('Tender', Icons.receipt_outlined),
            ('Purchase', Icons.shopping_bag_outlined),
            ('Rough Assort', Icons.grid_view_outlined),
          ].map((tab) {
            final isActive = tab.$1 == 'Rough Assort';
            return Padding(
              padding: const EdgeInsets.only(right: 4),
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 6,
                ),
                decoration: BoxDecoration(
                  gradient: isActive
                      ? LinearGradient(colors: _theme.primaryGradient)
                      : null,
                  color: isActive ? null : Colors.transparent,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  children: [
                    Icon(
                      tab.$2,
                      size: 13,
                      color: isActive ? Colors.white : _theme.textLight,
                    ),
                    const SizedBox(width: 5),
                    Text(
                      tab.$1,
                      style: TextStyle(
                        fontSize: 12,
                        fontWeight: FontWeight.w600,
                        color: isActive ? Colors.white : _theme.textLight,
                      ),
                    ),
                    if (isActive) ...[
                      const SizedBox(width: 5),
                      GestureDetector(
                        child: Icon(
                          Icons.close,
                          size: 12,
                          color: Colors.white.withOpacity(0.8),
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            );
          }),

          const Spacer(),
          ErpThemeSwitcher(
            current: _themeVariant,
            onChanged: (v) => setState(() => _themeVariant = v),
          ),
          const SizedBox(width: 16),
          Container(
            padding: const EdgeInsets.all(6),
            decoration: BoxDecoration(
              color: _theme.bg,
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: _theme.border),
            ),
            child: Icon(
              Icons.notifications_outlined,
              size: 18,
              color: _theme.textLight,
            ),
          ),
          const SizedBox(width: 8),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
            decoration: BoxDecoration(
              gradient: LinearGradient(colors: _theme.primaryGradient),
              borderRadius: BorderRadius.circular(8),
            ),
            child: const Row(
              children: [
                Icon(Icons.person_outline, size: 14, color: Colors.white),
                SizedBox(width: 5),
                Text(
                  'Admin',
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.white,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
1
likes
0
points
521
downloads

Publisher

unverified uploader

Weekly Downloads

A modern ERP-style reusable Flutter data table with grouping and totals.

License

unknown (license)

Dependencies

excel, flutter, html, http, intl, path_provider, pdf, printing

More

Packages that depend on erp_data_table