Vyuh CDX

Examples

Complete production-ready examples demonstrating best practices and patterns

This guide presents complete, production-ready examples from actual features built with the Vyuh Entity System. These examples demonstrate best practices and common patterns you can adapt for your own applications.

Example 1: User Management System

A complete user management implementation with roles, permissions, and activity tracking.

Entity Model

// lib/models/user.dart
import 'package:json_annotation/json_annotation.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';

part 'user.g.dart';

@JsonSerializable()
class User extends EntityBase {
  final String name;
  final String email;
  final String? phone;
  final UserRole role;
  final String? department;
  final String? locationId;
  final bool isActive;
  final DateTime? lastLogin;
  final Map<String, dynamic>? preferences;

  User({
    required super.id,
    required super.schemaType,
    required this.name,
    required this.email,
    this.phone,
    required this.role,
    this.department,
    this.locationId,
    this.isActive = true,
    this.lastLogin,
    this.preferences,
    super.createdAt,
    super.layout,
    super.modifiers,
  });

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  @override
  Map<String, dynamic> toJson() => _$UserToJson(this);

  User copyWith({
    String? name,
    String? email,
    String? phone,
    UserRole? role,
    String? department,
    String? locationId,
    bool? isActive,
    DateTime? lastLogin,
    Map<String, dynamic>? preferences,
  }) {
    return User(
      id: id,
      schemaType: schemaType,
      name: name ?? this.name,
      email: email ?? this.email,
      phone: phone ?? this.phone,
      role: role ?? this.role,
      department: department ?? this.department,
      locationId: locationId ?? this.locationId,
      isActive: isActive ?? this.isActive,
      lastLogin: lastLogin ?? this.lastLogin,
      preferences: preferences ?? this.preferences,
      createdAt: createdAt,
      layout: layout,
      modifiers: modifiers,
    );
  }
}

enum UserRole {
  @JsonValue('admin')
  admin,
  @JsonValue('manager')
  manager,
  @JsonValue('user')
  user,
  @JsonValue('viewer')
  viewer,
}

extension UserRoleExtension on UserRole {
  String get displayName {
    switch (this) {
      case UserRole.admin:
        return 'Administrator';
      case UserRole.manager:
        return 'Manager';
      case UserRole.user:
        return 'User';
      case UserRole.viewer:
        return 'Viewer';
    }
  }

  Color get color {
    switch (this) {
      case UserRole.admin:
        return Colors.red;
      case UserRole.manager:
        return Colors.orange;
      case UserRole.user:
        return Colors.blue;
      case UserRole.viewer:
        return Colors.grey;
    }
  }
}

API Implementation

// lib/api/user_api.dart
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/user.dart';

class UserApi extends EntityApi<User> {
  UserApi({required super.client});

  @override
  User fromJson(Map<String, dynamic> json) => User.fromJson(json);

  Future<bool> emailExists(String email) async {
    final response = await client.get(
      '/api/v1/users/check-email',
      queryParameters: {'email': email},
    );

    return response.data['exists'] ?? false;
  }

  Future<User> updatePassword(String userId, String newPassword) async {
    final response = await client.post(
      '/api/v1/users/$userId/update-password',
      data: {'password': newPassword},
    );

    return fromJson(response.data);
  }

  Future<void> resetPassword(String email) async {
    await client.post(
      '/api/v1/users/reset-password',
      data: {'email': email},
    );
  }

  Future<List<User>> getByLocation(String locationId) async {
    return list(filters: {'locationId': locationId});
  }

  Future<List<User>> getByRole(UserRole role) async {
    return list(filters: {'role': role.name});
  }

  Future<Map<String, int>> getUserStatistics() async {
    final response = await client.get('/api/v1/users/statistics');

    return Map<String, int>.from(response.data);
  }
}

Layout Implementation

// lib/layouts/user_layouts.dart
import 'package:flutter/material.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/user.dart';

class UserLayouts {
  static EntityLayoutDescriptor<User> create() {
    return EntityLayoutDescriptor<User>(
      list: [
        UserTableLayout(),
        UserGridLayout(),
      ],
      details: [
        UserDetailLayout(),
      ],
      summary: [
        UserCardLayout(),
      ],
    );
  }
}

