u
This commit is contained in:
@@ -183,6 +183,13 @@
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Organization Chart Tab -->
|
||||
<MudTabPanel Text="شجرهنامه" Icon="@Icons.Material.Filled.AccountTree">
|
||||
<div class="pa-4">
|
||||
<OrganizationChart />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Account Settings Tab -->
|
||||
<MudTabPanel Text="تنظیمات حساب" Icon="@Icons.Material.Filled.Settings">
|
||||
<div class="pa-4">
|
||||
|
||||
239
src/FrontOffice.Main/Shared/OrganizationChart.razor
Normal file
239
src/FrontOffice.Main/Shared/OrganizationChart.razor
Normal file
@@ -0,0 +1,239 @@
|
||||
@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)
|
||||
{
|
||||
<div class="org-level" id="level-1">
|
||||
@foreach (var child in _currentUser.Children)
|
||||
{
|
||||
<div class="org-node child-node" data-parent-id="@_currentUser.Id">
|
||||
<div class="node-connector"></div>
|
||||
<div class="node-card">
|
||||
<div class="node-avatar">
|
||||
<MudAvatar Size="Size.Medium">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="@(string.IsNullOrEmpty(child.Avatar) ? "images/avatar1.jpg" : child.Avatar)" />
|
||||
</MudAvatar>
|
||||
@if (child.Children?.Any() == true)
|
||||
{
|
||||
<button class="expand-btn" @onclick="() => ToggleChildExpand(child.Id)" data-user-id="@child.Id">
|
||||
<MudIcon Icon="@(child.IsExpanded ? Icons.Material.Filled.Remove : Icons.Material.Filled.Add)" Size="Size.Small" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="node-info">
|
||||
<div class="node-name">@child.FirstName @child.LastName</div>
|
||||
<div class="node-amounts">
|
||||
<div class="personal-amount">
|
||||
<span class="label">خرید شخصی:</span>
|
||||
<span class="amount">@(child.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
|
||||
</div>
|
||||
<div class="team-amount">
|
||||
<span class="label">خرید تیمی:</span>
|
||||
<span class="amount">@(child.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (child.IsExpanded && child.Children?.Any() == true)
|
||||
{
|
||||
<div class="org-sublevel">
|
||||
@foreach (var grandchild in child.Children)
|
||||
{
|
||||
<div class="org-node grandchild-node" data-parent-id="@child.Id">
|
||||
<div class="node-connector"></div>
|
||||
<div class="node-card">
|
||||
<div class="node-avatar">
|
||||
<MudAvatar Size="Size.Small">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="@(string.IsNullOrEmpty(grandchild.Avatar) ? "images/avatar1.jpg" : grandchild.Avatar)" />
|
||||
</MudAvatar>
|
||||
</div>
|
||||
<div class="node-info">
|
||||
<div class="node-name">@grandchild.FirstName @grandchild.LastName</div>
|
||||
<div class="node-amounts">
|
||||
<div class="personal-amount">
|
||||
<span class="label">خرید شخصی:</span>
|
||||
<span class="amount">@(grandchild.PersonalPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
|
||||
</div>
|
||||
<div class="team-amount">
|
||||
<span class="label">خرید تیمی:</span>
|
||||
<span class="amount">@(grandchild.TeamPurchase?.ToThousands().ToCurrencyUnitIRT() ?? "0 تومان")</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@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<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
|
||||
},
|
||||
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 = 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<UserNode>? Children { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
}
|
||||
}
|
||||
229
src/FrontOffice.Main/Shared/OrganizationChart.razor.css
Normal file
229
src/FrontOffice.Main/Shared/OrganizationChart.razor.css
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user