add the popup and fix the style fomr dasbhard
This commit is contained in:
parent
1dd5ea70f4
commit
4fe9871266
74
app/components/common/popup-bar-chart.tsx
Normal file
74
app/components/common/popup-bar-chart.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
XAxis,
|
||||
YAxis,
|
||||
LabelList,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import {
|
||||
type ChartConfig,
|
||||
ChartContainer,
|
||||
} from "~/components/ui/chart";
|
||||
import { formatNumber } from "~/lib/utils";
|
||||
|
||||
export type PopupChartDatum = {
|
||||
name: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Operational Fee",
|
||||
color: "#4ADE80", // Green-400
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
|
||||
export function PopupBarChart({
|
||||
data,
|
||||
}: {
|
||||
data: PopupChartDatum[];
|
||||
}) {
|
||||
return (
|
||||
<Card className="py-0 bg-transparent mt-4 border-none h-full">
|
||||
<CardContent className="px-2 sm:p-6 bg-transparent">
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-80 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={data}
|
||||
margin={{ top: 20, right: 20, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid vertical={false} stroke="#475569" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
||||
/>
|
||||
<YAxis
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
||||
tickFormatter={(value) => `${formatNumber(Math.round(value))}`}
|
||||
/>
|
||||
<Bar dataKey="value" fill={chartConfig.value.color} radius={[8, 8, 0, 0]}>
|
||||
<LabelList
|
||||
dataKey="value"
|
||||
position="top"
|
||||
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
|
||||
formatter={(v: number) => `${formatNumber(Math.round(v))}`}
|
||||
/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,13 +5,17 @@ export type CompanyInfo = {
|
|||
id: string;
|
||||
imageUrl: string;
|
||||
name: string;
|
||||
costReduction: number; // absolute value
|
||||
costReduction: number;
|
||||
revenue?: number;
|
||||
capacity?: number;
|
||||
costI : number,
|
||||
capacityI : number,
|
||||
revenueI : number,
|
||||
cost : number | string,
|
||||
};
|
||||
|
||||
export type D3ImageInfoProps = {
|
||||
companies: CompanyInfo[]; // exactly 6 items
|
||||
companies: CompanyInfo[];
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
|
@ -59,7 +63,7 @@ export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
|||
<div className="w-full h-[500px] rounded-xl p-4">
|
||||
<div dir="ltr" className="company-grid-container">
|
||||
{displayCompanies.map((company, index) => {
|
||||
const gp = gridPositions.find(v => v.name === company.name) || { col: (index % 5) + 1, row: Math.floor(index / 5) + 1 };
|
||||
const gp = gridPositions.find(v => v.name === company.name) ;
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
|
@ -77,7 +81,7 @@ export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
|||
|
||||
{company.name}
|
||||
</div>
|
||||
<InfoBox company={company} key={index +10} style={{ gridColumn: gp.colI , gridRow: gp.rowI }} />
|
||||
<InfoBox company={company} key={index +10} style={{ gridColumn: gp?.colI , gridRow: gp?.rowI }} />
|
||||
</>);
|
||||
})}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ export function DashboardHome() {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
// Chart and schematic data from select API
|
||||
const [companyChartData, setCompanyChartData] = useState<
|
||||
{ category: string; capacity: number; revenue: number; cost: number }[]
|
||||
{ category: string; capacity: number; revenue: number; cost: number , costI : number,
|
||||
capacityI : number,
|
||||
revenueI : number }[]
|
||||
>([]);
|
||||
const [totalIncreasedCapacity, setTotalIncreasedCapacity] = useState<number>(0);
|
||||
|
||||
|
|
@ -138,7 +140,6 @@ export function DashboardHome() {
|
|||
const capacityPct = preCap > 0 ? (incCap / preCap) * 100 : 0;
|
||||
const revenuePct = preInc > 0 ? (incInc / preInc) * 100 : 0;
|
||||
const costPct = preFee > 0 ? (costRed / preFee) * 100 : 0;
|
||||
console.log(costRed)
|
||||
return {
|
||||
category: rel,
|
||||
capacity: isFinite(capacityPct) ? capacityPct : 0,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
|||
import { cn } from "~/lib/utils";
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { Header } from "./header";
|
||||
import { StrategicAlignmentPopup } from "./strategic-alignment-popup";
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -16,6 +17,7 @@ export function DashboardLayout({
|
|||
}: DashboardLayoutProps) {
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
|
||||
const [isStrategicAlignmentPopupOpen, setIsStrategicAlignmentPopupOpen] = useState(false);
|
||||
|
||||
const toggleSidebarCollapse = () => {
|
||||
setIsSidebarCollapsed(!isSidebarCollapsed);
|
||||
|
|
@ -55,6 +57,7 @@ export function DashboardLayout({
|
|||
isCollapsed={isSidebarCollapsed}
|
||||
onToggleCollapse={toggleSidebarCollapse}
|
||||
className="h-full flex-shrink-0 relative z-10"
|
||||
onStrategicAlignmentClick={() => setIsStrategicAlignmentPopupOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -79,6 +82,7 @@ export function DashboardLayout({
|
|||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<StrategicAlignmentPopup open={isStrategicAlignmentPopupOpen} onOpenChange={setIsStrategicAlignmentPopupOpen} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ interface SidebarProps {
|
|||
isCollapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
className?: string;
|
||||
onStrategicAlignmentClick?: () => void;
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
|
|
@ -52,6 +53,7 @@ interface MenuItem {
|
|||
}
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
|
||||
{
|
||||
id: "dashboard",
|
||||
label: "صفحه اصلی",
|
||||
|
|
@ -119,6 +121,12 @@ const menuItems: MenuItem[] = [
|
|||
icon: Star,
|
||||
href: "/dashboard/top-innovations",
|
||||
},
|
||||
{
|
||||
id: "strategic-alignment",
|
||||
label: "میزان انطباق راهبردی",
|
||||
icon: BarChart3,
|
||||
href: "#", // This is not a route, it opens a popup
|
||||
},
|
||||
];
|
||||
|
||||
const bottomMenuItems: MenuItem[] = [
|
||||
|
|
@ -140,6 +148,7 @@ export function Sidebar({
|
|||
isCollapsed = false,
|
||||
onToggleCollapse,
|
||||
className,
|
||||
onStrategicAlignmentClick,
|
||||
}: SidebarProps) {
|
||||
const location = useLocation();
|
||||
const [expandedItems, setExpandedItems] = useState<string[]>([]);
|
||||
|
|
@ -211,16 +220,42 @@ export function Sidebar({
|
|||
const ItemIcon = item.icon;
|
||||
|
||||
const handleClick = () => {
|
||||
if (item.id === "logout") {
|
||||
if (item.id === "strategic-alignment") {
|
||||
onStrategicAlignmentClick?.();
|
||||
} else if (item.id === "logout") {
|
||||
logout();
|
||||
} else if (hasChildren) {
|
||||
toggleExpanded(item.id);
|
||||
}
|
||||
};
|
||||
|
||||
if (item.id === "strategic-alignment") {
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"w-full text-right",
|
||||
)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center w-full px-2 rounded-lg mt-4 transition-all duration-200 group",
|
||||
)}
|
||||
>
|
||||
<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">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={item.id} className="relative">
|
||||
{item.href && item.id !== "logout" ? (
|
||||
{item.href && item.href !== "#" ? (
|
||||
<Link to={item.href} className="block">
|
||||
<div
|
||||
className={cn(
|
||||
|
|
|
|||
203
app/components/dashboard/strategic-alignment-popup.tsx
Normal file
203
app/components/dashboard/strategic-alignment-popup.tsx
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
LabelList,
|
||||
Cell,
|
||||
} from "recharts";
|
||||
import apiService from "~/lib/api";
|
||||
import { Skeleton } from "~/components/ui/skeleton";
|
||||
import { formatNumber } from "~/lib/utils";
|
||||
import { ChartContainer } from "../ui/chart";
|
||||
|
||||
interface StrategicAlignmentData {
|
||||
strategic_theme: string;
|
||||
operational_fee_sum: number;
|
||||
percentage?: number;
|
||||
}
|
||||
|
||||
interface StrategicAlignmentPopupProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
// ✅ Chart config for shadcn/ui
|
||||
const chartConfig = {
|
||||
percentage: {
|
||||
label: "",
|
||||
color: "#3AEA83",
|
||||
},
|
||||
};
|
||||
|
||||
const maxHeight = 150;
|
||||
const barHeights = () => Math.floor(Math.random() * maxHeight);
|
||||
|
||||
const ChartSkeleton = () => (
|
||||
|
||||
<div className="flex justify-center h-96 w-full p-4">
|
||||
{/* Chart bars */}
|
||||
<div className=" w-full flex items-end gap-10">
|
||||
{[...Array(9)].map((_, i) => (
|
||||
<div key={i} className="flex flex-col items-center gap-1">
|
||||
<Skeleton
|
||||
className="w-10 bg-gray-700 rounded-md"
|
||||
style={{ height: `${barHeights()}px` }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Left space for Y-axis label */}
|
||||
<div className="flex flex-col justify-between mr-2">
|
||||
<Skeleton className="h-6 w-15 bg-gray-700 rounded" />
|
||||
<Skeleton className="h-6 w-15 bg-gray-700 rounded" />
|
||||
<Skeleton className="h-6 w-15 bg-gray-700 rounded" />
|
||||
<Skeleton className="h-6 w-15 bg-gray-700 rounded" />
|
||||
<Skeleton className="h-6 w-15 bg-gray-700 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export function StrategicAlignmentPopup({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: StrategicAlignmentPopupProps) {
|
||||
const [data, setData] = useState<StrategicAlignmentData[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchData();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await apiService.select({
|
||||
ProcessName: "project",
|
||||
OutputFields: [
|
||||
"strategic_theme",
|
||||
"sum(operational_fee) as operational_fee_sum",
|
||||
],
|
||||
GroupBy: ["strategic_theme"],
|
||||
});
|
||||
|
||||
const responseData =
|
||||
typeof response.data === "string"
|
||||
? JSON.parse(response.data)
|
||||
: response.data;
|
||||
|
||||
const processedData = responseData
|
||||
.map((item: any) => ({
|
||||
strategic_theme: item.strategic_theme || "N/A",
|
||||
operational_fee_sum: Math.max(0, Number(item.operational_fee_sum)),
|
||||
}))
|
||||
.filter((item: StrategicAlignmentData) => item.strategic_theme !== "");
|
||||
|
||||
const total = processedData.reduce(
|
||||
(acc: number, item: StrategicAlignmentData) =>
|
||||
acc + item.operational_fee_sum,
|
||||
0
|
||||
);
|
||||
|
||||
const dataWithPercentage = processedData.map(
|
||||
(item: StrategicAlignmentData) => ({
|
||||
...item,
|
||||
percentage:
|
||||
total > 0
|
||||
? Math.round((item.operational_fee_sum / total) * 100)
|
||||
: 0,
|
||||
})
|
||||
);
|
||||
setData(dataWithPercentage || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching strategic alignment data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="w-full max-w-4xl bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] text-white border-none">
|
||||
<DialogHeader className="border-b-3 mb-10 py-2 w-full pb-4 border-b-2 border-gray-500/20">
|
||||
<DialogTitle className="ml-auto ">میزان انطباق راهبردی</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<ChartSkeleton />
|
||||
) : (
|
||||
<>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-96 w-full">
|
||||
<BarChart
|
||||
data={data}
|
||||
margin={{ left: 12, right: 12 }}
|
||||
barGap={15}
|
||||
barSize={30}
|
||||
accessibilityLayer
|
||||
>
|
||||
<CartesianGrid vertical={false} stroke="#475569" />
|
||||
<XAxis
|
||||
dataKey="strategic_theme"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={10}
|
||||
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
||||
tickFormatter={(value) =>
|
||||
`${formatNumber(Math.round(value))}%`
|
||||
}
|
||||
label={{
|
||||
value: "تعداد برنامه ها" ,
|
||||
angle: -90,
|
||||
position: "insideLeft",
|
||||
fill: "#94a3b8",
|
||||
fontSize: 14,
|
||||
offset: 0,
|
||||
dy: 0,
|
||||
style: { textAnchor: "middle" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<Bar dataKey="percentage" radius={[8, 8, 0, 0]}>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={chartConfig.percentage.color} />
|
||||
))}
|
||||
<LabelList
|
||||
dataKey="percentage"
|
||||
position="top"
|
||||
style={{
|
||||
fill: "#ffffff",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
|
||||
/>
|
||||
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</ResponsiveContainer>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
13
app/components/ui/skeleton.tsx
Normal file
13
app/components/ui/skeleton.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { cn } from "~/lib/utils"
|
||||
|
||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="skeleton"
|
||||
className={cn("bg-accent animate-pulse rounded-md", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
61
app/components/ui/tooltip.tsx
Normal file
61
app/components/ui/tooltip.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
80
app/root.tsx
80
app/root.tsx
|
|
@ -41,51 +41,51 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||
</head>
|
||||
<body className="font-persian bg-gray-900 text-white">
|
||||
<AuthProvider>
|
||||
<GlobalRouteGuard>{children}</GlobalRouteGuard>
|
||||
<Toaster
|
||||
position="top-center"
|
||||
reverseOrder={false}
|
||||
gutter={8}
|
||||
containerClassName=""
|
||||
containerStyle={{}}
|
||||
toastOptions={{
|
||||
// Define default options
|
||||
className: "",
|
||||
duration: 4000,
|
||||
style: {
|
||||
background: "rgba(31, 41, 55, 0.95)",
|
||||
color: "#fff",
|
||||
fontFamily:
|
||||
"Vazirmatn, Inter, ui-sans-serif, system-ui, sans-serif",
|
||||
direction: "rtl",
|
||||
textAlign: "right",
|
||||
border: "1px solid rgba(16, 185, 129, 0.3)",
|
||||
},
|
||||
// Default options for specific types
|
||||
success: {
|
||||
duration: 3000,
|
||||
style: {
|
||||
background: "rgba(16, 185, 129, 0.9)",
|
||||
color: "#fff",
|
||||
},
|
||||
iconTheme: {
|
||||
primary: "#fff",
|
||||
secondary: "#10b981",
|
||||
},
|
||||
},
|
||||
error: {
|
||||
<GlobalRouteGuard>{children}</GlobalRouteGuard>
|
||||
<Toaster
|
||||
position="top-center"
|
||||
reverseOrder={false}
|
||||
gutter={8}
|
||||
containerClassName=""
|
||||
containerStyle={{}}
|
||||
toastOptions={{
|
||||
// Define default options
|
||||
className: "",
|
||||
duration: 4000,
|
||||
style: {
|
||||
background: "rgba(239, 68, 68, 0.9)",
|
||||
background: "rgba(31, 41, 55, 0.95)",
|
||||
color: "#fff",
|
||||
fontFamily:
|
||||
"Vazirmatn, Inter, ui-sans-serif, system-ui, sans-serif",
|
||||
direction: "rtl",
|
||||
textAlign: "right",
|
||||
border: "1px solid rgba(16, 185, 129, 0.3)",
|
||||
},
|
||||
iconTheme: {
|
||||
primary: "#fff",
|
||||
secondary: "#ef4444",
|
||||
// Default options for specific types
|
||||
success: {
|
||||
duration: 3000,
|
||||
style: {
|
||||
background: "rgba(16, 185, 129, 0.9)",
|
||||
color: "#fff",
|
||||
},
|
||||
iconTheme: {
|
||||
primary: "#fff",
|
||||
secondary: "#10b981",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
error: {
|
||||
duration: 4000,
|
||||
style: {
|
||||
background: "rgba(239, 68, 68, 0.9)",
|
||||
color: "#fff",
|
||||
},
|
||||
iconTheme: {
|
||||
primary: "#fff",
|
||||
secondary: "#ef4444",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</AuthProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"@types/d3": "^7.4.3",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ importers:
|
|||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@react-router/node':
|
||||
specifier: ^7.7.0
|
||||
version: 7.8.2(react-router@7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2)
|
||||
|
|
@ -755,6 +758,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8':
|
||||
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||
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
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
|
|
@ -3119,6 +3135,26 @@ snapshots:
|
|||
'@types/react': 19.1.12
|
||||
'@types/react-dom': 19.1.9(@types/react@19.1.12)
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1)
|
||||
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.12
|
||||
'@types/react-dom': 19.1.9(@types/react@19.1.12)
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.12)(react@19.1.1)':
|
||||
dependencies:
|
||||
react: 19.1.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user