class UserTableLayout extends TableListLayout<User> {
  UserTableLayout() : super(
    title: 'Users',
    columns: [
      TableColumn<User>(
        key: 'name',
        label: 'Name',
        getValue: (user) => user.name,
        sortable: true,
        searchable: true,
        buildCell: (context, user) =>
            Row(
              children: [
                CircleAvatar(
                  radius: 16,
                  backgroundColor: user.role.color,
                  child: Text(
                    user.name[0].toUpperCase(),
                    style: const TextStyle(color: Colors.white, fontSize: 12),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(user.name),
                      Text(
                        user.role.displayName,
                        style: Theme
                            .of(context)
                            .textTheme
                            .bodySmall,
                      ),
                    ],
                  ),
                ),
              ],
            ),
      ),
      TableColumn<User>(
        key: 'email',
        label: 'Email',
        getValue: (user) => user.email,
        sortable: true,
        searchable: true,
        copyable: true,
      ),
      TableColumn<User>(
        key: 'department',
        label: 'Department',
        getValue: (user) => user.department ?? '-',
        sortable: true,
      ),
      TableColumn<User>(
        key: 'status',
        label: 'Status',
        getValue: (user) => user.isActive ? 'Active' : 'Inactive',
        width: 100,
        buildCell: (context, user) =>
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: BoxDecoration(
                color: user.isActive
                    ? Colors.green.withOpacity(0.1)
                    : Colors.grey.withOpacity(0.1),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Icon(
                    user.isActive ? Icons.check_circle : Icons.cancel,
                    size: 14,
                    color: user.isActive ? Colors.green : Colors.grey,
                  ),
                  const SizedBox(width: 4),
                  Text(
                    user.isActive ? 'Active' : 'Inactive',
                    style: TextStyle(
                      fontSize: 12,
                      color: user.isActive ? Colors.green : Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
      ),
      TableColumn<User>(
        key: 'lastLogin',
        label: 'Last Login',
        getValue: (user) =>
        user.lastLogin != null
            ? _formatLastLogin(user.lastLogin!)
            : 'Never',
        sortable: true,
      ),
    ],
    filters: [
      Filter<User>(
        key: 'role',
        label: 'Role',
        options: UserRole.values.map((role) =>
            FilterOption(
              value: role.name,
              label: role.displayName,
            )).toList(),
        apply: (users, value) =>
            users.where((user) =>
            user.role.name == value
            ).toList(),
      ),
      Filter<User>(
        key: 'status',
        label: 'Status',
        options: [
          FilterOption(value: 'active', label: 'Active'),
          FilterOption(value: 'inactive', label: 'Inactive'),
        ],
        apply: (users, value) =>
            users.where((user) =>
            value == 'active' ? user.isActive : !user.isActive
            ).toList(),
      ),
    ],
    bulkActions: [
      BulkAction(
        label: 'Activate',
        icon: Icons.check_circle,
        onTap: (context, users) async {
          final api = context
              .read<EntityProvider<User>>()
              .api as UserApi;
          for (final user in users) {
            await api.update(user.id, user.copyWith(isActive: true));
          }
        },
        isEnabled: (users) => users.any((u) => !u.isActive),
      ),
      BulkAction(
        label: 'Deactivate',
        icon: Icons.cancel,
        onTap: (context, users) async {
          final api = context
              .read<EntityProvider<User>>()
              .api as UserApi;
          for (final user in users) {
            await api.update(user.id, user.copyWith(isActive: false));
          }
        },
        isEnabled: (users) => users.any((u) => u.isActive),
      ),
    ],
  );

  static String _formatLastLogin(DateTime lastLogin) {
    final now = DateTime.now();
    final difference = now.difference(lastLogin);

    if (difference.inDays == 0) {
      if (difference.inHours == 0) {
        return '${difference.inMinutes} minutes ago';
      }
      return '${difference.inHours} hours ago';
    } else if (difference.inDays < 7) {
      return '${difference.inDays} days ago';
    } else {
      return DateFormat('MMM d, y').format(lastLogin);
    }
  }
}

class UserDetailLayout extends EntityLayout<User> {
  @override
  Widget build(BuildContext context, User user) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(24),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Header Card
          Card(
            child: Padding(
              padding: const EdgeInsets.all(24),
              child: Row(
                children: [
                  CircleAvatar(
                    radius: 40,
                    backgroundColor: user.role.color,
                    child: Text(
                      user.name[0].toUpperCase(),
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 32,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                  const SizedBox(width: 24),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          user.name,
                          style: Theme
                              .of(context)
                              .textTheme
                              .headlineSmall,
                        ),
                        const SizedBox(height: 4),
                        Text(
                          user.email,
                          style: Theme
                              .of(context)
                              .textTheme
                              .bodyLarge,
                        ),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            Chip(
                              label: Text(user.role.displayName),
                              backgroundColor: user.role.color.withOpacity(0.1),
                              labelStyle: TextStyle(color: user.role.color),
                            ),
                            const SizedBox(width: 8),
                            if (!user.isActive)
                              const Chip(
                                label: Text('Inactive'),
                                backgroundColor: Colors.red,
                                labelStyle: TextStyle(color: Colors.white),
                              ),
                          ],
                        ),
                      ],
                    ),
                  ),
                  Column(
                    children: [
                      IconButton(
                        icon: const Icon(Icons.edit),
                        onPressed: () => context.go('/users/${user.id}/edit'),
                        tooltip: 'Edit User',
                      ),
                      IconButton(
                        icon: const Icon(Icons.history),
                        onPressed: () => _showActivityHistory(context, user),
                        tooltip: 'Activity History',
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),

          const SizedBox(height: 24),

          // Information Grid
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Expanded(
                child: _buildInfoSection(
                  context,
                  title: 'Contact Information',
                  children: [
                    _buildInfoRow('Email', user.email, copyable: true),
                    _buildInfoRow('Phone', user.phone ?? 'Not provided'),
                  ],
                ),
              ),
              const SizedBox(width: 24),
              Expanded(
                child: _buildInfoSection(
                  context,
                  title: 'Organization',
                  children: [
                    _buildInfoRow('Department', user.department ?? '-'),
                    if (user.locationId != null)
                      FutureBuilder<Location?>(
                        future: _loadLocation(user.locationId!),
                        builder: (context, snapshot) {
                          return _buildInfoRow(
                            'Location',
                            snapshot.data?.name ?? 'Loading...',
                          );
                        },
                      ),
                  ],
                ),
              ),
            ],
          ),

          const SizedBox(height: 24),

          // Activity Summary
          _buildActivitySummary(context, user),

          const SizedBox(height: 24),

          // Permissions
          _buildPermissionsSection(context, user),
        ],
      ),
    );
  }

  Widget _buildInfoSection(BuildContext context, {
    required String title,
    required List<Widget> children,
  }) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: Theme
                  .of(context)
                  .textTheme
                  .titleMedium,
            ),
            const SizedBox(height: 16),
            ...children,
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value, {bool copyable = false}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        children: [
          SizedBox(
            width: 120,
            child: Text(
              label,
              style: const TextStyle(fontWeight: FontWeight.w500),
            ),
          ),
          Expanded(
            child: copyable
                ? SelectableText(value)
                : Text(value),
          ),
        ],
      ),
    );
  }

  Widget _buildActivitySummary(BuildContext context, User user) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  'Recent Activity',
                  style: Theme
                      .of(context)
                      .textTheme
                      .titleMedium,
                ),
                TextButton(
                  onPressed: () => _showActivityHistory(context, user),
                  child: const Text('View All'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            if (user.lastLogin != null)
              ListTile(
                leading: const Icon(Icons.login),
                title: const Text('Last Login'),
                subtitle: Text(
                  DateFormat('MMM d, y at h:mm a').format(user.lastLogin!),
                ),
                contentPadding: EdgeInsets.zero,
              ),
            ListTile(
              leading: const Icon(Icons.calendar_today),
              title: const Text('Account Created'),
              subtitle: Text(
                DateFormat('MMM d, y').format(user.createdAt!),
              ),
              contentPadding: EdgeInsets.zero,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildPermissionsSection(BuildContext context, User user) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  'Permissions',
                  style: Theme
                      .of(context)
                      .textTheme
                      .titleMedium,
                ),
                if (context.read<EntityPermissionService>()
                    .hasPermission(Permission.update('users', user.id)))
                  IconButton(
                    icon: const Icon(Icons.edit),
                    onPressed: () => _editPermissions(context, user),
                    tooltip: 'Edit Permissions',
                  ),
              ],
            ),
            const SizedBox(height: 16),
            FutureBuilder<List<Permission>>(
              future: _loadUserPermissions(user.id),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return const CircularProgressIndicator();
                }

                final permissions = snapshot.data!;
                if (permissions.isEmpty) {
                  return Text(
                    'No specific permissions assigned',
                    style: Theme
                        .of(context)
                        .textTheme
                        .bodySmall,
                  );
                }

                return Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: permissions.map((permission) =>
                      Chip(
                        label: Text(
                          '${permission.resource}.${permission.actions.join(",")}',
                        ),
                      )).toList(),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  Future<Location?> _loadLocation(String locationId) async {
    // Load location details
    return null; // Placeholder
  }

  Future<List<Permission>> _loadUserPermissions(String userId) async {
    // Load user permissions
    return []; // Placeholder
  }

  void _showActivityHistory(BuildContext context, User user) {
    showDialog(
      context: context,
      builder: (_) =>
          Dialog(
            child: Container(
              width: 800,
              height: 600,
              padding: const EdgeInsets.all(24),
              child: Column(
                children: [
                  Text(
                    'Activity History - ${user.name}',
                    style: Theme
                        .of(context)
                        .textTheme
                        .titleLarge,
                  ),
                  const SizedBox(height: 16),
                  Expanded(
                    child: ActivityTimeline(
                      userId: user.id,
                    ),
                  ),
                ],
              ),
            ),
          ),
    );
  }

  void _editPermissions(BuildContext context, User user) {
    // Show permission editor
  }
}

