Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39e1ae4e81 | |||
| 67815aec2d | |||
| e10c25fc3e |
File diff suppressed because it is too large
Load Diff
|
|
@ -616,22 +616,22 @@ export function ProductInnovationPage() {
|
|||
size="sm"
|
||||
onClick={() => {
|
||||
handleProjectDetails(item)}}
|
||||
className="text-emerald-400 underline underline-offset-4 font-ligth text-base hover:bg-emerald-500/20 p-2 h-auto"
|
||||
className="text-emerald-400 underline underline-offset-4 font-ligth text-sm hover:bg-emerald-500/20 p-2 h-auto"
|
||||
>
|
||||
جزئیات بیشتر
|
||||
</Button>
|
||||
);
|
||||
case "project_no":
|
||||
return (
|
||||
<Badge variant="outline" className="font-mono text-base font-light">
|
||||
<Badge variant="outline" className="font-mono text-sm font-light">
|
||||
{String(value)}
|
||||
</Badge>
|
||||
);
|
||||
case "title":
|
||||
return <span className="font-light text-base text-white">{String(value)}</span>;
|
||||
return <span className="font-light text-sm text-white">{String(value)}</span>;
|
||||
case "project_status":
|
||||
return (
|
||||
<div className="flex items-center text-base font-light gap-1">
|
||||
<div className="flex items-center text-sm font-light gap-1">
|
||||
<Badge
|
||||
variant={statusColor(value as projectStatus)}
|
||||
className="font-semibold text-base border-2 p-0 block w-2 h-2 rounded-full"
|
||||
|
|
@ -652,7 +652,7 @@ export function ProductInnovationPage() {
|
|||
</Badge>
|
||||
);
|
||||
default:
|
||||
return <span className="text-white text-base font-light">{String(value) || "-"}</span>;
|
||||
return <span className="text-white text-sm font-light">{String(value) || "-"}</span>;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import {
|
|||
Radar,
|
||||
Settings,
|
||||
Star,
|
||||
Workflow,
|
||||
DiscAlbum,
|
||||
LucideLightbulb
|
||||
} from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { Link, useLocation } from "react-router";
|
||||
|
|
@ -95,15 +98,9 @@ const menuItems: MenuItem[] = [
|
|||
{
|
||||
id: "ideas",
|
||||
label: "ایدههای فناوری و نوآوری",
|
||||
icon: House,
|
||||
icon: LucideLightbulb,
|
||||
href: "/dashboard/manage-ideas-tech",
|
||||
},
|
||||
{
|
||||
id: "top-innovations",
|
||||
label: "نوآور برتر",
|
||||
icon: Star,
|
||||
href: "/dashboard/top-innovations",
|
||||
},
|
||||
{
|
||||
id: "strategic-alignment",
|
||||
label: "میزان انطباق راهبردی",
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
|||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute w-2 h-2 bg-green-400 rounded-full animate-pulse"
|
||||
className="absolute w-2 h-2 bg-pr-green rounded-full animate-pulse"
|
||||
style={{
|
||||
left: `${20 + i * 25}%`,
|
||||
top: `${30 + Math.random() * 40}%`,
|
||||
|
|
@ -287,7 +287,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
|||
{/* Actor Count Skeleton */}
|
||||
<CardHeader className="text-center pt-0 pb-4">
|
||||
<div className="w-36 h-5 rounded animate-pulse mx-auto mb-2"></div>
|
||||
<div className="w-16 h-8 bg-green-400 bg-opacity-30 rounded animate-pulse mx-auto"></div>
|
||||
<div className="w-16 h-8 bg-pr-green bg-opacity-30 rounded animate-pulse mx-auto"></div>
|
||||
</CardHeader>
|
||||
|
||||
{/* Bar Chart Skeleton */}
|
||||
|
|
@ -362,7 +362,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
|||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute w-2 h-2 bg-green-400 rounded-full animate-pulse"
|
||||
className="absolute w-2 h-2 bg-pr-green rounded-full animate-pulse"
|
||||
style={{
|
||||
left: `${20 + i * 25}%`,
|
||||
top: `${30 + Math.random() * 40}%`,
|
||||
|
|
@ -378,7 +378,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
|||
<CardContent className="pt-0 pb-6">
|
||||
<div className="bg-[rgba(255,255,255,0.1)] rounded-lg p-4 text-center">
|
||||
<div className="w-28 h-4 bg-gray-600 rounded animate-pulse mx-auto mb-1"></div>
|
||||
<div className="w-12 h-6 bg-green-400 bg-opacity-30 rounded animate-pulse mx-auto"></div>
|
||||
<div className="w-12 h-6 bg-pr-green bg-opacity-30 rounded animate-pulse mx-auto"></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export interface CompanyDetails {
|
|||
|
||||
export interface NetworkGraphProps {
|
||||
onNodeClick?: (node: CompanyDetails) => void;
|
||||
onLoadingChange?: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
function parseApiResponse(raw: any): any[] {
|
||||
|
|
@ -58,7 +59,7 @@ function isBrowser(): boolean {
|
|||
return typeof window !== "undefined";
|
||||
}
|
||||
|
||||
export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
||||
export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps) {
|
||||
const svgRef = useRef<SVGSVGElement | null>(null);
|
||||
const [nodes, setNodes] = useState<Node[]>([]);
|
||||
const [links, setLinks] = useState<Link[]>([]);
|
||||
|
|
@ -441,6 +442,19 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
if (d.isCenter) return;
|
||||
|
||||
if (onNodeClick && d.stageid) {
|
||||
// Open dialog immediately with basic info
|
||||
const basicDetails: CompanyDetails = {
|
||||
id: d.id,
|
||||
label: d.label,
|
||||
category: d.category,
|
||||
stageid: d.stageid,
|
||||
fields: [],
|
||||
};
|
||||
onNodeClick(basicDetails);
|
||||
|
||||
// Start loading
|
||||
onLoadingChange?.(true);
|
||||
|
||||
try {
|
||||
const res = await callAPI(d.stageid);
|
||||
const responseData = JSON.parse(res.data);
|
||||
|
|
@ -473,14 +487,10 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
onNodeClick(companyDetails);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch company details:", error);
|
||||
const basicDetails: CompanyDetails = {
|
||||
id: d.id,
|
||||
label: d.label,
|
||||
category: d.category,
|
||||
stageid: d.stageid,
|
||||
fields: [],
|
||||
};
|
||||
onNodeClick(basicDetails);
|
||||
// Keep the basic details already shown
|
||||
} finally {
|
||||
// Stop loading
|
||||
onLoadingChange?.(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { cn } from "~/lib/utils"
|
|||
|
||||
interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
|
||||
containerClassName?: string
|
||||
containerRef?: React.RefObject<HTMLDivElement>
|
||||
containerRef?: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
const Table = React.forwardRef<HTMLTableElement, TableProps>(
|
||||
|
|
|
|||
|
|
@ -56,10 +56,20 @@ function handleValue(val: any): any {
|
|||
export default function EcosystemPage() {
|
||||
const [selectedCompany, setSelectedCompany] =
|
||||
React.useState<CompanyDetails | null>(null);
|
||||
const [isDialogLoading, setIsDialogLoading] = React.useState(false);
|
||||
const { token } = useAuth();
|
||||
|
||||
const closeDialog = () => {
|
||||
setSelectedCompany(null);
|
||||
setIsDialogLoading(false);
|
||||
};
|
||||
|
||||
const handleNodeClick = (company: CompanyDetails) => {
|
||||
setSelectedCompany(company);
|
||||
};
|
||||
|
||||
const handleLoadingChange = (loading: boolean) => {
|
||||
setIsDialogLoading(loading);
|
||||
};
|
||||
|
||||
// Construct image URL
|
||||
|
|
@ -70,7 +80,7 @@ export default function EcosystemPage() {
|
|||
return (
|
||||
<ProtectedRoute requireAuth={true}>
|
||||
<DashboardLayout title="زیست بوم فناوری">
|
||||
<div className="">
|
||||
<div>
|
||||
<div className="grid grid-cols-1 items-start lg:grid-cols-12 gap-4">
|
||||
<div className="lg:col-span-4">
|
||||
<InfoPanel selectedCompany={selectedCompany} />
|
||||
|
|
@ -79,7 +89,7 @@ export default function EcosystemPage() {
|
|||
<div className="lg:col-span-8 h-full">
|
||||
<Card className="h-full overflow-hidden bg-transparent border-[#3F415A]">
|
||||
<CardContent className="p-0 h-full bg-transparent">
|
||||
<NetworkGraph onNodeClick={setSelectedCompany} />
|
||||
<NetworkGraph onNodeClick={handleNodeClick} onLoadingChange={handleLoadingChange} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
@ -91,7 +101,7 @@ export default function EcosystemPage() {
|
|||
open={!!selectedCompany}
|
||||
onOpenChange={(open) => !open && closeDialog()}
|
||||
>
|
||||
<DialogContent className="font-persian max-w-6xl max-h-[75vh] overflow-y-auto bg-[linear-gradient(to_bottom_left,#464861,20%,#111628)]">
|
||||
<DialogContent className="font-persian max-w-6xl min-h-max bg-[linear-gradient(to_bottom_left,#464861,20%,#111628)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-right border-b-2 border-gray-600 pt-2 pb-4 mr-4 text-sm font-semibold">
|
||||
معرفی
|
||||
|
|
@ -99,7 +109,44 @@ export default function EcosystemPage() {
|
|||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{isDialogLoading ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 p-4 gap-6">
|
||||
{/* Right Column - Loading Skeleton */}
|
||||
<div className="space-y-4 p-6 border-l-2 border-gray-600">
|
||||
{/* Company Image & Title Skeleton */}
|
||||
<div className="flex justify-between px-10 items-center mb-4">
|
||||
<div className="h-8 bg-gray-600 rounded animate-pulse w-48"></div>
|
||||
<div className="w-12 h-12 bg-gray-600 rounded-2xl animate-pulse"></div>
|
||||
</div>
|
||||
{/* Description Skeleton */}
|
||||
<div className="p-4 rounded-lg space-y-2">
|
||||
<div className="h-4 bg-gray-600 rounded animate-pulse w-full"></div>
|
||||
<div className="h-4 bg-gray-600 rounded animate-pulse w-5/6"></div>
|
||||
<div className="h-4 bg-gray-600 rounded animate-pulse w-4/6"></div>
|
||||
<div className="h-4 bg-gray-600 rounded animate-pulse w-3/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Left Column - Loading Skeleton */}
|
||||
<div className="space-y-2">
|
||||
<div className="h-6 bg-gray-600 rounded animate-pulse w-32"></div>
|
||||
<div className="space-y-3 px-2">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex justify-between items-center rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="h-4 w-4 bg-gray-600 rounded animate-pulse"></div>
|
||||
<div className="h-4 bg-gray-600 rounded animate-pulse w-24"></div>
|
||||
</div>
|
||||
<div className="h-4 bg-gray-600 rounded animate-pulse w-20"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid p-4 pb-6 grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Right Column - Description */}
|
||||
<div className="space-y-4 p-6 border-l-2 border-gray-600">
|
||||
{/* Company Image */}
|
||||
|
|
@ -122,7 +169,7 @@ export default function EcosystemPage() {
|
|||
/>
|
||||
) : null}
|
||||
<div
|
||||
className="w-24 h-24 rounded-full bg-gray-600 border-4 border-green-400 flex items-center justify-center"
|
||||
className="w-24 h-24 rounded-full bg-gray-600 border-4 border-pr-green flex items-center justify-center"
|
||||
style={{
|
||||
display:
|
||||
selectedCompany?.stageid && token?.accessToken
|
||||
|
|
@ -191,6 +238,7 @@ export default function EcosystemPage() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DashboardLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user