remove the package-lock json ,also fix the api call for project-management and fix some style in dashboard-home

This commit is contained in:
Saeed AB 2025-09-21 16:30:18 +03:30
parent 85ae658c85
commit 97331fdf34
7 changed files with 1421 additions and 8323 deletions

View File

@ -1,429 +1,433 @@
@import "tailwindcss"; @import "tailwindcss";
@import url(/font/fontiran.css); @import url(/font/iranfont.css);
@theme { @theme {
--font-sans: /* Teal color scale */
"Vazirmatn", "Inter", ui-sans-serif, system-ui, sans-serif, --color-teal-50: #f0fdfa;
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --color-teal-100: #ccfbf1;
--color-teal-200: #99f6e4;
--color-teal-300: #5eead4;
--color-teal-400: #2dd4bf;
--color-teal-500: #14b8a6;
--color-teal-600: #0d9488;
--color-teal-700: #0f766e;
--color-teal-800: #115e59;
--color-teal-900: #134e4a;
--color-teal-950: #042f2e;
/* Teal color scale */ /* Slate color scale */
--color-teal-50: #f0fdfa; --color-slate-50: #f8fafc;
--color-teal-100: #ccfbf1; --color-slate-100: #f1f5f9;
--color-teal-200: #99f6e4; --color-slate-200: #e2e8f0;
--color-teal-300: #5eead4; --color-slate-300: #cbd5e1;
--color-teal-400: #2dd4bf; --color-slate-400: #94a3b8;
--color-teal-500: #14b8a6; --color-slate-500: #64748b;
--color-teal-600: #0d9488; --color-slate-600: #475569;
--color-teal-700: #0f766e; --color-slate-700: #334155;
--color-teal-800: #115e59; --color-slate-800: #1e293b;
--color-teal-900: #134e4a; --color-slate-900: #0f172a;
--color-teal-950: #042f2e; --color-slate-950: #020617;
/* Slate color scale */ --color-pr-green: #3aea83;
--color-slate-50: #f8fafc; --color-pr-blue: #69c8ea;
--color-slate-100: #f1f5f9; --color-pr-red: #f76276;
--color-slate-200: #e2e8f0; --color-pr-gray: #3f415a;
--color-slate-300: #cbd5e1;
--color-slate-400: #94a3b8;
--color-slate-500: #64748b;
--color-slate-600: #475569;
--color-slate-700: #334155;
--color-slate-800: #1e293b;
--color-slate-900: #0f172a;
--color-slate-950: #020617;
--color-pr-green : #3AEA83;
--color-pr-blue : #69C8EA;
--color-pr-red : #F76276;
--color-pr-gray : #3F415A;
} }
html, html,
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
@media (prefers-color-scheme: dark) {
@media (prefers-color-scheme: dark) { color-scheme: dark;
color-scheme: dark; }
}
} }
body { body {
font-family: IRANYekanX !important; font-family: IRANYekanX;
direction: rtl; direction: rtl;
background-color: #cdcdcd; background-color: #cdcdcd;
margin: 0; margin: 0;
} }
h1, h2, h3, h4, h5, h6,input, textarea { h1,
font-family: IRANYekanX !important; h2,
h3,
h4,
h5,
h6,
input,
textarea {
font-family: IRANYekanX;
} }
/* RTL Support */ /* RTL Support */
html[dir="rtl"] { html[dir="rtl"] {
direction: rtl; direction: rtl;
} }
html[dir="rtl"] body { html[dir="rtl"] body {
text-align: right; text-align: right;
} }
@theme inline { @theme inline {
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px); --radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-card: var(--card); --color-card: var(--card);
--color-card-foreground: var(--card-foreground); --color-card-foreground: var(--card-foreground);
--color-popover: var(--popover); --color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground); --color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground); --color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted); --color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground); --color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent); --color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground); --color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
--color-ring: var(--ring); --color-ring: var(--ring);
} }
:root { :root {
--radius: 0.5rem; --radius: 0.5rem;
--color-green: #3AEA83; --color-green: #3aea83;
--color-blue: #69C8EA; --color-blue: #69c8ea;
--color-red: #F76276; --color-red: #f76276;
/* primary colors */ /* primary colors */
--color-pr-gray : #3F415A; --color-pr-gray: #3f415a;
--color-pr-green : var(--color-green); --color-pr-green: var(--color-green);
/* Light theme colors */ /* Light theme colors */
--background: #ffffff; --background: #ffffff;
--foreground: #0a0a0a; --foreground: #0a0a0a;
--card: #ffffff; --card: #ffffff;
--card-foreground: #0a0a0a; --card-foreground: #0a0a0a;
--popover: #ffffff; --popover: #ffffff;
--popover-foreground: #0a0a0a; --popover-foreground: #0a0a0a;
--primary: #22c55e; --primary: #22c55e;
--primary-foreground: #ffffff; --primary-foreground: #ffffff;
--secondary: #f5f5f5; --secondary: #f5f5f5;
--secondary-foreground: #0a0a0a; --secondary-foreground: #0a0a0a;
--muted: #f5f5f5; --muted: #f5f5f5;
--muted-foreground: #737373; --muted-foreground: #737373;
--accent: #f5f5f5; --accent: #f5f5f5;
--accent-foreground: #0a0a0a; --accent-foreground: #0a0a0a;
--destructive: #ef4444; --destructive: #ef4444;
--destructive-foreground: #ffffff; --destructive-foreground: #ffffff;
--border: #e5e5e5; --border: #e5e5e5;
--input: #e5e5e5; --input: #e5e5e5;
--ring: #22c55e; --ring: #22c55e;
/* Primary color scale */ /* Primary color scale */
--color-primary-50: #f0fdf4; --color-primary-50: #f0fdf4;
--color-primary-100: #dcfce7; --color-primary-100: #dcfce7;
--color-primary-200: #bbf7d0; --color-primary-200: #bbf7d0;
--color-primary-300: #86efac; --color-primary-300: #86efac;
--color-primary-400: #4ade80; --color-primary-400: #4ade80;
--color-primary-500: #22c55e; --color-primary-500: #22c55e;
--color-primary-600: #16a34a; --color-primary-600: #16a34a;
--color-primary-700: #15803d; --color-primary-700: #15803d;
--color-primary-800: #166534; --color-primary-800: #166534;
--color-primary-900: #14532d; --color-primary-900: #14532d;
--color-primary-950: #052e16; --color-primary-950: #052e16;
/* Secondary color scale (Blue) */ /* Secondary color scale (Blue) */
--color-secondary-50: #eff6ff; --color-secondary-50: #eff6ff;
--color-secondary-100: #dbeafe; --color-secondary-100: #dbeafe;
--color-secondary-200: #bfdbfe; --color-secondary-200: #bfdbfe;
--color-secondary-300: #93c5fd; --color-secondary-300: #93c5fd;
--color-secondary-400: #60a5fa; --color-secondary-400: #60a5fa;
--color-secondary-500: #3b82f6; --color-secondary-500: #3b82f6;
--color-secondary-600: #2563eb; --color-secondary-600: #2563eb;
--color-secondary-700: #1d4ed8; --color-secondary-700: #1d4ed8;
--color-secondary-800: #1e40af; --color-secondary-800: #1e40af;
--color-secondary-900: #1e3a8a; --color-secondary-900: #1e3a8a;
--color-secondary-950: #172554; --color-secondary-950: #172554;
/* Neutral color scale */ /* Neutral color scale */
--color-neutral-50: #fafafa; --color-neutral-50: #fafafa;
--color-neutral-100: #f5f5f5; --color-neutral-100: #f5f5f5;
--color-neutral-200: #e5e5e5; --color-neutral-200: #e5e5e5;
--color-neutral-300: #d4d4d4; --color-neutral-300: #d4d4d4;
--color-neutral-400: #a3a3a3; --color-neutral-400: #a3a3a3;
--color-neutral-500: #737373; --color-neutral-500: #737373;
--color-neutral-600: #525252; --color-neutral-600: #525252;
--color-neutral-700: #404040; --color-neutral-700: #404040;
--color-neutral-800: #262626; --color-neutral-800: #262626;
--color-neutral-900: #171717; --color-neutral-900: #171717;
--color-neutral-950: #0a0a0a; --color-neutral-950: #0a0a0a;
/* Status colors */ /* Status colors */
--color-success-50: #f0fdf4; --color-success-50: #f0fdf4;
--color-success-100: #dcfce7; --color-success-100: #dcfce7;
--color-success-500: #22c55e; --color-success-500: #22c55e;
--color-success-600: #16a34a; --color-success-600: #16a34a;
--color-success-700: #15803d; --color-success-700: #15803d;
--color-success-900: #14532d; --color-success-900: #14532d;
--color-error-50: #fef2f2; --color-error-50: #fef2f2;
--color-error-100: #fee2e2; --color-error-100: #fee2e2;
--color-error-500: #ef4444; --color-error-500: #ef4444;
--color-error-600: #dc2626; --color-error-600: #dc2626;
--color-error-700: #b91c1c; --color-error-700: #b91c1c;
--color-error-900: #7f1d1d; --color-error-900: #7f1d1d;
--color-warning-50: #fffbeb; --color-warning-50: #fffbeb;
--color-warning-100: #fef3c7; --color-warning-100: #fef3c7;
--color-warning-500: #f59e0b; --color-warning-500: #f59e0b;
--color-warning-600: #d97706; --color-warning-600: #d97706;
--color-warning-700: #b45309; --color-warning-700: #b45309;
--color-warning-900: #78350f; --color-warning-900: #78350f;
--color-info-50: #eff6ff; --color-info-50: #eff6ff;
--color-info-100: #dbeafe; --color-info-100: #dbeafe;
--color-info-500: #3b82f6; --color-info-500: #3b82f6;
--color-info-600: #2563eb; --color-info-600: #2563eb;
--color-info-700: #1d4ed8; --color-info-700: #1d4ed8;
--color-info-900: #1e3a8a; --color-info-900: #1e3a8a;
/* Teal colors */ /* Teal colors */
--color-teal-50: #f0fdfa; --color-teal-50: #f0fdfa;
--color-teal-100: #ccfbf1; --color-teal-100: #ccfbf1;
--color-teal-200: #99f6e4; --color-teal-200: #99f6e4;
--color-teal-300: #5eead4; --color-teal-300: #5eead4;
--color-teal-400: #2dd4bf; --color-teal-400: #2dd4bf;
--color-teal-500: #14b8a6; --color-teal-500: #14b8a6;
--color-teal-600: #0d9488; --color-teal-600: #0d9488;
--color-teal-700: #0f766e; --color-teal-700: #0f766e;
--color-teal-800: #115e59; --color-teal-800: #115e59;
--color-teal-900: #134e4a; --color-teal-900: #134e4a;
/* Dark colors */ /* Dark colors */
--color-dark-50: #f8fafc; --color-dark-50: #f8fafc;
--color-dark-100: #f1f5f9; --color-dark-100: #f1f5f9;
--color-dark-200: #e2e8f0; --color-dark-200: #e2e8f0;
--color-dark-300: #cbd5e1; --color-dark-300: #cbd5e1;
--color-dark-400: #94a3b8; --color-dark-400: #94a3b8;
--color-dark-500: #64748b; --color-dark-500: #64748b;
--color-dark-600: #475569; --color-dark-600: #475569;
--color-dark-700: #334155; --color-dark-700: #334155;
--color-dark-800: #1e293b; --color-dark-800: #1e293b;
--color-dark-900: #0f172a; --color-dark-900: #0f172a;
--color-dark-950: #020617; --color-dark-950: #020617;
/* Login specific colors */ /* Login specific colors */
--color-login-primary: var(--color-green); --color-login-primary: var(--color-green);
--color-login-dark-start: #464861; --color-login-dark-start: #464861;
--color-login-dark-end: #111628; --color-login-dark-end: #111628;
} }
.dark { .dark {
/* Dark theme colors */ /* Dark theme colors */
--background: #020617; --background: #020617;
--foreground: #f8fafc; --foreground: #f8fafc;
--card: #0f172a; --card: #0f172a;
--card-foreground: #f8fafc; --card-foreground: #f8fafc;
--popover: #0f172a; --popover: #0f172a;
--popover-foreground: #f8fafc; --popover-foreground: #f8fafc;
--primary: #22c55e; --primary: #22c55e;
--primary-foreground: #0a0a0a; --primary-foreground: #0a0a0a;
--secondary: #1e293b; --secondary: #1e293b;
--secondary-foreground: #f8fafc; --secondary-foreground: #f8fafc;
--muted: #1e293b; --muted: #1e293b;
--muted-foreground: #94a3b8; --muted-foreground: #94a3b8;
--accent: #1e293b; --accent: #1e293b;
--accent-foreground: #f8fafc; --accent-foreground: #f8fafc;
--destructive: #ef4444; --destructive: #ef4444;
--destructive-foreground: #f8fafc; --destructive-foreground: #f8fafc;
--border: #1e293b; --border: #1e293b;
--input: #1e293b; --input: #1e293b;
--ring: #22c55e; --ring: #22c55e;
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body {
@apply bg-background text-foreground;
}
body {
@apply bg-background text-foreground;
}
} }
/* Persian/Farsi font class */ /* Persian/Farsi font class */
.font-persian { .font-persian {
font-family: IRANYekanX; font-family: "IRANYekanX";
} }
/* Custom utility classes */ /* Custom utility classes */
.gradient-primary { .gradient-primary {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
var(--color-primary-500) 0%, var(--color-primary-500) 0%,
var(--color-primary-600) 100% var(--color-primary-600) 100%
); );
} }
.gradient-secondary { .gradient-secondary {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
var(--color-secondary-500) 0%, var(--color-secondary-500) 0%,
var(--color-secondary-600) 100% var(--color-secondary-600) 100%
); );
} }
.gradient-background { .gradient-background {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
var(--color-neutral-50) 0%, var(--color-neutral-50) 0%,
var(--color-neutral-100) 100% var(--color-neutral-100) 100%
); );
} }
.dark .gradient-background { .dark .gradient-background {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
var(--color-neutral-900) 0%, var(--color-neutral-900) 0%,
var(--color-neutral-800) 100% var(--color-neutral-800) 100%
); );
} }
/* Login page specific styles */ /* Login page specific styles */
.login-page { .login-page {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
var(--color-login-dark-start) 0%, var(--color-login-dark-start) 0%,
var(--color-login-dark-end) 100% var(--color-login-dark-end) 100%
); );
} }
.login-sidebar { .login-sidebar {
background: var(--color-login-primary); background: var(--color-login-primary);
} }
/* Animation classes */ /* Animation classes */
.animate-fade-in { .animate-fade-in {
animation: fadeIn 0.3s ease-in-out; animation: fadeIn 0.3s ease-in-out;
} }
.animate-slide-up { .animate-slide-up {
animation: slideUp 0.3s ease-out; animation: slideUp 0.3s ease-out;
} }
.animate-slide-down { .animate-slide-down {
animation: slideDown 0.3s ease-out; animation: slideDown 0.3s ease-out;
} }
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
} }
to { to {
opacity: 1; opacity: 1;
} }
} }
@keyframes slideUp { @keyframes slideUp {
from { from {
transform: translateY(10px); transform: translateY(10px);
opacity: 0; opacity: 0;
} }
to { to {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
} }
@keyframes slideDown { @keyframes slideDown {
from { from {
transform: translateY(-10px); transform: translateY(-10px);
opacity: 0; opacity: 0;
} }
to { to {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
} }
/* Toast customization for RTL */ /* Toast customization for RTL */
.Toaster__toast { .Toaster__toast {
direction: rtl; direction: rtl;
text-align: right; text-align: right;
} }
/* Form focus styles */ /* Form focus styles */
.form-input:focus-within { .form-input:focus-within {
@apply ring-2 ring-primary/20 border-primary; @apply ring-2 ring-primary/20 border-primary;
} }
/* Button hover effects */ /* Button hover effects */
.btn-hover-scale:hover { .btn-hover-scale:hover {
transform: scale(1.02); transform: scale(1.02);
transition: transform 0.2s ease-in-out; transition: transform 0.2s ease-in-out;
} }
/* Custom shadows */ /* Custom shadows */
.shadow-primary { .shadow-primary {
box-shadow: box-shadow:
0 4px 6px -1px rgb(34 197 94 / 0.1), 0 4px 6px -1px rgb(34 197 94 / 0.1),
0 2px 4px -2px rgb(34 197 94 / 0.1); 0 2px 4px -2px rgb(34 197 94 / 0.1);
} }
.shadow-error { .shadow-error {
box-shadow: box-shadow:
0 4px 6px -1px rgb(239 68 68 / 0.1), 0 4px 6px -1px rgb(239 68 68 / 0.1),
0 2px 4px -2px rgb(239 68 68 / 0.1); 0 2px 4px -2px rgb(239 68 68 / 0.1);
} }
/* Loading states */ /* Loading states */
.loading-shimmer { .loading-shimmer {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%; background-size: 200% 100%;
animation: shimmer 1.5s infinite; animation: shimmer 1.5s infinite;
} }
.dark .loading-shimmer { .dark .loading-shimmer {
background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%); background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
background-size: 200% 100%; background-size: 200% 100%;
} }
@keyframes shimmer { @keyframes shimmer {
0% { 0% {
background-position: -200% 0; background-position: -200% 0;
} }
100% { 100% {
background-position: 200% 0; background-position: 200% 0;
} }
} }
/* Table/container specific custom dark scrollbar */ /* Table/container specific custom dark scrollbar */
.custom-scrollbar { .custom-scrollbar {
scrollbar-width: thin; /* Firefox */ scrollbar-width: thin; /* Firefox */
scrollbar-color: rgba(100, 116, 139, 0.6) transparent; /* thumb track */ scrollbar-color: rgba(100, 116, 139, 0.6) transparent; /* thumb track */
} }
.custom-scrollbar::-webkit-scrollbar { .custom-scrollbar::-webkit-scrollbar {
width: 2px; width: 2px;
height: 2px; height: 2px;
} }
.custom-scrollbar::-webkit-scrollbar-track { .custom-scrollbar::-webkit-scrollbar-track {
background: rgba(241, 245, 249, 0.6); /* slate-100 */ background: rgba(241, 245, 249, 0.6); /* slate-100 */
} }
.custom-scrollbar::-webkit-scrollbar-thumb { .custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, rgba(16, 185, 129, 0.6), rgba(16, 185, 129, 0.9)); /* emerald */ background: linear-gradient(
border-radius: 9999px; to bottom,
border: .5px solid transparent; rgba(16, 185, 129, 0.6),
background-clip: padding-box; rgba(16, 185, 129, 0.9)
); /* emerald */
border-radius: 9999px;
border: 0.5px solid transparent;
background-clip: padding-box;
} }
.custom-scrollbar:hover::-webkit-scrollbar-thumb { .custom-scrollbar:hover::-webkit-scrollbar-thumb {
@ -438,52 +442,51 @@ html[dir="rtl"] body {
.dark .custom-scrollbar::-webkit-scrollbar-thumb { .dark .custom-scrollbar::-webkit-scrollbar-thumb {
} }
:root { :root {
--form-control-color: #3F415A; --form-control-color: #3f415a;
--form-control-disabled: ##5F6284; --form-control-disabled: ##5f6284;
--form-background: #3AEA83; --form-background: #3aea83;
} }
input[type="checkbox"] { input[type="checkbox"] {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
margin: 0; margin: 0;
font: inherit; font: inherit;
color: #5F6284; color: #5f6284;
background-color: transparent; background-color: transparent;
width: 1.15em; width: 1.15em;
height: 1.15em; height: 1.15em;
border: 1px solid #5F6284; border: 1px solid #5f6284;
border-radius: 0.15em; border-radius: 0.15em;
transform: translateY(-0.075em); transform: translateY(-0.075em);
display: grid; display: grid;
place-content: center; place-content: center;
cursor: pointer; cursor: pointer;
} }
input[type="checkbox"]::before { input[type="checkbox"]::before {
content: ""; content: "";
width: 0.65em; width: 0.65em;
height: 0.65em; height: 0.65em;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
transform: scale(0); transform: scale(0);
transform-origin: bottom left; transform-origin: bottom left;
transition: 120ms transform ease-in-out; transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em var(--form-control-color); box-shadow: inset 1em 1em var(--form-control-color);
} }
input[type="checkbox"]:checked::before { input[type="checkbox"]:checked::before {
transform: scale(1); transform: scale(1);
} }
input[type="checkbox"]:checked { input[type="checkbox"]:checked {
background-color: #3AEA83 ; background-color: #3aea83;
border: 1px solid transparent; border: 1px solid transparent;
} }
input[type="checkbox"]:disabled { input[type="checkbox"]:disabled {
--form-control-color: var(--form-control-disabled); --form-control-color: var(--form-control-disabled);
color: var(--form-control-disabled); color: var(--form-control-disabled);
cursor: not-allowed; cursor: not-allowed;
} }

View File

@ -595,10 +595,10 @@ export function DashboardHome() {
تحقق ارزش ها تحقق ارزش ها
</p> </p>
<TabsList className="bg-transparent py-2 border m-6 border-gray-600"> <TabsList className="bg-transparent py-2 border m-6 border-gray-600">
<TabsTrigger value="canvas" className=""> <TabsTrigger value="canvas" className="cursor-pointer">
شماتیک شماتیک
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="charts" className=" text-white font-light "> <TabsTrigger value="charts" className=" text-white cursor-pointer font-light ">
مقایسه ای مقایسه ای
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>

View File

@ -97,7 +97,7 @@ export function Header({
{ {
user?.id === 2041 && <button user?.id === 2041 && <button
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian" className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
onClick={redirectHandler}> onClick={redirectHandler}>
<Server className="h-4 w-4" /> <Server className="h-4 w-4" />
ورود به میزکار مدیریت</button> ورود به میزکار مدیریت</button>

View File

@ -289,7 +289,6 @@ export function ProjectManagementPage() {
const { scrollTop, scrollHeight, clientHeight } = scrollContainer; const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
// Trigger load more when scrolled to 90% of the container // Trigger load more when scrolled to 90% of the container
if (scrollPercentage >= 0.9) { if (scrollPercentage >= 0.9) {
loadMore(); loadMore();
@ -518,7 +517,7 @@ export function ProjectManagementPage() {
label: "میزان نوآوری", label: "میزان نوآوری",
palette: ["#C3BF8B", "#10B981", "#F59E0B", "#EF4444", "#FDE68A"], palette: ["#C3BF8B", "#10B981", "#F59E0B", "#EF4444", "#FDE68A"],
}, },
{ {
key: "executive_phase", key: "executive_phase",
label: "فاز اجرایی", label: "فاز اجرایی",
palette: ["#C3BF8B", "#10B981", "#F59E0B", "#EF4444", "#FDE68A"], palette: ["#C3BF8B", "#10B981", "#F59E0B", "#EF4444", "#FDE68A"],
@ -554,7 +553,10 @@ export function ProjectManagementPage() {
// Compute counts and totals for each category so footer segments can be proportional // Compute counts and totals for each category so footer segments can be proportional
const categoryStats = useMemo(() => { const categoryStats = useMemo(() => {
const stats: Record<string, { counts: Record<string, number>; total: number }> = {}; const stats: Record<
string,
{ counts: Record<string, number>; total: number }
> = {};
categoryDefs.forEach((cat) => { categoryDefs.forEach((cat) => {
const counts: Record<string, number> = {}; const counts: Record<string, number> = {};
let total = 0; let total = 0;
@ -613,7 +615,9 @@ export function ProjectManagementPage() {
.map((p) => calculateRemainingDays((p as any).end_date)) .map((p) => calculateRemainingDays((p as any).end_date))
.filter((v) => v !== null) as number[]; .filter((v) => v !== null) as number[];
res["remaining_time"] = remainingValues.length res["remaining_time"] = remainingValues.length
? Math.round(remainingValues.reduce((a, b) => a + b, 0) / remainingValues.length) ? Math.round(
remainingValues.reduce((a, b) => a + b, 0) / remainingValues.length,
)
: null; : null;
// For other keys, parse numeric values // For other keys, parse numeric values
@ -623,11 +627,17 @@ export function ProjectManagementPage() {
.map((p) => { .map((p) => {
const raw = (p as any)[k]; const raw = (p as any)[k];
if (raw == null) return NaN; if (raw == null) return NaN;
const num = Number(String(raw).toString().replace(/[^0-9.-]/g, "")); const num = Number(
String(raw)
.toString()
.replace(/[^0-9.-]/g, ""),
);
return Number.isFinite(num) ? num : NaN; return Number.isFinite(num) ? num : NaN;
}) })
.filter((n) => !Number.isNaN(n)); .filter((n) => !Number.isNaN(n));
res[k] = vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null; res[k] = vals.length
? vals.reduce((a, b) => a + b, 0) / vals.length
: null;
}); });
return res; return res;
@ -668,11 +678,13 @@ export function ProjectManagementPage() {
const color = getCategoryColor(column.key, value); const color = getCategoryColor(column.key, value);
return ( return (
<span className="inline-flex items-center justify-end flex-row-reverse gap-2 w-full"> <span className="inline-flex items-center justify-end flex-row-reverse gap-2 w-full">
<span className="text-gray-300">{!!value ? String(value) : "-"}</span> <span className="text-gray-300">
{!!value ? String(value) : "-"}
</span>
<span <span
style={{ style={{
backgroundColor: color, backgroundColor: color,
display : !value ? "none" : "block", display: !value ? "none" : "block",
}} }}
className="inline-block w-2 h-2 rounded-full" className="inline-block w-2 h-2 rounded-full"
/> />
@ -689,25 +701,30 @@ export function ProjectManagementPage() {
case "deviation_from_program": case "deviation_from_program":
case "cost_deviation": case "cost_deviation":
return ( return (
<span className="text-sm font-normal">{formatNumber(value as any)}</span> <span className="text-sm font-normal">
{formatNumber(value as any)}
</span>
); );
case "start_date": case "start_date":
case "end_date": case "end_date":
case "done_date": case "done_date":
return ( return (
<span className=" text-sm font-normal">{formatDate(String(value))}</span> <span className=" text-sm font-normal">
{formatDate(String(value))}
</span>
); );
case "project_no": case "project_no":
return ( return (
<Badge <Badge variant="teal" className="border-emerald-500/50">
variant="teal"
className="border-emerald-500/50"
>
{String(value)} {String(value)}
</Badge> </Badge>
); );
case "title": case "title":
return <span className="text-sm font-normal text-white">{String(value)}</span>; return (
<span className="text-sm font-normal text-white">
{String(value)}
</span>
);
case "importance_project": case "importance_project":
return ( return (
<Badge <Badge
@ -741,222 +758,258 @@ export function ProjectManagementPage() {
<div className="relative"> <div className="relative">
<div className="relative overflow-auto custom-scrollbar max-h-[calc(100vh-120px)]"> <div className="relative overflow-auto custom-scrollbar max-h-[calc(100vh-120px)]">
<Table className="table-fixed"> <Table className="table-fixed">
<TableHeader className="sticky top-0 z-50 bg-[#3F415A]"> <TableHeader className="sticky top-0 z-50 bg-[#3F415A]">
<TableRow className="bg-[#3F415A]"> <TableRow className="bg-[#3F415A]">
{columns.map((column) => ( {columns.map((column) => (
<TableHead <TableHead
key={column.key} key={column.key}
className={` text-right font-persian whitespace-nowrap text-white font-semibold bg-[#3F415A] sticky top-0 z-20`} className={` text-right font-persian whitespace-nowrap text-white font-semibold bg-[#3F415A] sticky top-0 z-20`}
style={{ width: column.width}} style={{ width: column.width }}
> >
{column.sortable ? ( {column.sortable ? (
<button <button
onClick={() => handleSort(column.key)} onClick={() => handleSort(column.key)}
className="flex items-center gap-2" className="flex items-center gap-2"
> >
<span>{column.label}</span> <span>{column.label}</span>
{sortConfig.field === column.key ? ( {sortConfig.field === column.key ? (
sortConfig.direction === "asc" ? ( sortConfig.direction === "asc" ? (
<ChevronUp className="w-4 h-4" /> <ChevronUp className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)
) : ( ) : (
<ChevronDown className="w-4 h-4" /> <div className="w-4 h-4" />
) )}
) : ( </button>
<div className="w-4 h-4" /> ) : (
)} column.label
</button> )}
) : ( </TableHead>
column.label ))}
)
}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
// Skeleton loading rows (compact)
Array.from({ length: 20 }).map((_, index) => (
<TableRow
key={`skeleton-${index}`}
className="text-sm leading-tight h-8"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-right border-emerald-500/20 py-1 px-2 break-words"
>
<div className="flex items-center gap-2">
<div className="w-2.5 h-2.5 bg-gray-600 rounded-full animate-pulse" />
<div
className="h-2.5 bg-gray-600 rounded animate-pulse"
style={{ width: `${Math.random() * 60 + 40}%` }}
/>
</div>
</TableCell>
))}
</TableRow>
))
) : projects.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-8"
>
<span className="text-gray-400 font-persian">
هیچ پروژهای یافت نشد
</span>
</TableCell>
</TableRow> </TableRow>
) : ( </TableHeader>
projects.map((project, index) => (
<TableRow <TableBody>
key={`${project.project_no}-${index}`} {loading ? (
className="text-sm leading-tight h-8" // Skeleton loading rows (compact)
> Array.from({ length: 20 }).map((_, index) => (
{columns.map((column) => ( <TableRow
<TableCell key={`skeleton-${index}`}
key={column.key} className="text-sm leading-tight h-8"
className="text-right border-emerald-500/20 text-sm py-1 px-2 break-words" >
> {columns.map((column) => (
{renderCellContent(project, column)} <TableCell
</TableCell> key={column.key}
))} className="text-right border-emerald-500/20 py-1 px-2 break-words"
>
<div className="flex items-center gap-2">
<div className="w-2.5 h-2.5 bg-gray-600 rounded-full animate-pulse" />
<div
className="h-2.5 bg-gray-600 rounded animate-pulse"
style={{
width: `${Math.random() * 60 + 40}%`,
}}
/>
</div>
</TableCell>
))}
</TableRow>
))
) : projects.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-8"
>
<span className="text-gray-400 font-persian">
هیچ پروژهای یافت نشد
</span>
</TableCell>
</TableRow> </TableRow>
)) ) : (
)} projects.map((project, index) => (
</TableBody> <TableRow
key={`${project.project_no}-${index}`}
className="text-sm leading-tight h-8"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-right border-emerald-500/20 text-sm py-1 px-2 break-words"
>
{renderCellContent(project, column)}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
<TableFooter className="sticky py-2 bottom-[-1px] bg-[#3F415A]"> <TableFooter className="sticky py-2 bottom-[-1px] bg-[#3F415A]">
<TableRow> <TableRow>
{columns.map((column, colIndex) => { {columns.map((column, colIndex) => {
// First column: show total projects text similar to API count // First column: show total projects text similar to API count
if (colIndex === 0) { if (colIndex === 0) {
return ( return (
<TableCell key={column.key} className="p-3 text-sm text-white font-semibold font-persian"> <TableCell
کل پروژهها: {formatNumber(actualTotalCount)} key={column.key}
</TableCell> className="p-3 text-sm text-white font-semibold font-persian"
); >
} کل پروژهها: {formatNumber(actualTotalCount)}
// importance_project: render importance bar with specified colors </TableCell>
if (column.key === "importance_project") { );
const imp = importanceCounts; }
const order = ["بالا", "متوسط", "پایین"]; // importance_project: render importance bar with specified colors
const colorFor = (k: string) => { if (column.key === "importance_project") {
switch (k) { const imp = importanceCounts;
case "بالا": const order = ["بالا", "متوسط", "پایین"];
return "var(--color-pr-green)"; // green const colorFor = (k: string) => {
case "متوسط": switch (k) {
return "#69C8EA"; // blue-ish case "بالا":
case "پایین": return "var(--color-pr-green)"; // green
return "#F76276"; // red case "متوسط":
default: return "#69C8EA"; // blue-ish
return "#6B7280"; case "پایین":
} return "#F76276"; // red
}; default:
return ( return "#6B7280";
<TableCell key={column.key} className="p-1"> }
<div className="w-full bg-gray-800 rounded-sm overflow-hidden h-3 flex"> };
{order.map((k) => { return (
const cnt = imp.counts[k] || 0; <TableCell key={column.key} className="p-1">
const widthPercent = imp.total > 0 ? (cnt / imp.total) * 100 : 0; <div className="w-full bg-gray-800 rounded-sm overflow-hidden h-3 flex">
return ( {order.map((k) => {
<div const cnt = imp.counts[k] || 0;
key={k} const widthPercent =
title={`${k} (${cnt})`} imp.total > 0 ? (cnt / imp.total) * 100 : 0;
className="h-3 flex items-center justify-center text-xs font-medium"
style={{ width: `${widthPercent}%`, backgroundColor: colorFor(k) }}
>
</div>
);
})}
</div>
</TableCell>
);
}
// For category-like columns: strategic_theme, value_technology_and_innovation, innovation, executive_phase
const categoryLike = [
"strategic_theme",
"value_technology_and_innovation",
"innovation",
"executive_phase",
];
if (categoryLike.includes(column.key)) {
const stat = categoryStats[column.key] || { counts: {}, total: 0 };
const entries = Object.entries(stat.counts);
return (
<TableCell key={column.key} className="p-1">
<div className="w-full bg-gray-800 rounded-sm overflow-hidden h-3 flex">
{entries.length > 0 ? (
entries.map(([val, cnt]) => {
let color = categoryColorMaps[column.key]?.[val] || "#6B7280";
if (column.key === "executive_phase") {
color = (phaseColors as any)[val] || color;
}
const widthPercent = stat.total > 0 ? (cnt / stat.total) * 100 : 0;
return ( return (
<div <div
key={val} key={k}
title={`${val} (${cnt})`} title={`${k} (${cnt})`}
className="h-3 flex items-center justify-center text-xs font-medium" className="h-3 flex items-center justify-center text-xs font-medium"
style={{ width: `${widthPercent}%`, backgroundColor: color }} style={{
> width: `${widthPercent}%`,
</div> backgroundColor: colorFor(k),
}}
></div>
); );
}) })}
) : ( </div>
<div className="h-3 w-full bg-gray-700" /> </TableCell>
)} );
</div>
</TableCell>
);
}
// remove bar for type_of_innovation (show empty cell)
if (column.key === "type_of_innovation") {
return <TableCell key={column.key} className="p-1" />;
}
// remaining_time: show average days with color (green/red/white)
if (column.key === "remaining_time") {
const avg = numericAverages["remaining_time"] as number | null;
const color = avg == null ? "#9CA3AF" : avg > 0 ? "#3AEA83" : avg < 0 ? "#F76276" : "#FFFFFF";
return (
<TableCell key={column.key} className="p-2 text-right font-medium" style={{ color }}>
{avg == null ? "-" : `${formatNumber(avg)} روز`}
</TableCell>
);
}
// For numeric columns: show average rounded
const numericKeyMap: Record<string, string> = {
renewed_duration: "renewed_duration",
deviation_from_program: "deviation_from_program",
approved_budget: "approved_budget",
budget_spent: "budget_spent",
cost_deviation: "cost_deviation",
};
const mapped = (numericKeyMap as any)[column.key];
if (mapped) {
const avg = numericAverages[mapped] as number | null;
let display = "-";
if (avg != null) {
display = mapped.includes("budget") ? formatCurrency(String(Math.round(avg))) : formatNumber(Math.round(avg));
} }
return (
<TableCell key={column.key} className="p-2 text-right font-medium text-gray-200">
{display}
</TableCell>
);
}
// Default: empty cell to keep alignment // For category-like columns: strategic_theme, value_technology_and_innovation, innovation, executive_phase
return <TableCell key={column.key} className="p-1" />; const categoryLike = [
})} "strategic_theme",
</TableRow> "value_technology_and_innovation",
</TableFooter> "innovation",
"executive_phase",
];
if (categoryLike.includes(column.key)) {
const stat = categoryStats[column.key] || {
counts: {},
total: 0,
};
const entries = Object.entries(stat.counts);
return (
<TableCell key={column.key} className="p-1">
<div className="w-full bg-gray-800 rounded-sm overflow-hidden h-3 flex">
{entries.length > 0 ? (
entries.map(([val, cnt]) => {
let color =
categoryColorMaps[column.key]?.[val] ||
"#6B7280";
if (column.key === "executive_phase") {
color =
(phaseColors as any)[val] || color;
}
const widthPercent =
stat.total > 0
? (cnt / stat.total) * 100
: 0;
return (
<div
key={val}
title={`${val} (${cnt})`}
className="h-3 flex items-center justify-center text-xs font-medium"
style={{
width: `${widthPercent}%`,
backgroundColor: color,
}}
></div>
);
})
) : (
<div className="h-3 w-full bg-gray-700" />
)}
</div>
</TableCell>
);
}
// remove bar for type_of_innovation (show empty cell)
if (column.key === "type_of_innovation") {
return <TableCell key={column.key} className="p-1" />;
}
// remaining_time: show average days with color (green/red/white)
if (column.key === "remaining_time") {
const avg = numericAverages["remaining_time"] as
| number
| null;
const color =
avg == null
? "#9CA3AF"
: avg > 0
? "#3AEA83"
: avg < 0
? "#F76276"
: "#FFFFFF";
return (
<TableCell
key={column.key}
className="p-2 text-right font-medium"
style={{ color }}
>
{avg == null ? "-" : `${formatNumber(avg)} روز`}
</TableCell>
);
}
// For numeric columns: show average rounded
const numericKeyMap: Record<string, string> = {
renewed_duration: "renewed_duration",
deviation_from_program: "deviation_from_program",
approved_budget: "approved_budget",
budget_spent: "budget_spent",
cost_deviation: "cost_deviation",
};
const mapped = (numericKeyMap as any)[column.key];
if (mapped) {
const avg = numericAverages[mapped] as number | null;
let display = "-";
if (avg != null) {
display = mapped.includes("budget")
? formatCurrency(String(Math.round(avg)))
: formatNumber(Math.round(avg));
}
return (
<TableCell
key={column.key}
className="p-2 text-right font-medium text-gray-200"
>
{display}
</TableCell>
);
}
// Default: empty cell to keep alignment
return <TableCell key={column.key} className="p-1" />;
})}
</TableRow>
</TableFooter>
</Table> </Table>
</div> </div>
</div> </div>
@ -973,8 +1026,6 @@ export function ProjectManagementPage() {
)} )}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</DashboardLayout> </DashboardLayout>

6910
package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -17,92 +17,100 @@ This set of fonts are used in this project under the license: (.....)
* *
**/ **/
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 100; font-weight: 100;
src: url('woff/IRANYekanX-Thin.woff') format('woff'), src:
url('woff2/IRANYekanX-Thin.woff2') format('woff2'); url("/font/woff/IRANYekanX-Thin.woff") format("woff"),
url("/font/woff2/IRANYekanX-Thin.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 200; font-weight: 200;
src: url('woff/IRANYekanX-UltraLight.woff') format('woff'), src:
url('woff2/IRANYekanX-UltraLight.woff2') format('woff2'); url("/font/woff/IRANYekanX-UltraLight.woff") format("woff"),
url("/font/woff2/IRANYekanX-UltraLight.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url('woff/IRANYekanX-Light.woff') format('woff'), src:
url('woff2/IRANYekanX-Light.woff2') format('woff2'); url("/font/woff/IRANYekanX-Light.woff") format("woff"),
url("/font/woff2/IRANYekanX-Light.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: url('woff/IRANYekanX-Medium.woff') format('woff'), src:
url('woff2/IRANYekanX-Medium.woff2') format('woff2'); url("/font/woff/IRANYekanX-Medium.woff") format("woff"),
url("/font/woff2/IRANYekanX-Medium.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: url('woff/IRANYekanX-DemiBold.woff') format('woff'), src:
url('woff2/IRANYekanX-DemiBold.woff2') format('woff2'); url("/font/woff/IRANYekanX-DemiBold.woff") format("woff"),
url("/font/woff2/IRANYekanX-DemiBold.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 800; font-weight: 800;
src: url('woff/IRANYekanX-ExtraBold.woff') format('woff'), src:
url('woff2/IRANYekanX-ExtraBold.woff2') format('woff2'); url("/font/woff/IRANYekanX-ExtraBold.woff") format("woff"),
url("/font/woff2/IRANYekanX-ExtraBold.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
src: url('woff/IRANYekanX-Black.woff') format('woff'), src:
url('woff2/IRANYekanX-Black.woff2') format('woff2'); url("/font/woff/IRANYekanX-Black.woff") format("woff"),
url("/font/woff2/IRANYekanX-Black.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 950; font-weight: 950;
src: url('woff/IRANYekanX-ExtraBlack.woff') format('woff'), src:
url('woff2/IRANYekanX-ExtraBlack.woff2') format('woff2'); url("/font/woff/IRANYekanX-ExtraBlack.woff") format("woff"),
url("/font/woff2/IRANYekanX-ExtraBlack.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: 1000; font-weight: 1000;
src: url('woff/IRANYekanX-Heavy.woff') format('woff'), src:
url('woff2/IRANYekanX-Heavy.woff2') format('woff2'); url("/font/woff/IRANYekanX-Heavy.woff") format("woff"),
url("/font/woff2/IRANYekanX-Heavy.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: bold; font-weight: bold;
src: url('woff/IRANYekanX-Bold.woff') format('woff'), src:
url('woff2/IRANYekanX-Bold.woff2') format('woff2'); url("/font/woff/IRANYekanX-Bold.woff") format("woff"),
url("/font/woff2/IRANYekanX-Bold.woff2") format("woff2");
} }
@font-face { @font-face {
font-family: IRANYekanX; font-family: IRANYekanX;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
src: url('woff/IRANYekanX-Regular.woff') format('woff'), src:
url('woff2/IRANYekanX-Regular.woff2') format('woff2'); url("/font/woff/IRANYekanX-Regular.woff") format("woff"),
url("/font/woff2/IRANYekanX-Regular.woff2") format("woff2");
} }