Form Implementation

// lib/forms/user_form.dart
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import 'package:vyuh_feature_forms/vyuh_feature_forms.dart';
import '../models/user.dart';

class UserFormDescriptor extends EntityFormDescriptor<User> {
  @override
  StepForm prepare(User? entity) {
    final isEdit = entity != null;

    return StepForm(
      title: isEdit ? 'Edit User' : 'Create User',
      steps: [
        FormStep(
          title: 'Basic Information',
          form: FormBuilder(
            fields: [
              TextField(
                name: 'name',
                label: 'Full Name',
                value: entity?.name,
                validators: [
                  required(),
                  minLength(3),
                  maxLength(100),
                ],
                helperText: 'Enter the user\'s full name',
              ),
              TextField(
                name: 'email',
                label: 'Email Address',
                value: entity?.email,
                validators: [
                  required(),
                  email(),
                  AsyncValidator(
                    validator: (value) async {
                      if (isEdit && value == entity.email) return null;

                      final api = UserApi(client: SharedApiClient());
                      final exists = await api.emailExists(value!);

                      return exists ? 'Email already in use' : null;
                    },
                  ),
                ],
                keyboardType: TextInputType.emailAddress,
                enabled: !isEdit, // Can't change email after creation
              ),
              TextField(
                name: 'phone',
                label: 'Phone Number',
                value: entity?.phone,
                validators: [
                  pattern(
                    r'^\+?[\d\s-()]+$',
                    'Please enter a valid phone number',
                  ),
                ],
                keyboardType: TextInputType.phone,
              ),
            ],
          ),
        ),
        FormStep(
          title: 'Role & Organization',
          form: FormBuilder(
            fields: [
              SelectField(
                name: 'role',
                label: 'Role',
                value: entity?.role,
                options: UserRole.values.map((role) =>
                    Option(
                      value: role,
                      label: role.displayName,
                      description: _getRoleDescription(role),
                    )).toList(),
                validators: [required()],
              ),
              SelectField(
                name: 'department',
                label: 'Department',
                value: entity?.department,
                optionsBuilder: () async {
                  final departments = await _loadDepartments();
                  return departments.map((dept) =>
                      Option(
                        value: dept.id,
                        label: dept.name,
                      )).toList();
                },
              ),
              SelectField(
                name: 'locationId',
                label: 'Location',
                value: entity?.locationId,
                dependsOn: ['department'],
                optionsBuilder: (formData) async {
                  final deptId = formData['department'] as String?;
                  if (deptId == null) return [];

                  final locations = await _loadLocationsForDepartment(deptId);
                  return locations.map((loc) =>
                      Option(
                        value: loc.id,
                        label: loc.name,
                      )).toList();
                },
              ),
            ],
          ),
        ),
        FormStep(
          title: 'Account Settings',
          form: FormBuilder(
            fields: [
              if (!isEdit)
                ToggleField(
                  name: 'sendWelcomeEmail',
                  label: 'Send Welcome Email',
                  value: true,
                  description: 'Send login credentials to the user',
                ),
              ToggleField(
                name: 'isActive',
                label: 'Active Account',
                value: entity?.isActive ?? true,
                description: 'Inactive users cannot log in',
              ),
              if (isEdit)
                InfoField(
                  label: 'Last Login',
                  value: entity.lastLogin != null
                      ? DateFormat('MMM d, y at h:mm a').format(entity.lastLogin!)
                      : 'Never',
                ),
            ],
          ),
        ),
      ],
      actions: [
        if (isEdit)
          FormAction(
            label: 'Reset Password',
            icon: Icons.lock_reset,
            onTap: (context) async {
              final confirmed = await showDialog<bool>(
                context: context,
                builder: (_) =>
                    AlertDialog(
                      title: const Text('Reset Password?'),
                      content: const Text(
                        'This will send a password reset email to the user.',
                      ),
                      actions: [
                        TextButton(
                          onPressed: () => Navigator.of(context).pop(false),
                          child: const Text('Cancel'),
                        ),
                        ElevatedButton(
                          onPressed: () => Navigator.of(context).pop(true),
                          child: const Text('Reset'),
                        ),
                      ],
                    ),
              );

              if (confirmed == true) {
                final api = UserApi(client: SharedApiClient());
                await api.resetPassword(entity.email);

                if (context.mounted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Password reset email sent'),
                    ),
                  );
                }
              }
            },
          ),
      ],
    );
  }

  @override
  Map<String, dynamic> toFormData(User entity) {
    return {
      'name': entity.name,
      'email': entity.email,
      'phone': entity.phone,
      'role': entity.role,
      'department': entity.department,
      'locationId': entity.locationId,
      'isActive': entity.isActive,
    };
  }

  @override
  User fromFormData(Map<String, dynamic> data, {User? entity}) {
    return User(
      id: entity?.id ?? const Uuid().v4(),
      schemaType: 'users',
      name: data['name'],
      email: data['email'],
      phone: data['phone'],
      role: data['role'],
      department: data['department'],
      locationId: data['locationId'],
      isActive: data['isActive'],
      lastLogin: entity?.lastLogin,
      preferences: entity?.preferences,
      createdAt: entity?.createdAt ?? DateTime.now(),
    );
  }

  String _getRoleDescription(UserRole role) {
    switch (role) {
      case UserRole.admin:
        return 'Full system access';
      case UserRole.manager:
        return 'Manage users and content';
      case UserRole.user:
        return 'Standard user access';
      case UserRole.viewer:
        return 'Read-only access';
    }
  }

  Future<List<Department>> _loadDepartments() async {
    // Load departments
    return []; // Placeholder
  }

  Future<List<Location>> _loadLocationsForDepartment(String deptId) async {
    // Load locations for department
    return []; // Placeholder
  }
}

