This commit is contained in:
MeysamMoghaddam
2025-10-13 22:40:23 +03:30
parent 560694fc28
commit 8abb5dcd5b
9 changed files with 50 additions and 45 deletions

View File

@@ -1,46 +0,0 @@
@inject IJSRuntime JSRuntime
<div class="org-chart-container">
<div class="org-tree">
<div class="org-node root-node" id="root-node">
<div class="node-card">
<div class="node-avatar">
<MudAvatar Size="Size.Large">
<MudImage ObjectFit="ObjectFit.Cover"
ObjectPosition="ObjectPosition.Center"
Src="images/avatar1.jpg" />
</MudAvatar>
@if (_currentUser?.Children?.Any() == true)
{
<button class="expand-btn" @onclick="ToggleExpand" data-user-id="@_currentUser.Id">
<MudIcon Icon="@(_isExpanded ? Icons.Material.Filled.Remove : Icons.Material.Filled.Add)" Size="Size.Small" />
</button>
}
</div>
<div class="node-info">
<div class="node-name">@(_currentUser?.FirstName) @(_currentUser?.LastName)</div>
<div class="node-amounts">
<div class="personal-amount">
<span class="label">خرید شخصی:</span>
<span class="amount">@(_currentUser?.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
</div>
<div class="team-amount">
<span class="label">خرید تیمی:</span>
<span class="amount">@(_currentUser?.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
</div>
</div>
</div>
</div>
</div>
@if (_isExpanded && _currentUser?.Children?.Any() == true)
{
<CascadingValue Value="this">
<OrganizationChartLevel Nodes="_currentUser.Children" Level="1" />
</CascadingValue>
}
</div>
</div>

View File

@@ -1,147 +0,0 @@
using static FrontOffice.Main.Shared.OrganizationChartLevel;
namespace FrontOffice.Main.Shared;
public partial class OrganizationChart
{
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<UserNode>
{
new UserNode
{
Id = 2,
FirstName = "مریم",
LastName = "احمدی",
Avatar = "images/avatar2.jpg",
PersonalPurchase = 1800000,
TeamPurchase = 8500000,
Children = new List<UserNode>
{
new UserNode
{
Id = 5,
FirstName = "سارا",
LastName = "کریمی",
PersonalPurchase = 1200000,
TeamPurchase = 3200000,
Children = new List<UserNode>
{
new UserNode
{
Id = 8,
FirstName = "نازنین",
LastName = "رضایی",
PersonalPurchase = 950000,
TeamPurchase = 2100000
},
new UserNode
{
Id = 8,
FirstName = "فرزاد",
LastName = "رضایی",
PersonalPurchase = 950000,
TeamPurchase = 2100000
}
}
},
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<UserNode>
{
new UserNode
{
Id = 7,
FirstName = "فاطمه",
LastName = "رضایی",
PersonalPurchase = 1350000,
TeamPurchase = 4100000
},
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();
}
public void ToggleNodeExpand(long userId)
{
var node = FindNode(_currentUser, userId);
if (node != null)
{
node.IsExpanded = !node.IsExpanded;
StateHasChanged();
}
}
private UserNode? FindNode(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 = FindNode(child, userId);
if (found != null) return found;
}
}
return null;
}
}

View File

@@ -1,253 +0,0 @@
.org-chart-container {
width: 100%;
height: 600px;
overflow: auto;
padding: 20px 0;
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #fafafa;
position: relative;
}
.org-tree {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
position: absolute;
}
.org-node {
position: relative;
}
.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: -40px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 20px;
background: #ddd;
}
.org-level::after {
content: '';
position: absolute;
top: -20px;
left: 0;
right: 0;
height: 2px;
background: #ddd;
z-index: 1;
}
.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;
}
.org-sublevel::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: #ddd;
z-index: 1;
}
/* 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);
}

View File

@@ -1,43 +0,0 @@
<div class="org-level" id="level-@Level">
@foreach (var node in Nodes)
{
<div class="org-node child-node" data-parent-id="@node.Id">
<div class="node-connector"></div>
<div class="node-card">
<div class="node-avatar">
<MudAvatar Size="@GetAvatarSize(Level)">
<MudImage ObjectFit="ObjectFit.Cover"
ObjectPosition="ObjectPosition.Center"
Src="@(string.IsNullOrEmpty(node.Avatar) ? "images/avatar1.jpg" : node.Avatar)" />
</MudAvatar>
@if (node.Children?.Any() == true)
{
<button class="expand-btn" @onclick="() => ToggleNodeExpand(node.Id)" data-user-id="@node.Id">
<MudIcon Icon="@(node.IsExpanded ? Icons.Material.Filled.Remove : Icons.Material.Filled.Add)" Size="Size.Small" />
</button>
}
</div>
<div class="node-info">
<div class="node-name">@node.FirstName @node.LastName</div>
<div class="node-amounts">
<div class="personal-amount">
<span class="label">خرید شخصی:</span>
<span class="amount">@(node.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
</div>
<div class="team-amount">
<span class="label">خرید تیمی:</span>
<span class="amount">@(node.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
</div>
</div>
</div>
</div>
@if (node.IsExpanded && node.Children?.Any() == true)
{
<OrganizationChartLevel Nodes="node.Children" Level="@(Level + 1)" />
}
</div>
}
</div>

View File

@@ -1,49 +0,0 @@
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Shared;
public partial class OrganizationChartLevel
{
[Parameter] public List<UserNode>? Nodes { get; set; }
[Parameter] public int Level { get; set; } = 1;
[CascadingParameter] private OrganizationChart? ParentChart { get; set; }
private Size GetAvatarSize(int level)
{
return level switch
{
1 => Size.Large,
2 => Size.Medium,
_ => Size.Small
};
}
private void ToggleNodeExpand(long userId)
{
if (ParentChart != null)
{
ParentChart.ToggleNodeExpand(userId);
}
}
private void SafeToggleNodeExpand(long userId)
{
if (ParentChart != null)
{
ParentChart.ToggleNodeExpand(userId);
}
}
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<UserNode>? Children { get; set; }
public bool IsExpanded { get; set; }
}
}

View File

@@ -1,178 +0,0 @@
/* Organization Chart Level Styles */
.org-level {
display: flex;
justify-content: center;
flex-wrap: nowrap;
position: relative;
margin-top: 80px;
width: max-content;
margin-left: auto;
margin-right: auto;
}
.org-level::before {
content: '';
position: absolute;
top: -80px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 40px;
background: #ddd;
}
.org-level::after {
content: '';
position: absolute;
top: -40px;
left: 0;
right: 0;
height: 2px;
background: #ddd;
z-index: 1;
}
/* Node styles for levels */
.org-node {
position: relative;
margin: 0px 15px 20px 15px;
display: flex;
flex-direction: column;
align-items: center;
min-width: 200px;
}
.node-connector {
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 40px;
background: #ddd;
}
.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;
}
/* Responsive adjustments for levels */
@media (max-width: 768px) {
.org-level {
flex-direction: column;
align-items: center;
margin-top: 20px;
}
.org-node {
margin: 10px 0;
}
.node-card {
min-width: 160px;
max-width: 220px;
padding: 12px;
}
.node-name {
font-size: 13px;
}
.node-amounts {
font-size: 11px;
}
}
/* Animation for level expansion */
.org-level {
animation: fadeInLevel 0.3s ease-in-out;
}
@keyframes fadeInLevel {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}