diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor b/src/FrontOffice.Main/Pages/Profile/Index.razor index e619cdf..76ab08e 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor @@ -183,6 +183,13 @@ + + +
+ +
+
+
diff --git a/src/FrontOffice.Main/Shared/OrganizationChart.razor b/src/FrontOffice.Main/Shared/OrganizationChart.razor new file mode 100644 index 0000000..68327cd --- /dev/null +++ b/src/FrontOffice.Main/Shared/OrganizationChart.razor @@ -0,0 +1,239 @@ +@inject IJSRuntime JSRuntime + +
+
+
+
+
+ + + + @if (_currentUser?.Children?.Any() == true) + { + + } +
+
+
@(_currentUser?.FirstName) @(_currentUser?.LastName)
+
+
+ خرید شخصی: + @(_currentUser?.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان") +
+
+ خرید تیمی: + @(_currentUser?.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان") +
+
+
+
+
+ + @if (_isExpanded && _currentUser?.Children?.Any() == true) + { +
+ @foreach (var child in _currentUser.Children) + { +
+
+
+
+ + + + @if (child.Children?.Any() == true) + { + + } +
+
+
@child.FirstName @child.LastName
+
+
+ خرید شخصی: + @(child.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان") +
+
+ خرید تیمی: + @(child.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان") +
+
+
+
+ + @if (child.IsExpanded && child.Children?.Any() == true) + { +
+ @foreach (var grandchild in child.Children) + { +
+
+
+
+ + + +
+
+
@grandchild.FirstName @grandchild.LastName
+
+
+ خرید شخصی: + @(grandchild.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان") +
+
+ خرید تیمی: + @(grandchild.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان") +
+
+
+
+
+ } +
+ } +
+ } +
+ } +
+
+ +@code { + private UserNode? _currentUser; + private bool _isExpanded; + + protected override async Task OnInitializedAsync() + { + await LoadCurrentUser(); + } + + private async Task LoadCurrentUser() + { + // Mock data - replace with actual API call + _currentUser = new UserNode + { + Id = 1, + FirstName = "علی", + LastName = "رضایی", + Avatar = "images/avatar1.jpg", + PersonalPurchase = 2500000, + TeamPurchase = 15000000, + Children = new List + { + new UserNode + { + Id = 2, + FirstName = "مریم", + LastName = "احمدی", + Avatar = "images/avatar2.jpg", + PersonalPurchase = 1800000, + TeamPurchase = 8500000, + Children = new List + { + new UserNode + { + Id = 5, + FirstName = "سارا", + LastName = "کریمی", + PersonalPurchase = 1200000, + TeamPurchase = 3200000 + }, + new UserNode + { + Id = 6, + FirstName = "امیر", + LastName = "حسینی", + PersonalPurchase = 950000, + TeamPurchase = 1800000 + } + } + }, + new UserNode + { + Id = 3, + FirstName = "حسن", + LastName = "کریمی", + Avatar = "images/avatar3.jpg", + PersonalPurchase = 2200000, + TeamPurchase = 9200000, + Children = new List + { + new UserNode + { + Id = 7, + FirstName = "فاطمه", + LastName = "رضایی", + PersonalPurchase = 1350000, + TeamPurchase = 4100000 + } + } + }, + new UserNode + { + Id = 4, + FirstName = "زهرا", + LastName = "محمدی", + Avatar = "images/avatar4.jpg", + PersonalPurchase = 1950000, + TeamPurchase = 7800000 + } + } + }; + } + + private void ToggleExpand() + { + _isExpanded = !_isExpanded; + StateHasChanged(); + } + + private void ToggleChildExpand(long userId) + { + var child = FindChild(_currentUser, userId); + if (child != null) + { + child.IsExpanded = !child.IsExpanded; + StateHasChanged(); + } + } + + private UserNode? FindChild(UserNode? node, long userId) + { + if (node == null) return null; + if (node.Id == userId) return node; + + if (node.Children != null) + { + foreach (var child in node.Children) + { + var found = FindChild(child, userId); + if (found != null) return found; + } + } + + return null; + } + + public class UserNode + { + public long Id { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Avatar { get; set; } + public long? PersonalPurchase { get; set; } + public long? TeamPurchase { get; set; } + public List? Children { get; set; } + public bool IsExpanded { get; set; } + } +} \ No newline at end of file diff --git a/src/FrontOffice.Main/Shared/OrganizationChart.razor.css b/src/FrontOffice.Main/Shared/OrganizationChart.razor.css new file mode 100644 index 0000000..1d0e4ea --- /dev/null +++ b/src/FrontOffice.Main/Shared/OrganizationChart.razor.css @@ -0,0 +1,229 @@ +.org-chart-container { + width: 100%; + overflow-x: auto; + padding: 20px 0; +} + +.org-tree { + display: flex; + flex-direction: column; + align-items: center; + min-width: 800px; +} + +.org-node { + position: relative; + margin: 20px 0; +} + +.root-node { + margin-bottom: 40px; +} + +.child-node { + display: inline-block; + margin: 0 15px; + vertical-align: top; +} + +.grandchild-node { + display: inline-block; + margin: 20px 10px 0; + vertical-align: top; +} + +.node-card { + background: white; + border-radius: 12px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 2px solid #f0f0f0; + min-width: 200px; + max-width: 280px; + transition: all 0.3s ease; +} + +.node-card:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + border-color: #e0e0e0; +} + +.node-avatar { + position: relative; + text-align: center; + margin-bottom: 12px; +} + +.expand-btn { + position: absolute; + bottom: -8px; + left: 50%; + transform: translateX(-50%); + background: #0380C0; + color: white; + border: none; + border-radius: 50%; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.expand-btn:hover { + background: #026a9e; + transform: translateX(-50%) scale(1.1); +} + +.node-info { + text-align: center; +} + +.node-name { + font-weight: 600; + font-size: 14px; + color: #333; + margin-bottom: 8px; +} + +.node-amounts { + font-size: 12px; +} + +.node-amounts > div { + margin-bottom: 4px; +} + +.personal-amount { + color: #0380C0; +} + +.team-amount { + color: #05AF82; +} + +.label { + font-weight: 500; + margin-left: 4px; +} + +.amount { + font-weight: 600; +} + +.node-connector { + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + width: 2px; + height: 20px; + background: #ddd; +} + +.org-level { + display: flex; + justify-content: center; + flex-wrap: wrap; + position: relative; +} + +.org-level::before { + content: ''; + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + width: 2px; + height: 20px; + background: #ddd; +} + +.org-sublevel { + display: flex; + justify-content: center; + flex-wrap: wrap; + margin-top: 20px; + position: relative; +} + +.org-sublevel::before { + content: ''; + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + width: 2px; + height: 20px; + background: #ddd; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .org-chart-container { + padding: 10px 0; + } + + .org-tree { + min-width: 100%; + } + + .child-node { + margin: 0 8px; + } + + .grandchild-node { + margin: 15px 5px 0; + } + + .node-card { + min-width: 160px; + max-width: 220px; + padding: 12px; + } + + .node-name { + font-size: 13px; + } + + .node-amounts { + font-size: 11px; + } +} + +/* Animation for expand/collapse */ +.org-level, +.org-sublevel { + animation: fadeIn 0.3s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Connector lines styling */ +.node-connector, +.org-level::before, +.org-sublevel::before { + background: linear-gradient(to bottom, #ddd, #bbb); +} + +/* Hover effects */ +.node-card:hover .node-avatar .mud-avatar { + transform: scale(1.05); + transition: transform 0.2s ease; +} + +.expand-btn:active { + transform: translateX(-50%) scale(0.95); +} \ No newline at end of file