Complete Configuration

// lib/config/user_config.dart
import 'package:flutter/material.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/user.dart';
import '../api/user_api.dart';
import '../layouts/user_layouts.dart';
import '../forms/user_form.dart';

class UserConfig {
  static final instance = EntityConfiguration<User>(
    metadata: EntityMetadata(
      identifier: 'users',
      name: 'User',
      pluralName: 'Users',
      description: 'System users and administrators',
      icon: Icons.people,
      themeColor: Colors.indigo,
      category: 'Administration',
      route: EntityRouteBuilder.fromIdentifier('users'),
      getHelp: (context) async => '''
# User Management

Manage system users, their roles, and permissions.

## Roles

- **Administrator**: Full system access
- **Manager**: Can manage users and content
- **User**: Standard access to assigned features
- **Viewer**: Read-only access

## Common Tasks

1. **Add a new user**: Click the + button and fill in user details
2. **Reset password**: Edit a user and click "Reset Password"
3. **Deactivate user**: Edit user and toggle "Active Account" off

## Permissions

User permissions are managed through roles and specific permission grants.
Contact your system administrator to modify permissions.
''',
    ),

    api: (client) => UserApi(client: client),

    layouts: UserLayouts.create(),

    form: UserFormDescriptor(),

    actions: EntityActions<User>(
      list: [
        EntityAction(
          label: 'Import Users',
          icon: Icons.upload_file,
          onTap: (context, _) async {
            // Show import dialog
            final file = await FilePicker.platform.pickFiles(
              type: FileType.custom,
              allowedExtensions: ['csv'],
            );

            if (file != null) {
              // Import users from CSV
              await _importUsers(context, file.files.first);
            }
          },
        ),
        EntityAction(
          label: 'Export Users',
          icon: Icons.download,
          onTap: (context, users) async {
            final csv = _generateUsersCsv(users);

            // Save file
            final fileName = 'users_${DateTime
                .now()
                .millisecondsSinceEpoch}.csv';
            await FileSaver.instance.saveFile(
              fileName,
              Uint8List.fromList(csv.codeUnits),
              'csv',
            );

            if (context.mounted) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Exported ${users.length} users')),
              );
            }
          },
        ),
      ],
      single: [
        EntityAction(
          label: 'View Activity',
          icon: Icons.history,
          onTap: (context, [user]) async {
            showDialog(
              context: context,
              builder: (_) =>
                  ActivityDialog(
                    title: 'Activity - ${user.name}',
                    userId: user.id,
                  ),
            );
          },
        ),
        EntityAction(
          label: 'Manage Permissions',
          icon: Icons.security,
          onTap: (context, [user]) async {
            final hasPermission = await context
                .read<EntityPermissionService>()
                .hasPermission(Permission.update('permissions'));

            if (!hasPermission) {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('You do not have permission to manage permissions'),
                ),
              );
              return;
            }

            if (context.mounted) {
              context.go('/users/${user.id}/permissions');
            }
          },
        ),
      ],
    ),
  );

  static Future<void> _importUsers(BuildContext context,
      PlatformFile file,) async {
    // Implementation for importing users from CSV
  }

  static String _generateUsersCsv(List<User> users) {
    final csv = StringBuffer();

    // Header
    csv.writeln('Name,Email,Role,Department,Status,Last Login');

    // Data
    for (final user in users) {
      csv.writeln(
        '${user.name},'
            '${user.email},'
            '${user.role.displayName},'
            '${user.department ?? ""},'
            '${user.isActive ? "Active" : "Inactive"},'
            '${user.lastLogin?.toIso8601String() ?? "Never"}',
      );
    }

    return csv.toString();
  }
}

