Compare commits

...

8 Commits

Author SHA1 Message Date
efe32f8084 fix the width of charts in dashboard 2025-09-17 13:00:51 +03:30
bf973d3147 Merge branch 'main' of https://git.pelekan.org/Saeed0920/inogen into refactor_#1 2025-09-17 12:23:01 +03:30
8bdc533b0b fix the bug in sidebar 2025-09-17 12:22:11 +03:30
137882fcb3 fix the api to handle the token expire things (#11)
fix the api token expire time!

Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/11
Co-authored-by: saeed0920 <sd.eed1381@gmail.com>
Co-committed-by: saeed0920 <sd.eed1381@gmail.com>
2025-09-16 22:08:39 +03:30
MehrdadAdabi
e3431533ca fix:change header styles 2025-09-15 22:34:00 +03:30
MehrdadAdabi
1b38e78ebd fix: change redirect button postion in ui 2025-09-14 17:25:30 +03:30
MehrdadAdabi
f1b4d313eb Merge branch 'main' of http://git.sepehrdata.com/Saeed0920/inogen 2025-09-14 16:09:20 +03:30
MehrdadAdabi
6fdfe83b1e feat:add redirect link to bpms 2025-09-14 16:07:30 +03:30
7 changed files with 179 additions and 68 deletions

View File

@ -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) => {

View File

@ -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">

View File

@ -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}

View File

@ -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>

View File

@ -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();
@ -218,22 +221,16 @@ export function Sidebar({
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>
) )

View File

@ -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
View File

@ -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",