import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:generp/Utils/app_colors.dart'; import 'package:generp/screens/commom/accountsListDetails.dart'; import 'package:graphview/GraphView.dart'; import 'package:provider/provider.dart'; import '../../Models/hrmmodels/ogresponse.dart'; import '../../Notifiers/hrmprovider/orgprovider.dart'; import '../../Utils/commonServices.dart'; import '../../Utils/commonWidgets.dart'; class OrgChartt extends StatefulWidget { const OrgChartt({super.key}); @override State createState() => _OrgCharttState(); } class _OrgCharttState extends State { Map _source = {ConnectivityResult.none: false}; final MyConnectivity _connectivity = MyConnectivity.instance; final Graph graph = Graph(); final Map nodeMap = {}; String connection = 'Offline'; bool _isGraphReady = false; final Map nodeKeys = {}; final TransformationController _transformationController = TransformationController(); final builder = BuchheimWalkerConfiguration() ..levelSeparation = 50 ..siblingSeparation = 20 ..subtreeSeparation = 30 ..orientation = BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM; @override void initState() { super.initState(); _connectivity.initialise(); _connectivity.myStream.listen((source) { setState(() => _source = source); }); _transformationController.value = Matrix4.identity()..scale(0.5); WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of(context, listen: false).ogChart(context).then(( _, ) { setState(() { _isGraphReady = true; }); }); _centerOnRoot("130"); }); } void _centerOnRoot(String rootId) { if (!nodeKeys.containsKey(rootId)) return; final key = nodeKeys[rootId]!; final context = key.currentContext; if (context == null) return; final renderBox = context.findRenderObject() as RenderBox; final rootPosition = renderBox.localToGlobal(Offset.zero); final screenSize = MediaQuery.of(context).size; final rootSize = renderBox.size; final screenCenter = Offset(screenSize.width / 2, screenSize.height / 2); final rootCenter = rootPosition + Offset(rootSize.width / 2, rootSize.height / 2); final dx = screenCenter.dx - rootCenter.dx; final dy = screenCenter.dy - rootCenter.dy; _transformationController.value = Matrix4.identity()..translate(dx, dy); } @override Widget build(BuildContext context) { switch (_source.keys.toList()[0]) { case ConnectivityResult.mobile: case ConnectivityResult.wifi: connection = 'Online'; break; case ConnectivityResult.none: default: connection = 'Offline'; } return Platform.isAndroid ? WillPopScope( onWillPop: () => onBackPressed(context), child: SafeArea(top: false, bottom: true, child: _scaffold(context)), ) : _scaffold(context); } Widget _scaffold(BuildContext context) { return Consumer( builder: (context, provider, child) { graph.nodes.clear(); graph.edges.clear(); nodeMap.clear(); if (provider.rootID != null && provider.employees.isNotEmpty) { _buildGraph(provider); if (kDebugMode) { print( 'Graph built with ${graph.nodes.length} nodes and ${graph.edges.length} edges', ); } } return Scaffold( backgroundColor: Color(0xFFF6F6F8), appBar: appbar2New( context, "Organisation Chart", null, SizedBox.shrink(), 0xFFFFFFF, ), body: provider.rootID == null && provider.employees.isEmpty ? Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(AppColors.app_blue), ), ) : SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ if (graph.nodes.isNotEmpty && _isGraphReady) SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height * 0.8, child: InteractiveViewer( boundaryMargin: const EdgeInsets.all( double.infinity, ), panEnabled: true, scaleEnabled: true, constrained: false, minScale: 0.1, scaleFactor: 1, transformationController: _transformationController, maxScale: 5, panAxis: PanAxis.free, // scaleEnabled: true, // panAxis: PanAxis.free child: GraphView( graph: graph, algorithm: BuchheimWalkerAlgorithm( builder, TreeEdgeRenderer(builder), ), paint: Paint() ..color = AppColors.grey_thick ..strokeWidth = 1 ..style = PaintingStyle.stroke, builder: (Node node) { final key = GlobalKey(); nodeKeys[node.key!.value] = key; final employee = _getEmployeeById( node.key!.value, provider, ); if (employee == null) { if (kDebugMode) { print( 'No employee found for node ID: ${node.key!.value}', ); } return Container( width: 50, height: 82, color: Colors.transparent, child: const Center( child: Text('Invalid Node'), ), ); } return InkResponse( onTap: () { // Navigator.push( // context, // MaterialPageRoute( // builder: // (context) => // employeeDetails( // accountID: employee.id, // ), // ), // ); }, child: Container( key: key, width: 200, padding: const EdgeInsets.all(8.0), // Reduced padding child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 100, height: 100, child: ClipRRect( borderRadius: BorderRadius.circular(50), child: CachedNetworkImage( cacheKey: employee.profile ?? '', fit: BoxFit.cover, imageUrl: employee.profile ?? '', useOldImageOnUrlChange: false, placeholder: (context, url) => CircularProgressIndicator.adaptive(), errorWidget: (context, url, error) => Icon(Icons.error), ), ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: Text( employee.name ?? 'Unknown', maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors.semi_black, fontFamily: "JakartaMedium", fontSize: 25, ), ), ), ], ), SizedBox(height: 10), Row( children: [ Expanded( child: Text( employee.title ?? '', maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors.grey_thick, fontSize: 18, ), // Reduced font size ), ), ], ), ], ), ), ); }, ), ), ), ), ], ), ), ); }, ); } void _buildGraph(Orgprovider provider) { final rootNode = Node.Id(provider.rootID!); nodeMap[provider.rootID!] = rootNode; graph.addNode(rootNode); _addChildren(provider.employees, rootNode, provider); } void _addChildren( List children, Node parentNode, Orgprovider provider, ) { for (var child in children) { if (child.id == null || child.id!.isEmpty) { if (kDebugMode) { print('Skipping invalid child with null or empty ID'); } continue; } if (nodeMap.containsKey(child.id)) { if (kDebugMode) { print('Skipping duplicate node ID: ${child.id}'); } continue; } final childNode = Node.Id(child.id!); nodeMap[child.id!] = childNode; graph.addNode(childNode); graph.addEdge( parentNode, childNode, paint: Paint()..color = AppColors.grey_thick, ); if (child.children != null && child.children!.isNotEmpty) { _addChildren(child.children!, childNode, provider); } } } Children? _getEmployeeById(String id, Orgprovider provider) { if (id == provider.rootID) { return Children( id: provider.rootID, name: provider.rootName, title: provider.rootTitle, profile: provider.rootProfile, ); } return _findEmployeeInList(id, provider.employees); } Children? _findEmployeeInList(String id, List employees) { for (var employee in employees) { if (employee.id == id) { return employee; } if (employee.children != null && employee.children!.isNotEmpty) { final found = _findEmployeeInList(id, employee.children!); if (found != null) { return found; } } } return null; } }