Example 2: Reference Standards Management

A laboratory reference standards system with expiration tracking and usage history.

Entity Model

// lib/models/reference_standard.dart
import 'package:json_annotation/json_annotation.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';

part 'reference_standard.g.dart';

@JsonSerializable()
class ReferenceStandard extends EntityBase {
  final String name;
  final String catalogNumber;
  final String? lotNumber;
  final String manufacturer;
  final ReferenceStandardType type;
  final DateTime? expiryDate;
  final StorageCondition storageCondition;
  final String? locationId;
  final double? concentration;
  final String? concentrationUnit;
  final double currentQuantity;
  final String quantityUnit;
  final Map<String, dynamic>? specifications;
  final bool isExpired;
  final bool isLowStock;

  ReferenceStandard({
    required super.id,
    required super.schemaType,
    required this.name,
    required this.catalogNumber,
    this.lotNumber,
    required this.manufacturer,
    required this.type,
    this.expiryDate,
    required this.storageCondition,
    this.locationId,
    this.concentration,
    this.concentrationUnit,
    required this.currentQuantity,
    required this.quantityUnit,
    this.specifications,
    super.createdAt,
    super.layout,
    super.modifiers,
  })
      : isExpired = expiryDate != null && expiryDate.isBefore(DateTime.now()),
        isLowStock = currentQuantity < 10; // Simple threshold

