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:
parent
85ae658c85
commit
97331fdf34
657
app/app.css
657
app/app.css
|
|
@ -1,429 +1,433 @@
|
|||
@import "tailwindcss";
|
||||
@import url(/font/fontiran.css);
|
||||
@import url(/font/iranfont.css);
|
||||
|
||||
@theme {
|
||||
--font-sans:
|
||||
"Vazirmatn", "Inter", ui-sans-serif, system-ui, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
/* Teal color scale */
|
||||
--color-teal-50: #f0fdfa;
|
||||
--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 */
|
||||
--color-teal-50: #f0fdfa;
|
||||
--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;
|
||||
/* Slate color scale */
|
||||
--color-slate-50: #f8fafc;
|
||||
--color-slate-100: #f1f5f9;
|
||||
--color-slate-200: #e2e8f0;
|
||||
--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;
|
||||
|
||||
/* Slate color scale */
|
||||
--color-slate-50: #f8fafc;
|
||||
--color-slate-100: #f1f5f9;
|
||||
--color-slate-200: #e2e8f0;
|
||||
--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;
|
||||
--color-pr-green: #3aea83;
|
||||
--color-pr-blue: #69c8ea;
|
||||
--color-pr-red: #f76276;
|
||||
--color-pr-gray: #3f415a;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color-scheme: dark;
|
||||
}
|
||||
@apply bg-background text-foreground;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: IRANYekanX !important;
|
||||
direction: rtl;
|
||||
background-color: #cdcdcd;
|
||||
margin: 0;
|
||||
font-family: IRANYekanX;
|
||||
direction: rtl;
|
||||
background-color: #cdcdcd;
|
||||
margin: 0;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6,input, textarea {
|
||||
font-family: IRANYekanX !important;
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
input,
|
||||
textarea {
|
||||
font-family: IRANYekanX;
|
||||
}
|
||||
|
||||
/* RTL Support */
|
||||
html[dir="rtl"] {
|
||||
direction: rtl;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
html[dir="rtl"] body {
|
||||
text-align: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.5rem;
|
||||
--radius: 0.5rem;
|
||||
|
||||
--color-green: #3AEA83;
|
||||
--color-blue: #69C8EA;
|
||||
--color-red: #F76276;
|
||||
--color-green: #3aea83;
|
||||
--color-blue: #69c8ea;
|
||||
--color-red: #f76276;
|
||||
|
||||
/* primary colors */
|
||||
--color-pr-gray : #3F415A;
|
||||
--color-pr-green : var(--color-green);
|
||||
/* primary colors */
|
||||
--color-pr-gray: #3f415a;
|
||||
--color-pr-green: var(--color-green);
|
||||
|
||||
/* Light theme colors */
|
||||
--background: #ffffff;
|
||||
--foreground: #0a0a0a;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #0a0a0a;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #0a0a0a;
|
||||
--primary: #22c55e;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f5f5f5;
|
||||
--secondary-foreground: #0a0a0a;
|
||||
--muted: #f5f5f5;
|
||||
--muted-foreground: #737373;
|
||||
--accent: #f5f5f5;
|
||||
--accent-foreground: #0a0a0a;
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #ffffff;
|
||||
--border: #e5e5e5;
|
||||
--input: #e5e5e5;
|
||||
--ring: #22c55e;
|
||||
/* Light theme colors */
|
||||
--background: #ffffff;
|
||||
--foreground: #0a0a0a;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #0a0a0a;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #0a0a0a;
|
||||
--primary: #22c55e;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f5f5f5;
|
||||
--secondary-foreground: #0a0a0a;
|
||||
--muted: #f5f5f5;
|
||||
--muted-foreground: #737373;
|
||||
--accent: #f5f5f5;
|
||||
--accent-foreground: #0a0a0a;
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #ffffff;
|
||||
--border: #e5e5e5;
|
||||
--input: #e5e5e5;
|
||||
--ring: #22c55e;
|
||||
|
||||
/* Primary color scale */
|
||||
--color-primary-50: #f0fdf4;
|
||||
--color-primary-100: #dcfce7;
|
||||
--color-primary-200: #bbf7d0;
|
||||
--color-primary-300: #86efac;
|
||||
--color-primary-400: #4ade80;
|
||||
--color-primary-500: #22c55e;
|
||||
--color-primary-600: #16a34a;
|
||||
--color-primary-700: #15803d;
|
||||
--color-primary-800: #166534;
|
||||
--color-primary-900: #14532d;
|
||||
--color-primary-950: #052e16;
|
||||
/* Primary color scale */
|
||||
--color-primary-50: #f0fdf4;
|
||||
--color-primary-100: #dcfce7;
|
||||
--color-primary-200: #bbf7d0;
|
||||
--color-primary-300: #86efac;
|
||||
--color-primary-400: #4ade80;
|
||||
--color-primary-500: #22c55e;
|
||||
--color-primary-600: #16a34a;
|
||||
--color-primary-700: #15803d;
|
||||
--color-primary-800: #166534;
|
||||
--color-primary-900: #14532d;
|
||||
--color-primary-950: #052e16;
|
||||
|
||||
/* Secondary color scale (Blue) */
|
||||
--color-secondary-50: #eff6ff;
|
||||
--color-secondary-100: #dbeafe;
|
||||
--color-secondary-200: #bfdbfe;
|
||||
--color-secondary-300: #93c5fd;
|
||||
--color-secondary-400: #60a5fa;
|
||||
--color-secondary-500: #3b82f6;
|
||||
--color-secondary-600: #2563eb;
|
||||
--color-secondary-700: #1d4ed8;
|
||||
--color-secondary-800: #1e40af;
|
||||
--color-secondary-900: #1e3a8a;
|
||||
--color-secondary-950: #172554;
|
||||
/* Secondary color scale (Blue) */
|
||||
--color-secondary-50: #eff6ff;
|
||||
--color-secondary-100: #dbeafe;
|
||||
--color-secondary-200: #bfdbfe;
|
||||
--color-secondary-300: #93c5fd;
|
||||
--color-secondary-400: #60a5fa;
|
||||
--color-secondary-500: #3b82f6;
|
||||
--color-secondary-600: #2563eb;
|
||||
--color-secondary-700: #1d4ed8;
|
||||
--color-secondary-800: #1e40af;
|
||||
--color-secondary-900: #1e3a8a;
|
||||
--color-secondary-950: #172554;
|
||||
|
||||
/* Neutral color scale */
|
||||
--color-neutral-50: #fafafa;
|
||||
--color-neutral-100: #f5f5f5;
|
||||
--color-neutral-200: #e5e5e5;
|
||||
--color-neutral-300: #d4d4d4;
|
||||
--color-neutral-400: #a3a3a3;
|
||||
--color-neutral-500: #737373;
|
||||
--color-neutral-600: #525252;
|
||||
--color-neutral-700: #404040;
|
||||
--color-neutral-800: #262626;
|
||||
--color-neutral-900: #171717;
|
||||
--color-neutral-950: #0a0a0a;
|
||||
/* Neutral color scale */
|
||||
--color-neutral-50: #fafafa;
|
||||
--color-neutral-100: #f5f5f5;
|
||||
--color-neutral-200: #e5e5e5;
|
||||
--color-neutral-300: #d4d4d4;
|
||||
--color-neutral-400: #a3a3a3;
|
||||
--color-neutral-500: #737373;
|
||||
--color-neutral-600: #525252;
|
||||
--color-neutral-700: #404040;
|
||||
--color-neutral-800: #262626;
|
||||
--color-neutral-900: #171717;
|
||||
--color-neutral-950: #0a0a0a;
|
||||
|
||||
/* Status colors */
|
||||
--color-success-50: #f0fdf4;
|
||||
--color-success-100: #dcfce7;
|
||||
--color-success-500: #22c55e;
|
||||
--color-success-600: #16a34a;
|
||||
--color-success-700: #15803d;
|
||||
--color-success-900: #14532d;
|
||||
/* Status colors */
|
||||
--color-success-50: #f0fdf4;
|
||||
--color-success-100: #dcfce7;
|
||||
--color-success-500: #22c55e;
|
||||
--color-success-600: #16a34a;
|
||||
--color-success-700: #15803d;
|
||||
--color-success-900: #14532d;
|
||||
|
||||
--color-error-50: #fef2f2;
|
||||
--color-error-100: #fee2e2;
|
||||
--color-error-500: #ef4444;
|
||||
--color-error-600: #dc2626;
|
||||
--color-error-700: #b91c1c;
|
||||
--color-error-900: #7f1d1d;
|
||||
--color-error-50: #fef2f2;
|
||||
--color-error-100: #fee2e2;
|
||||
--color-error-500: #ef4444;
|
||||
--color-error-600: #dc2626;
|
||||
--color-error-700: #b91c1c;
|
||||
--color-error-900: #7f1d1d;
|
||||
|
||||
--color-warning-50: #fffbeb;
|
||||
--color-warning-100: #fef3c7;
|
||||
--color-warning-500: #f59e0b;
|
||||
--color-warning-600: #d97706;
|
||||
--color-warning-700: #b45309;
|
||||
--color-warning-900: #78350f;
|
||||
--color-warning-50: #fffbeb;
|
||||
--color-warning-100: #fef3c7;
|
||||
--color-warning-500: #f59e0b;
|
||||
--color-warning-600: #d97706;
|
||||
--color-warning-700: #b45309;
|
||||
--color-warning-900: #78350f;
|
||||
|
||||
--color-info-50: #eff6ff;
|
||||
--color-info-100: #dbeafe;
|
||||
--color-info-500: #3b82f6;
|
||||
--color-info-600: #2563eb;
|
||||
--color-info-700: #1d4ed8;
|
||||
--color-info-900: #1e3a8a;
|
||||
--color-info-50: #eff6ff;
|
||||
--color-info-100: #dbeafe;
|
||||
--color-info-500: #3b82f6;
|
||||
--color-info-600: #2563eb;
|
||||
--color-info-700: #1d4ed8;
|
||||
--color-info-900: #1e3a8a;
|
||||
|
||||
/* Teal colors */
|
||||
--color-teal-50: #f0fdfa;
|
||||
--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;
|
||||
/* Teal colors */
|
||||
--color-teal-50: #f0fdfa;
|
||||
--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;
|
||||
|
||||
/* Dark colors */
|
||||
--color-dark-50: #f8fafc;
|
||||
--color-dark-100: #f1f5f9;
|
||||
--color-dark-200: #e2e8f0;
|
||||
--color-dark-300: #cbd5e1;
|
||||
--color-dark-400: #94a3b8;
|
||||
--color-dark-500: #64748b;
|
||||
--color-dark-600: #475569;
|
||||
--color-dark-700: #334155;
|
||||
--color-dark-800: #1e293b;
|
||||
--color-dark-900: #0f172a;
|
||||
--color-dark-950: #020617;
|
||||
/* Dark colors */
|
||||
--color-dark-50: #f8fafc;
|
||||
--color-dark-100: #f1f5f9;
|
||||
--color-dark-200: #e2e8f0;
|
||||
--color-dark-300: #cbd5e1;
|
||||
--color-dark-400: #94a3b8;
|
||||
--color-dark-500: #64748b;
|
||||
--color-dark-600: #475569;
|
||||
--color-dark-700: #334155;
|
||||
--color-dark-800: #1e293b;
|
||||
--color-dark-900: #0f172a;
|
||||
--color-dark-950: #020617;
|
||||
|
||||
/* Login specific colors */
|
||||
--color-login-primary: var(--color-green);
|
||||
--color-login-dark-start: #464861;
|
||||
--color-login-dark-end: #111628;
|
||||
/* Login specific colors */
|
||||
--color-login-primary: var(--color-green);
|
||||
--color-login-dark-start: #464861;
|
||||
--color-login-dark-end: #111628;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark theme colors */
|
||||
--background: #020617;
|
||||
--foreground: #f8fafc;
|
||||
--card: #0f172a;
|
||||
--card-foreground: #f8fafc;
|
||||
--popover: #0f172a;
|
||||
--popover-foreground: #f8fafc;
|
||||
--primary: #22c55e;
|
||||
--primary-foreground: #0a0a0a;
|
||||
--secondary: #1e293b;
|
||||
--secondary-foreground: #f8fafc;
|
||||
--muted: #1e293b;
|
||||
--muted-foreground: #94a3b8;
|
||||
--accent: #1e293b;
|
||||
--accent-foreground: #f8fafc;
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #f8fafc;
|
||||
--border: #1e293b;
|
||||
--input: #1e293b;
|
||||
--ring: #22c55e;
|
||||
/* Dark theme colors */
|
||||
--background: #020617;
|
||||
--foreground: #f8fafc;
|
||||
--card: #0f172a;
|
||||
--card-foreground: #f8fafc;
|
||||
--popover: #0f172a;
|
||||
--popover-foreground: #f8fafc;
|
||||
--primary: #22c55e;
|
||||
--primary-foreground: #0a0a0a;
|
||||
--secondary: #1e293b;
|
||||
--secondary-foreground: #f8fafc;
|
||||
--muted: #1e293b;
|
||||
--muted-foreground: #94a3b8;
|
||||
--accent: #1e293b;
|
||||
--accent-foreground: #f8fafc;
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #f8fafc;
|
||||
--border: #1e293b;
|
||||
--input: #1e293b;
|
||||
--ring: #22c55e;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Persian/Farsi font class */
|
||||
.font-persian {
|
||||
font-family: IRANYekanX;
|
||||
font-family: "IRANYekanX";
|
||||
}
|
||||
|
||||
/* Custom utility classes */
|
||||
.gradient-primary {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary-500) 0%,
|
||||
var(--color-primary-600) 100%
|
||||
);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary-500) 0%,
|
||||
var(--color-primary-600) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.gradient-secondary {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-secondary-500) 0%,
|
||||
var(--color-secondary-600) 100%
|
||||
);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-secondary-500) 0%,
|
||||
var(--color-secondary-600) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.gradient-background {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-neutral-50) 0%,
|
||||
var(--color-neutral-100) 100%
|
||||
);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-neutral-50) 0%,
|
||||
var(--color-neutral-100) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.dark .gradient-background {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-neutral-900) 0%,
|
||||
var(--color-neutral-800) 100%
|
||||
);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-neutral-900) 0%,
|
||||
var(--color-neutral-800) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* Login page specific styles */
|
||||
.login-page {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-login-dark-start) 0%,
|
||||
var(--color-login-dark-end) 100%
|
||||
);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-login-dark-start) 0%,
|
||||
var(--color-login-dark-end) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.login-sidebar {
|
||||
background: var(--color-login-primary);
|
||||
background: var(--color-login-primary);
|
||||
}
|
||||
|
||||
/* Animation classes */
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slideUp 0.3s ease-out;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-down {
|
||||
animation: slideDown 0.3s ease-out;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
from {
|
||||
transform: translateY(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
from {
|
||||
transform: translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Toast customization for RTL */
|
||||
.Toaster__toast {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Form focus styles */
|
||||
.form-input:focus-within {
|
||||
@apply ring-2 ring-primary/20 border-primary;
|
||||
@apply ring-2 ring-primary/20 border-primary;
|
||||
}
|
||||
|
||||
/* Button hover effects */
|
||||
.btn-hover-scale:hover {
|
||||
transform: scale(1.02);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
transform: scale(1.02);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Custom shadows */
|
||||
.shadow-primary {
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgb(34 197 94 / 0.1),
|
||||
0 2px 4px -2px rgb(34 197 94 / 0.1);
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgb(34 197 94 / 0.1),
|
||||
0 2px 4px -2px rgb(34 197 94 / 0.1);
|
||||
}
|
||||
|
||||
.shadow-error {
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgb(239 68 68 / 0.1),
|
||||
0 2px 4px -2px rgb(239 68 68 / 0.1);
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgb(239 68 68 / 0.1),
|
||||
0 2px 4px -2px rgb(239 68 68 / 0.1);
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.loading-shimmer {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
.dark .loading-shimmer {
|
||||
background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
|
||||
background-size: 200% 100%;
|
||||
background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table/container specific custom dark scrollbar */
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: rgba(100, 116, 139, 0.6) transparent; /* thumb track */
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: rgba(100, 116, 139, 0.6) transparent; /* thumb track */
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
background: linear-gradient(to bottom, rgba(16, 185, 129, 0.6), rgba(16, 185, 129, 0.9)); /* emerald */
|
||||
border-radius: 9999px;
|
||||
border: .5px solid transparent;
|
||||
background-clip: padding-box;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(16, 185, 129, 0.6),
|
||||
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 {
|
||||
|
|
@ -438,52 +442,51 @@ html[dir="rtl"] body {
|
|||
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--form-control-color: #3F415A;
|
||||
--form-control-disabled: ##5F6284;
|
||||
--form-background: #3AEA83;
|
||||
--form-control-color: #3f415a;
|
||||
--form-control-disabled: ##5f6284;
|
||||
--form-background: #3aea83;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
color: #5F6284;
|
||||
background-color: transparent;
|
||||
width: 1.15em;
|
||||
height: 1.15em;
|
||||
border: 1px solid #5F6284;
|
||||
border-radius: 0.15em;
|
||||
transform: translateY(-0.075em);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
color: #5f6284;
|
||||
background-color: transparent;
|
||||
width: 1.15em;
|
||||
height: 1.15em;
|
||||
border: 1px solid #5f6284;
|
||||
border-radius: 0.15em;
|
||||
transform: translateY(-0.075em);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="checkbox"]::before {
|
||||
content: "";
|
||||
width: 0.65em;
|
||||
height: 0.65em;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
transform: scale(0);
|
||||
transform-origin: bottom left;
|
||||
transition: 120ms transform ease-in-out;
|
||||
box-shadow: inset 1em 1em var(--form-control-color);
|
||||
content: "";
|
||||
width: 0.65em;
|
||||
height: 0.65em;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
transform: scale(0);
|
||||
transform-origin: bottom left;
|
||||
transition: 120ms transform ease-in-out;
|
||||
box-shadow: inset 1em 1em var(--form-control-color);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
background-color: #3AEA83 ;
|
||||
border: 1px solid transparent;
|
||||
background-color: #3aea83;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:disabled {
|
||||
--form-control-color: var(--form-control-disabled);
|
||||
color: var(--form-control-disabled);
|
||||
cursor: not-allowed;
|
||||
--form-control-color: var(--form-control-disabled);
|
||||
color: var(--form-control-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,10 +595,10 @@ export function DashboardHome() {
|
|||
تحقق ارزش ها
|
||||
</p>
|
||||
<TabsList className="bg-transparent py-2 border m-6 border-gray-600">
|
||||
<TabsTrigger value="canvas" className="">
|
||||
<TabsTrigger value="canvas" className="cursor-pointer">
|
||||
شماتیک
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="charts" className=" text-white font-light ">
|
||||
<TabsTrigger value="charts" className=" text-white cursor-pointer font-light ">
|
||||
مقایسه ای
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export function Header({
|
|||
|
||||
{
|
||||
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}>
|
||||
<Server className="h-4 w-4" />
|
||||
ورود به میزکار مدیریت</button>
|
||||
|
|
|
|||
|
|
@ -289,7 +289,6 @@ export function ProjectManagementPage() {
|
|||
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
||||
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
|
||||
|
||||
// Trigger load more when scrolled to 90% of the container
|
||||
if (scrollPercentage >= 0.9) {
|
||||
loadMore();
|
||||
|
|
@ -518,7 +517,7 @@ export function ProjectManagementPage() {
|
|||
label: "میزان نوآوری",
|
||||
palette: ["#C3BF8B", "#10B981", "#F59E0B", "#EF4444", "#FDE68A"],
|
||||
},
|
||||
{
|
||||
{
|
||||
key: "executive_phase",
|
||||
label: "فاز اجرایی",
|
||||
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
|
||||
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) => {
|
||||
const counts: Record<string, number> = {};
|
||||
let total = 0;
|
||||
|
|
@ -613,7 +615,9 @@ export function ProjectManagementPage() {
|
|||
.map((p) => calculateRemainingDays((p as any).end_date))
|
||||
.filter((v) => v !== null) as number[];
|
||||
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;
|
||||
|
||||
// For other keys, parse numeric values
|
||||
|
|
@ -623,11 +627,17 @@ export function ProjectManagementPage() {
|
|||
.map((p) => {
|
||||
const raw = (p as any)[k];
|
||||
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;
|
||||
})
|
||||
.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;
|
||||
|
|
@ -668,11 +678,13 @@ export function ProjectManagementPage() {
|
|||
const color = getCategoryColor(column.key, value);
|
||||
return (
|
||||
<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
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
display : !value ? "none" : "block",
|
||||
display: !value ? "none" : "block",
|
||||
}}
|
||||
className="inline-block w-2 h-2 rounded-full"
|
||||
/>
|
||||
|
|
@ -689,25 +701,30 @@ export function ProjectManagementPage() {
|
|||
case "deviation_from_program":
|
||||
case "cost_deviation":
|
||||
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 "end_date":
|
||||
case "done_date":
|
||||
return (
|
||||
<span className=" text-sm font-normal">{formatDate(String(value))}</span>
|
||||
<span className=" text-sm font-normal">
|
||||
{formatDate(String(value))}
|
||||
</span>
|
||||
);
|
||||
case "project_no":
|
||||
return (
|
||||
<Badge
|
||||
variant="teal"
|
||||
className="border-emerald-500/50"
|
||||
>
|
||||
<Badge variant="teal" className="border-emerald-500/50">
|
||||
{String(value)}
|
||||
</Badge>
|
||||
);
|
||||
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":
|
||||
return (
|
||||
<Badge
|
||||
|
|
@ -741,222 +758,258 @@ export function ProjectManagementPage() {
|
|||
<div className="relative">
|
||||
<div className="relative overflow-auto custom-scrollbar max-h-[calc(100vh-120px)]">
|
||||
<Table className="table-fixed">
|
||||
<TableHeader className="sticky top-0 z-50 bg-[#3F415A]">
|
||||
<TableRow className="bg-[#3F415A]">
|
||||
{columns.map((column) => (
|
||||
<TableHead
|
||||
key={column.key}
|
||||
className={` text-right font-persian whitespace-nowrap text-white font-semibold bg-[#3F415A] sticky top-0 z-20`}
|
||||
style={{ width: column.width}}
|
||||
>
|
||||
{column.sortable ? (
|
||||
<button
|
||||
onClick={() => handleSort(column.key)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span>{column.label}</span>
|
||||
{sortConfig.field === column.key ? (
|
||||
sortConfig.direction === "asc" ? (
|
||||
<ChevronUp className="w-4 h-4" />
|
||||
<TableHeader className="sticky top-0 z-50 bg-[#3F415A]">
|
||||
<TableRow className="bg-[#3F415A]">
|
||||
{columns.map((column) => (
|
||||
<TableHead
|
||||
key={column.key}
|
||||
className={` text-right font-persian whitespace-nowrap text-white font-semibold bg-[#3F415A] sticky top-0 z-20`}
|
||||
style={{ width: column.width }}
|
||||
>
|
||||
{column.sortable ? (
|
||||
<button
|
||||
onClick={() => handleSort(column.key)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span>{column.label}</span>
|
||||
{sortConfig.field === column.key ? (
|
||||
sortConfig.direction === "asc" ? (
|
||||
<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>
|
||||
) : (
|
||||
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>
|
||||
<div className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
column.label
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
) : (
|
||||
projects.map((project, index) => (
|
||||
<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>
|
||||
))}
|
||||
</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>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
) : (
|
||||
projects.map((project, index) => (
|
||||
<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]">
|
||||
<TableRow>
|
||||
{columns.map((column, colIndex) => {
|
||||
// First column: show total projects text similar to API count
|
||||
if (colIndex === 0) {
|
||||
return (
|
||||
<TableCell key={column.key} className="p-3 text-sm text-white font-semibold font-persian">
|
||||
کل پروژهها: {formatNumber(actualTotalCount)}
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
// importance_project: render importance bar with specified colors
|
||||
if (column.key === "importance_project") {
|
||||
const imp = importanceCounts;
|
||||
const order = ["بالا", "متوسط", "پایین"];
|
||||
const colorFor = (k: string) => {
|
||||
switch (k) {
|
||||
case "بالا":
|
||||
return "var(--color-pr-green)"; // green
|
||||
case "متوسط":
|
||||
return "#69C8EA"; // blue-ish
|
||||
case "پایین":
|
||||
return "#F76276"; // red
|
||||
default:
|
||||
return "#6B7280";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<TableCell key={column.key} className="p-1">
|
||||
<div className="w-full bg-gray-800 rounded-sm overflow-hidden h-3 flex">
|
||||
{order.map((k) => {
|
||||
const cnt = imp.counts[k] || 0;
|
||||
const widthPercent = imp.total > 0 ? (cnt / imp.total) * 100 : 0;
|
||||
return (
|
||||
<div
|
||||
key={k}
|
||||
title={`${k} (${cnt})`}
|
||||
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;
|
||||
<TableFooter className="sticky py-2 bottom-[-1px] bg-[#3F415A]">
|
||||
<TableRow>
|
||||
{columns.map((column, colIndex) => {
|
||||
// First column: show total projects text similar to API count
|
||||
if (colIndex === 0) {
|
||||
return (
|
||||
<TableCell
|
||||
key={column.key}
|
||||
className="p-3 text-sm text-white font-semibold font-persian"
|
||||
>
|
||||
کل پروژهها: {formatNumber(actualTotalCount)}
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
// importance_project: render importance bar with specified colors
|
||||
if (column.key === "importance_project") {
|
||||
const imp = importanceCounts;
|
||||
const order = ["بالا", "متوسط", "پایین"];
|
||||
const colorFor = (k: string) => {
|
||||
switch (k) {
|
||||
case "بالا":
|
||||
return "var(--color-pr-green)"; // green
|
||||
case "متوسط":
|
||||
return "#69C8EA"; // blue-ish
|
||||
case "پایین":
|
||||
return "#F76276"; // red
|
||||
default:
|
||||
return "#6B7280";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<TableCell key={column.key} className="p-1">
|
||||
<div className="w-full bg-gray-800 rounded-sm overflow-hidden h-3 flex">
|
||||
{order.map((k) => {
|
||||
const cnt = imp.counts[k] || 0;
|
||||
const widthPercent =
|
||||
imp.total > 0 ? (cnt / imp.total) * 100 : 0;
|
||||
return (
|
||||
<div
|
||||
key={val}
|
||||
title={`${val} (${cnt})`}
|
||||
key={k}
|
||||
title={`${k} (${cnt})`}
|
||||
className="h-3 flex items-center justify-center text-xs font-medium"
|
||||
style={{ width: `${widthPercent}%`, backgroundColor: color }}
|
||||
>
|
||||
</div>
|
||||
style={{
|
||||
width: `${widthPercent}%`,
|
||||
backgroundColor: colorFor(k),
|
||||
}}
|
||||
></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));
|
||||
})}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
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>
|
||||
// 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 (
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -973,8 +1026,6 @@ export function ProjectManagementPage() {
|
|||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
|
|
|
|||
6910
package-lock.json
generated
6910
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
1546
pnpm-lock.yaml
1546
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -17,92 +17,100 @@ This set of fonts are used in this project under the license: (.....)
|
|||
*
|
||||
**/
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('woff/IRANYekanX-Thin.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Thin.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Thin.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('woff/IRANYekanX-UltraLight.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-UltraLight.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-UltraLight.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-UltraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('woff/IRANYekanX-Light.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Light.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Light.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('woff/IRANYekanX-Medium.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Medium.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Medium.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('woff/IRANYekanX-DemiBold.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-DemiBold.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-DemiBold.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-DemiBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('woff/IRANYekanX-ExtraBold.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-ExtraBold.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-ExtraBold.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-ExtraBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('woff/IRANYekanX-Black.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Black.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Black.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Black.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 950;
|
||||
src: url('woff/IRANYekanX-ExtraBlack.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-ExtraBlack.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 950;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-ExtraBlack.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-ExtraBlack.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 1000;
|
||||
src: url('woff/IRANYekanX-Heavy.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Heavy.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: 1000;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Heavy.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Heavy.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url('woff/IRANYekanX-Bold.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Bold.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Bold.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url('woff/IRANYekanX-Regular.woff') format('woff'),
|
||||
url('woff2/IRANYekanX-Regular.woff2') format('woff2');
|
||||
font-family: IRANYekanX;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src:
|
||||
url("/font/woff/IRANYekanX-Regular.woff") format("woff"),
|
||||
url("/font/woff2/IRANYekanX-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user