Compare commits
8 Commits
005b3e3e6d
...
efe32f8084
| Author | SHA1 | Date | |
|---|---|---|---|
| efe32f8084 | |||
| bf973d3147 | |||
| 8bdc533b0b | |||
| 137882fcb3 | |||
|
|
e3431533ca | ||
|
|
1b38e78ebd | ||
|
|
f1b4d313eb | ||
|
|
6fdfe83b1e |
|
|
@ -312,7 +312,7 @@ export function DashboardHome() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="grid gird-cols-3 p-3 pb-0 gap-4">
|
<div className="grid grid-cols-3 p-3 pb-0 gap-4">
|
||||||
{/* Top Cards Row - Redesigned to match other components */}
|
{/* Top Cards Row - Redesigned to match other components */}
|
||||||
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
||||||
{/* Ideas Card */}
|
{/* Ideas Card */}
|
||||||
|
|
@ -378,7 +378,7 @@ export function DashboardHome() {
|
||||||
gridType="circle"
|
gridType="circle"
|
||||||
radialLines={false}
|
radialLines={false}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
className="first:fill-red-400 last:fill-background"
|
className="first:fill-red-400 last:fill-[#111628]"
|
||||||
polarRadius={[38, 31]}
|
polarRadius={[38, 31]}
|
||||||
/>
|
/>
|
||||||
<RadialBar
|
<RadialBar
|
||||||
|
|
@ -506,7 +506,7 @@ export function DashboardHome() {
|
||||||
gridType="circle"
|
gridType="circle"
|
||||||
radialLines={false}
|
radialLines={false}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
className="first:fill-red-400 last:fill-background"
|
className="first:fill-red-400 last:fill-[#111628]"
|
||||||
polarRadius={[38, 31]}
|
polarRadius={[38, 31]}
|
||||||
/>
|
/>
|
||||||
<RadialBar
|
<RadialBar
|
||||||
|
|
@ -604,12 +604,12 @@ export function DashboardHome() {
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TabsContent value="charts" className="w-ful h-full">
|
<TabsContent value="charts" className="h-full">
|
||||||
<InteractiveBarChart data={companyChartData} />
|
<InteractiveBarChart data={companyChartData} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="canvas" className="w-ful h-full">
|
<TabsContent value="canvas" className="w-ful h-full">
|
||||||
<div className="p-4 h-full">
|
<div className="p-4 h-full w-full">
|
||||||
<D3ImageInfo
|
<D3ImageInfo
|
||||||
companies={
|
companies={
|
||||||
companyChartData.map((item) => {
|
companyChartData.map((item) => {
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,16 @@ import { cn } from "~/lib/utils";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
PanelLeft,
|
PanelLeft,
|
||||||
Search,
|
|
||||||
Bell,
|
|
||||||
Settings,
|
Settings,
|
||||||
User,
|
User,
|
||||||
Moon,
|
|
||||||
Sun,
|
|
||||||
Menu,
|
Menu,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Globe,
|
Server,
|
||||||
HelpCircle,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import apiService from "~/lib/api";
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
onToggleSidebar?: () => void;
|
onToggleSidebar?: () => void;
|
||||||
|
|
@ -32,6 +31,17 @@ export function Header({
|
||||||
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
|
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
|
||||||
const [isNotificationOpen, setIsNotificationOpen] = useState(false);
|
const [isNotificationOpen, setIsNotificationOpen] = useState(false);
|
||||||
|
|
||||||
|
const redirectHandler = async () => {
|
||||||
|
try {
|
||||||
|
const getData = await apiService.post('/GenerateSsoCode')
|
||||||
|
const url = `http://localhost:3000/redirect/${getData.data}`;
|
||||||
|
// const url = `https://inogen-bpms.pelekan.org/redirect/${getData.data}`;
|
||||||
|
window.open(url, "_blank");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -63,6 +73,16 @@ export function Header({
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* User Menu */}
|
{/* User Menu */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
onClick={redirectHandler}>
|
||||||
|
<Server className="h-4 w-4" />
|
||||||
|
ورود به میزکار مدیریت</button>
|
||||||
|
}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -82,7 +102,7 @@ export function Header({
|
||||||
</div>
|
</div>
|
||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
{/* Profile Dropdown */}
|
{/* Profile Dropdown */}
|
||||||
{isProfileMenuOpen && (
|
{isProfileMenuOpen && (
|
||||||
<div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50">
|
<div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50">
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export function InteractiveBarChart({
|
||||||
return (
|
return (
|
||||||
<Card className="py-0 bg-transparent mt-8 border-none h-full">
|
<Card className="py-0 bg-transparent mt-8 border-none h-full">
|
||||||
<CardContent className="p-2 bg-transparent">
|
<CardContent className="p-2 bg-transparent">
|
||||||
<ChartContainer config={chartConfig} className="aspect-auto h-96 w-full">
|
<ChartContainer config={chartConfig} className="aspect-auto h-96">
|
||||||
<BarChart
|
<BarChart
|
||||||
accessibilityLayer
|
accessibilityLayer
|
||||||
data={data}
|
data={data}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { cn } from "~/lib/utils";
|
||||||
import { Sidebar } from "./sidebar";
|
import { Sidebar } from "./sidebar";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { StrategicAlignmentPopup } from "./strategic-alignment-popup";
|
import { StrategicAlignmentPopup } from "./strategic-alignment-popup";
|
||||||
|
import apiService from "~/lib/api";
|
||||||
|
|
||||||
interface DashboardLayoutProps {
|
interface DashboardLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|
@ -27,6 +28,8 @@ export function DashboardLayout({
|
||||||
setIsMobileSidebarOpen(!isMobileSidebarOpen);
|
setIsMobileSidebarOpen(!isMobileSidebarOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="h-screen flex overflow-hidden bg-[linear-gradient(to_bottom_left,#464861,20%,#111628)] relative overflow-x-hidden"
|
className="h-screen flex overflow-hidden bg-[linear-gradient(to_bottom_left,#464861,20%,#111628)] relative overflow-x-hidden"
|
||||||
|
|
@ -58,6 +61,7 @@ export function DashboardLayout({
|
||||||
onToggleCollapse={toggleSidebarCollapse}
|
onToggleCollapse={toggleSidebarCollapse}
|
||||||
className="h-full flex-shrink-0 relative z-10"
|
className="h-full flex-shrink-0 relative z-10"
|
||||||
onStrategicAlignmentClick={() => setIsStrategicAlignmentPopupOpen(true)}
|
onStrategicAlignmentClick={() => setIsStrategicAlignmentPopupOpen(true)}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
Settings,
|
Settings,
|
||||||
Star,
|
Star,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
DiscAlbum
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Link, useLocation } from "react-router";
|
import { Link, useLocation } from "react-router";
|
||||||
|
|
@ -112,6 +113,7 @@ const menuItems: MenuItem[] = [
|
||||||
icon: null,
|
icon: null,
|
||||||
href: "#", // This is not a route, it opens a popup
|
href: "#", // This is not a route, it opens a popup
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const bottomMenuItems: MenuItem[] = [
|
const bottomMenuItems: MenuItem[] = [
|
||||||
|
|
@ -206,6 +208,7 @@ export function Sidebar({
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (item.id === "strategic-alignment") {
|
if (item.id === "strategic-alignment") {
|
||||||
|
console.log("test")
|
||||||
onStrategicAlignmentClick?.();
|
onStrategicAlignmentClick?.();
|
||||||
} else if (item.id === "logout") {
|
} else if (item.id === "logout") {
|
||||||
logout();
|
logout();
|
||||||
|
|
@ -215,27 +218,21 @@ export function Sidebar({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item.id === "strategic-alignment") {
|
if (item.id === "strategic-alignment") {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={cn(
|
|
||||||
"w-full text-right",
|
|
||||||
)}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center w-full px-2 rounded-lg mt-4 transition-all duration-200 group",
|
"flex items-center justify-center w-full px-2 rounded-lg mt-4 transition-all duration-200 group",
|
||||||
)}
|
)}
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div className="flex justify-center rounded-xl border-gray-500/20 border-2 cursor-pointer transition-all hover:bg-[#3F415A]/50 bg-[#3F415A] py-2 text-center items-center gap-3 min-w-0 flex-1">
|
<div className="flex justify-center rounded-xl border-gray-500/20 border-2 cursor-pointer transition-all hover:bg-[#3F415A]/50 bg-[#3F415A] py-2 text-center items-center gap-3 min-w-0 flex-1">
|
||||||
<span className="font-persian text-sm font-medium truncate">
|
<span className="font-persian text-sm font-medium truncate">
|
||||||
{item.label}
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,19 @@ class ApiService {
|
||||||
this.token = null;
|
this.token = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleSessionExpired(message?: string) {
|
||||||
|
this.clearToken();
|
||||||
|
localStorage.removeItem("auth_token");
|
||||||
|
localStorage.removeItem("auth_user");
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem("sessionExpired", "1");
|
||||||
|
} catch {}
|
||||||
|
if (message) {
|
||||||
|
toast.error(message);
|
||||||
|
}
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
|
||||||
private async request<T = any>(
|
private async request<T = any>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
options: RequestInit = {},
|
options: RequestInit = {},
|
||||||
|
|
@ -66,6 +79,11 @@ class ApiService {
|
||||||
const response = await fetch(url, config);
|
const response = await fetch(url, config);
|
||||||
const data: ApiResponse<T> = await response.json();
|
const data: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
|
if (data.errorCode === 301) {
|
||||||
|
this.handleSessionExpired("نشست شما منقضی شده است. لطفا دوباره وارد شوید.");
|
||||||
|
throw new Error(data.message || "Session expired");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle different response states
|
// Handle different response states
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -91,13 +109,7 @@ class ApiService {
|
||||||
|
|
||||||
// Handle authentication errors
|
// Handle authentication errors
|
||||||
if (error instanceof Error && error.message.includes("401")) {
|
if (error instanceof Error && error.message.includes("401")) {
|
||||||
this.clearToken();
|
this.handleSessionExpired();
|
||||||
localStorage.removeItem("auth_token");
|
|
||||||
localStorage.removeItem("auth_user");
|
|
||||||
try {
|
|
||||||
sessionStorage.setItem("sessionExpired", "1");
|
|
||||||
} catch {}
|
|
||||||
window.location.href = "/login";
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,6 +140,11 @@ class ApiService {
|
||||||
const response = await fetch(fullUrl, config);
|
const response = await fetch(fullUrl, config);
|
||||||
const apiData: ApiResponse<T> = await response.json();
|
const apiData: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
|
if (apiData.errorCode === 301) {
|
||||||
|
this.handleSessionExpired("نشست شما منقضی شده است. لطفا دوباره وارد شوید.");
|
||||||
|
throw new Error(apiData.message || "Session expired");
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(apiData?.message || `HTTP error! status: ${response.status}`);
|
throw new Error(apiData?.message || `HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
package-lock.json
generated
73
package-lock.json
generated
|
|
@ -10,10 +10,12 @@
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-progress": "^1.1.7",
|
"@radix-ui/react-progress": "^1.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@react-router/node": "^7.7.0",
|
"@react-router/node": "^7.7.0",
|
||||||
"@react-router/serve": "^7.7.1",
|
"@react-router/serve": "^7.7.1",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
|
|
@ -967,6 +969,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover": {
|
||||||
|
"version": "1.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
|
||||||
|
"integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.3",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.7",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-popper": "1.2.8",
|
||||||
|
"@radix-ui/react-portal": "1.1.9",
|
||||||
|
"@radix-ui/react-presence": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"aria-hidden": "^1.2.4",
|
||||||
|
"react-remove-scroll": "^2.6.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -1202,6 +1241,40 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-popper": "1.2.8",
|
||||||
|
"@radix-ui/react-portal": "1.1.9",
|
||||||
|
"@radix-ui/react-presence": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user