  factory ReferenceStandard.fromJson(Map<String, dynamic> json) =>
      _$ReferenceStandardFromJson(json);

  @override
  Map<String, dynamic> toJson() => _$ReferenceStandardToJson(this);

  int get daysUntilExpiry {
    if (expiryDate == null) return -1;
    return expiryDate!.difference(DateTime.now()).inDays;
  }

  String get status {
    if (isExpired) return 'Expired';
    if (daysUntilExpiry <= 30) return 'Expiring Soon';
    if (isLowStock) return 'Low Stock';
    return 'Available';
  }

  Color get statusColor {
    switch (status) {
      case 'Expired':
        return Colors.red;
      case 'Expiring Soon':
        return Colors.orange;
      case 'Low Stock':
        return Colors.amber;
      default:
        return Colors.green;
    }
  }
}

enum ReferenceStandardType {
  @JsonValue('chemical')
  chemical,
  @JsonValue('biological')
  biological,
  @JsonValue('physical')
  physical,
}

enum StorageCondition {
  @JsonValue('room_temperature')
  roomTemperature,
  @JsonValue('refrigerated')
  refrigerated,
  @JsonValue('frozen')
  frozen,
  @JsonValue('ultra_low')
  ultraLow,
}

Dashboard Widget

// lib/widgets/reference_standard_dashboard.dart
import 'package:flutter/material.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/reference_standard.dart';

class ReferenceStandardDashboard extends EntityDashboardWidget<ReferenceStandard> {
  ReferenceStandardDashboard() : super(
    title: 'Reference Standards Overview',
    icon: Icons.science,
    gridWidth: 4,
    gridHeight: 2,
  );

  @override
  Widget build(BuildContext context, DashboardState state) {
    return FutureBuilder<List<ReferenceStandard>>(
      future: _loadStandards(context),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Center(child: CircularProgressIndicator());
        }

        final standards = snapshot.data!;
        final stats = _calculateStats(standards);

        return Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Summary Cards
              SizedBox(
                height: 100,
                child: Row(
                  children: [
                    Expanded(
                      child: _StatCard(
                        title: 'Total Standards',
                        value: stats['total'].toString(),
                        icon: Icons.inventory,
                        color: Colors.blue,
                      ),
                    ),
                    const SizedBox(width: 16),
                    Expanded(
                      child: _StatCard(
                        title: 'Expiring Soon',
                        value: stats['expiringSoon'].toString(),
                        icon: Icons.warning,
                        color: Colors.orange,
                        onTap: () => _showExpiringStandards(context),
                      ),
                    ),
                    const SizedBox(width: 16),
                    Expanded(
                      child: _StatCard(
                        title: 'Low Stock',
                        value: stats['lowStock'].toString(),
                        icon: Icons.inventory_2,
                        color: Colors.amber,
                        onTap: () => _showLowStockStandards(context),
                      ),
                    ),
                    const SizedBox(width: 16),
                    Expanded(
                      child: _StatCard(
                        title: 'Expired',
                        value: stats['expired'].toString(),
                        icon: Icons.dangerous,
                        color: Colors.red,
                        onTap: () => _showExpiredStandards(context),
                      ),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 24),

              // Charts
              Expanded(
                child: Row(
                  children: [
                    // By Type Chart
                    Expanded(
                      child: Card(
                        child: Padding(
                          padding: const EdgeInsets.all(16),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                'Standards by Type',
                                style: Theme
                                    .of(context)
                                    .textTheme
                                    .titleMedium,
                              ),
                              const SizedBox(height: 16),
                              Expanded(
                                child: _TypeDistributionChart(
                                  standards: standards,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                    const SizedBox(width: 16),
                    // Expiry Timeline
                    Expanded(
                      child: Card(
                        child: Padding(
                          padding: const EdgeInsets.all(16),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                'Upcoming Expirations',
                                style: Theme
                                    .of(context)
                                    .textTheme
                                    .titleMedium,
                              ),
                              const SizedBox(height: 16),
                              Expanded(
                                child: _ExpiryTimeline(
                                  standards: standards
                                      .where((s) => s.expiryDate != null && !s.isExpired)
                                      .toList()
                                    ..sort((a, b) =>
                                        a.expiryDate!.compareTo(b.expiryDate!)),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Future<List<ReferenceStandard>> _loadStandards(BuildContext context) async {
    final api = context
        .read<EntityProvider<ReferenceStandard>>()
        .api;
    return api.list();
  }

  Map<String, int> _calculateStats(List<ReferenceStandard> standards) {
    final now = DateTime.now();
    final thirtyDaysFromNow = now.add(const Duration(days: 30));

    return {
      'total': standards.length,
      'expiringSoon': standards.where((s) =>
      s.expiryDate != null &&
          s.expiryDate!.isAfter(now) &&
          s.expiryDate!.isBefore(thirtyDaysFromNow)
      ).length,
      'lowStock': standards
          .where((s) => s.isLowStock && !s.isExpired)
          .length,
      'expired': standards
          .where((s) => s.isExpired)
          .length,
    };
  }

  void _showExpiringStandards(BuildContext context) {
    context.go('/reference-standards?filter=expiring');
  }

  void _showLowStockStandards(BuildContext context) {
    context.go('/reference-standards?filter=low-stock');
  }

  void _showExpiredStandards(BuildContext context) {
    context.go('/reference-standards?filter=expired');
  }
}

class _StatCard extends StatelessWidget {
  final String title;
  final String value;
  final IconData icon;
  final Color color;
  final VoidCallback? onTap;

  const _StatCard({
    required this.title,
    required this.value,
    required this.icon,
    required this.color,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      color: color.withOpacity(0.1),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(8),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(icon, color: color, size: 32),
              const SizedBox(height: 8),
              Text(
                value,
                style: Theme
                    .of(context)
                    .textTheme
                    .headlineMedium
                    ?.copyWith(
                  color: color,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Text(
                title,
                style: Theme
                    .of(context)
                    .textTheme
                    .bodySmall,
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Example 3: Equipment Management with Areas

Complex entity relationships with equipment assigned to areas.

Area-Equipment Relationship Layout

// lib/layouts/equipment_areas_layout.dart
import 'package:flutter/material.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/equipment.dart';
import '../models/area.dart';

class EquipmentAreasLayout extends EntityLayout<Equipment> {
  @override
  Widget build(BuildContext context, EntityListViewState<Equipment> state) {
    return FutureBuilder<List<Area>>(
      future: _loadAreas(context),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Center(child: CircularProgressIndicator());
        }

        final areas = snapshot.data!;
        final equipmentByArea = _groupEquipmentByArea(state.entities, areas);

        return DefaultTabController(
          length: areas.length + 1,
          child: Column(
            children: [
              TabBar(
                isScrollable: true,
                tabs: [
                  const Tab(text: 'All Equipment'),
                  ...areas.map((area) =>
                      Tab(
                        text: area.name,
                        icon: _buildAreaIcon(area, equipmentByArea[area.id]?.length ?? 0),
                      )),
                ],
              ),
              Expanded(
                child: TabBarView(
                  children: [
                    // All equipment view
                    _buildEquipmentGrid(context, state.entities),
                    // Area-specific views
                    ...areas.map((area) =>
                        _buildAreaView(
                          context,
                          area,
                          equipmentByArea[area.id] ?? [],
                        )),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Future<List<Area>> _loadAreas(BuildContext context) async {
    final api = context
        .read<EntityProvider<Area>>()
        .api;
    return api.list();
  }

  Map<String, List<Equipment>> _groupEquipmentByArea(List<Equipment> equipment,
      List<Area> areas,) {
    final grouped = <String, List<Equipment>>{};

    for (final area in areas) {
      grouped[area.id] = equipment
          .where((e) => e.areaId == area.id)
          .toList();
    }

    return grouped;
  }

  Widget _buildAreaIcon(Area area, int equipmentCount) {
    return Badge(
      label: Text(equipmentCount.toString()),
      child: Icon(_getIconForAreaType(area.type)),
    );
  }

  IconData _getIconForAreaType(AreaType type) {
    switch (type) {
      case AreaType.laboratory:
        return Icons.science;
      case AreaType.storage:
        return Icons.warehouse;
      case AreaType.office:
        return Icons.business;
      case AreaType.production:
        return Icons.factory;
      default:
        return Icons.domain;
    }
  }

  Widget _buildEquipmentGrid(BuildContext context, List<Equipment> equipment) {
    return GridView.builder(
      padding: const EdgeInsets.all(16),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        childAspectRatio: 1.5,
        crossAxisSpacing: 16,
        mainAxisSpacing: 16,
      ),
      itemCount: equipment.length,
      itemBuilder: (context, index) {
        final item = equipment[index];
        return _EquipmentCard(equipment: item);
      },
    );
  }

  Widget _buildAreaView(BuildContext context,
      Area area,
      List<Equipment> equipment,) {
    if (equipment.isEmpty) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.inventory_2_outlined,
              size: 64,
              color: Theme
                  .of(context)
                  .disabledColor,
            ),
            const SizedBox(height: 16),
            Text(
              'No equipment in ${area.name}',
              style: Theme
                  .of(context)
                  .textTheme
                  .titleMedium,
            ),
            const SizedBox(height: 8),
            ElevatedButton.icon(
              onPressed: () => _assignEquipmentToArea(context, area),
              icon: const Icon(Icons.add),
              label: const Text('Assign Equipment'),
            ),
          ],
        ),
      );
    }

    return Column(
      children: [
        // Area summary
        Container(
          padding: const EdgeInsets.all(16),
          color: Theme
              .of(context)
              .primaryColor
              .withOpacity(0.1),
          child: Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      area.name,
                      style: Theme
                          .of(context)
                          .textTheme
                          .titleLarge,
                    ),
                    if (area.description != null)
                      Text(
                        area.description!,
                        style: Theme
                            .of(context)
                            .textTheme
                            .bodyMedium,
                      ),
                  ],
                ),
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text(
                    '${equipment.length} Equipment',
                    style: Theme
                        .of(context)
                        .textTheme
                        .titleMedium,
                  ),
                  Text(
                    '${equipment
                        .where((e) => e.status == EquipmentStatus.operational)
                        .length} Operational',
                    style: Theme
                        .of(context)
                        .textTheme
                        .bodySmall,
                  ),
                ],
              ),
            ],
          ),
        ),
        // Equipment grid
        Expanded(
          child: _buildEquipmentGrid(context, equipment),
        ),
      ],
    );
  }

  void _assignEquipmentToArea(BuildContext context, Area area) {
    showDialog(
      context: context,
      builder: (_) => AssignEquipmentDialog(area: area),
    );
  }
}

class _EquipmentCard extends StatelessWidget {
  final Equipment equipment;

  const _EquipmentCard({required this.equipment});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: InkWell(
        onTap: () => context.go('/equipment/${equipment.id}'),
        borderRadius: BorderRadius.circular(8),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Icon(
                    _getEquipmentIcon(equipment.type),
                    size: 32,
                    color: equipment.statusColor,
                  ),
                  const Spacer(),
                  _StatusBadge(status: equipment.status),
                ],
              ),
              const SizedBox(height: 8),
              Text(
                equipment.name,
                style: Theme
                    .of(context)
                    .textTheme
                    .titleMedium,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
              Text(
                equipment.serialNumber,
                style: Theme
                    .of(context)
                    .textTheme
                    .bodySmall,
              ),
              const Spacer(),
              if (equipment.nextCalibrationDate != null)
                Row(
                  children: [
                    Icon(
                      Icons.schedule,
                      size: 16,
                      color: _getCalibrationColor(equipment.nextCalibrationDate!),
                    ),
                    const SizedBox(width: 4),
                    Text(
                      'Cal: ${DateFormat('MMM d').format(equipment.nextCalibrationDate!)}',
                      style: Theme
                          .of(context)
                          .textTheme
                          .bodySmall
                          ?.copyWith(
                        color: _getCalibrationColor(equipment.nextCalibrationDate!),
                      ),
                    ),
                  ],
                ),
            ],
          ),
        ),
      ),
    );
  }

  IconData _getEquipmentIcon(EquipmentType type) {
    switch (type) {
      case EquipmentType.analytical:
        return Icons.analytics;
      case EquipmentType.measurement:
        return Icons.straighten;
      case EquipmentType.safety:
        return Icons.health_and_safety;
      case EquipmentType.general:
        return Icons.inventory;
    }
  }

  Color _getCalibrationColor(DateTime date) {
    final daysUntil = date
        .difference(DateTime.now())
        .inDays;
    if (daysUntil < 0) return Colors.red;
    if (daysUntil < 30) return Colors.orange;
    return Colors.green;
  }
}

class _StatusBadge extends StatelessWidget {
  final EquipmentStatus status;

  const _StatusBadge({required this.status});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: status.color.withOpacity(0.2),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Text(
        status.displayName,
        style: TextStyle(
          fontSize: 12,
          color: status.color,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

Summary

These examples demonstrate:

  1. Complete Entity Implementation - From models to UI
  2. Complex Relationships - Managing related entities
  3. Advanced UI Patterns - Custom layouts and interactions
  4. Real-World Features - Import/export, dashboards, activity tracking
  5. Production-Ready Code - Error handling, validation, permissions

Key takeaways:

  • Use the entity system's built-in features before creating custom solutions
  • Leverage composition for reusable configurations
  • Implement proper error handling and validation
  • Design with relationships in mind
  • Create rich, interactive UIs that enhance productivity

The Vyuh Entity System provides a powerful foundation for building sophisticated business applications with consistent patterns and minimal boilerplate code.