mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat: implement comprehensive light/dark theme support for all chart components
- Add CSS variables to all chart tooltips and components for dynamic theme switching - Update AlertPill component to use theme-aware colors (--chart-alert-bg, --chart-alert-text) - Change all alert icons to use red color (--chart-fail) consistently across charts - Add ScatterDataPoint type to centralized types.ts - Fix scatter-plot-with-selection component type definitions and imports - Migrate shadcn Select component to shared folder structure - Update ThreatMap to use shadcn Select instead of native select element - Ensure all graph components dynamically update colors when theme toggles This enables seamless light/dark theme support across: - ChartTooltip and MultiSeriesChartTooltip - LineChart, RadarChart, ScatterPlot custom tooltips - AlertPill and icon coloring - All form elements now use shadcn components
This commit is contained in:
@@ -8,24 +8,12 @@ import {
|
||||
getProvidersOverview,
|
||||
} from "@/actions/overview/overview";
|
||||
import { FilterControls } from "@/components/filters";
|
||||
import {
|
||||
DonutChart,
|
||||
HorizontalBarChart,
|
||||
LineChart,
|
||||
RadarChart,
|
||||
RadialChart,
|
||||
SankeyChart,
|
||||
ScatterPlot,
|
||||
ThreatMap,
|
||||
} from "@/components/graphs";
|
||||
import { LighthouseBanner } from "@/components/lighthouse";
|
||||
import {
|
||||
FindingsBySeverityChart,
|
||||
FindingsByStatusChart,
|
||||
LinkToFindings,
|
||||
ProvidersOverview,
|
||||
RadarChartWithSelection,
|
||||
ScatterPlotWithSelection,
|
||||
SkeletonFindingsBySeverityChart,
|
||||
SkeletonFindingsByStatusChart,
|
||||
SkeletonProvidersOverview,
|
||||
@@ -39,659 +27,6 @@ import { FindingProps, SearchParamsProps } from "@/types";
|
||||
|
||||
const FILTER_PREFIX = "filter[";
|
||||
|
||||
// Sample data for ThreatMap component
|
||||
const RISK_LEVELS = {
|
||||
LOW_HIGH: "low-high",
|
||||
HIGH: "high",
|
||||
CRITICAL: "critical",
|
||||
} as const;
|
||||
|
||||
const sampleThreatMapData = {
|
||||
regions: ["US East", "US West", "Europe", "Asia Pacific", "South America"],
|
||||
locations: [
|
||||
{
|
||||
id: "us-east-1",
|
||||
name: "US East-1",
|
||||
region: "US East",
|
||||
coordinates: [-77.4875, 39.0438] as [number, number],
|
||||
totalFindings: 455,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
change: 21,
|
||||
severityData: [
|
||||
{ name: "Info", value: 10, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 543,
|
||||
percentage: 25,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 221,
|
||||
percentage: 18,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 1232,
|
||||
percentage: 32,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 432,
|
||||
percentage: 22,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "us-west-2",
|
||||
name: "US West-2",
|
||||
region: "US West",
|
||||
coordinates: [-121.7269, 45.6387] as [number, number],
|
||||
totalFindings: 324,
|
||||
riskLevel: RISK_LEVELS.HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 8, percentage: 2, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 89,
|
||||
percentage: 15,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 156,
|
||||
percentage: 28,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 187,
|
||||
percentage: 35,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 124,
|
||||
percentage: 20,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "eu-west-1",
|
||||
name: "EU West-1",
|
||||
region: "Europe",
|
||||
coordinates: [-6.2603, 53.3498] as [number, number],
|
||||
totalFindings: 567,
|
||||
riskLevel: RISK_LEVELS.CRITICAL,
|
||||
change: 15,
|
||||
severityData: [
|
||||
{ name: "Info", value: 12, percentage: 2, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 98,
|
||||
percentage: 10,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 203,
|
||||
percentage: 22,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 354,
|
||||
percentage: 38,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 267,
|
||||
percentage: 28,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "eu-central-1",
|
||||
name: "EU Central-1",
|
||||
region: "Europe",
|
||||
coordinates: [8.6821, 50.1109] as [number, number],
|
||||
totalFindings: 289,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 15, percentage: 5, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 134,
|
||||
percentage: 32,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 87,
|
||||
percentage: 22,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 103,
|
||||
percentage: 28,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 43,
|
||||
percentage: 13,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ap-northeast-1",
|
||||
name: "AP Northeast-1",
|
||||
region: "Asia Pacific",
|
||||
coordinates: [139.6917, 35.6895] as [number, number],
|
||||
totalFindings: 412,
|
||||
riskLevel: RISK_LEVELS.HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 9, percentage: 2, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 87,
|
||||
percentage: 18,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 145,
|
||||
percentage: 26,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 198,
|
||||
percentage: 34,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 112,
|
||||
percentage: 20,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ap-southeast-1",
|
||||
name: "AP Southeast-1",
|
||||
region: "Asia Pacific",
|
||||
coordinates: [103.8198, 1.3521] as [number, number],
|
||||
totalFindings: 378,
|
||||
riskLevel: RISK_LEVELS.HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 11, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 76,
|
||||
percentage: 16,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 132,
|
||||
percentage: 25,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 187,
|
||||
percentage: 36,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 94,
|
||||
percentage: 20,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ap-south-1",
|
||||
name: "AP South-1",
|
||||
region: "Asia Pacific",
|
||||
coordinates: [72.8777, 19.076] as [number, number],
|
||||
totalFindings: 234,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
change: -8,
|
||||
severityData: [
|
||||
{ name: "Info", value: 7, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 98,
|
||||
percentage: 28,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 76,
|
||||
percentage: 24,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 89,
|
||||
percentage: 30,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 43,
|
||||
percentage: 15,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sa-east-1",
|
||||
name: "SA East-1",
|
||||
region: "South America",
|
||||
coordinates: [-46.6333, -23.5505] as [number, number],
|
||||
totalFindings: 189,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 6, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 87,
|
||||
percentage: 30,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 54,
|
||||
percentage: 22,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 67,
|
||||
percentage: 28,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 32,
|
||||
percentage: 17,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ca-central-1",
|
||||
name: "CA Central-1",
|
||||
region: "US East",
|
||||
coordinates: [-73.5673, 45.5017] as [number, number],
|
||||
totalFindings: 267,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 8, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 112,
|
||||
percentage: 27,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 87,
|
||||
percentage: 23,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 98,
|
||||
percentage: 30,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 54,
|
||||
percentage: 17,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ap-southeast-2",
|
||||
name: "AP Southeast-2",
|
||||
region: "Asia Pacific",
|
||||
coordinates: [151.2093, -33.8688] as [number, number],
|
||||
totalFindings: 345,
|
||||
riskLevel: RISK_LEVELS.HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 10, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 78,
|
||||
percentage: 17,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 121,
|
||||
percentage: 25,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 167,
|
||||
percentage: 35,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 89,
|
||||
percentage: 20,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "eu-west-2",
|
||||
name: "EU West-2",
|
||||
region: "Europe",
|
||||
coordinates: [-0.1278, 51.5074] as [number, number],
|
||||
totalFindings: 423,
|
||||
riskLevel: RISK_LEVELS.HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 11, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 98,
|
||||
percentage: 19,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 154,
|
||||
percentage: 26,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 189,
|
||||
percentage: 32,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 112,
|
||||
percentage: 20,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "us-east-2",
|
||||
name: "US East-2",
|
||||
region: "US East",
|
||||
coordinates: [-82.9988, 39.9612] as [number, number],
|
||||
totalFindings: 298,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 9, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 123,
|
||||
percentage: 28,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 89,
|
||||
percentage: 23,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 101,
|
||||
percentage: 29,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 54,
|
||||
percentage: 17,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "us-west-1",
|
||||
name: "US West-1",
|
||||
region: "US West",
|
||||
coordinates: [-121.8863, 37.3382] as [number, number],
|
||||
totalFindings: 389,
|
||||
riskLevel: RISK_LEVELS.HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 10, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 87,
|
||||
percentage: 18,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 143,
|
||||
percentage: 26,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 176,
|
||||
percentage: 33,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 98,
|
||||
percentage: 20,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "eu-north-1",
|
||||
name: "EU North-1",
|
||||
region: "Europe",
|
||||
coordinates: [18.0686, 59.3293] as [number, number],
|
||||
totalFindings: 213,
|
||||
riskLevel: RISK_LEVELS.LOW_HIGH,
|
||||
severityData: [
|
||||
{ name: "Info", value: 7, percentage: 3, color: "var(--chart-info)" },
|
||||
{
|
||||
name: "Low",
|
||||
value: 89,
|
||||
percentage: 29,
|
||||
color: "var(--chart-warning)",
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 67,
|
||||
percentage: 23,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 76,
|
||||
percentage: 28,
|
||||
color: "var(--chart-danger)",
|
||||
},
|
||||
{
|
||||
name: "Critical",
|
||||
value: 43,
|
||||
percentage: 17,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Sample data for DonutChart (Findings by Severity)
|
||||
const sampleDonutChartData = [
|
||||
{
|
||||
name: "Critical",
|
||||
value: 432,
|
||||
percentage: 22,
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
change: 5,
|
||||
},
|
||||
{
|
||||
name: "High",
|
||||
value: 1232,
|
||||
percentage: 32,
|
||||
color: "var(--chart-danger)",
|
||||
change: 8,
|
||||
},
|
||||
{
|
||||
name: "Medium",
|
||||
value: 925,
|
||||
percentage: 28,
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
change: -3,
|
||||
},
|
||||
{
|
||||
name: "Low",
|
||||
value: 543,
|
||||
percentage: 15,
|
||||
color: "var(--chart-warning)",
|
||||
change: -2,
|
||||
},
|
||||
{
|
||||
name: "Info",
|
||||
value: 108,
|
||||
percentage: 3,
|
||||
color: "var(--chart-info)",
|
||||
change: 0,
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for LineChart
|
||||
const sampleLineChartData = [
|
||||
{ date: "Jan", critical: 385, high: 1100, medium: 820, low: 450 },
|
||||
{ date: "Feb", critical: 398, high: 1150, medium: 870, low: 480 },
|
||||
{ date: "Mar", critical: 410, high: 1180, medium: 890, low: 510 },
|
||||
{ date: "Apr", critical: 425, high: 1210, medium: 915, low: 530 },
|
||||
{ date: "May", critical: 418, high: 1195, medium: 905, low: 520 },
|
||||
{ date: "Jun", critical: 432, high: 1232, medium: 925, low: 543 },
|
||||
];
|
||||
|
||||
const sampleLineConfig = [
|
||||
{
|
||||
dataKey: "critical",
|
||||
color: "var(--chart-danger-emphasis)",
|
||||
label: "Critical",
|
||||
},
|
||||
{ dataKey: "high", color: "var(--chart-danger)", label: "High" },
|
||||
{
|
||||
dataKey: "medium",
|
||||
color: "var(--chart-warning-emphasis)",
|
||||
label: "Medium",
|
||||
},
|
||||
{ dataKey: "low", color: "var(--chart-warning)", label: "Low" },
|
||||
];
|
||||
|
||||
// Sample data for HorizontalBarChart (Severity Distribution)
|
||||
const sampleHorizontalBarData = [
|
||||
{
|
||||
name: "Critical",
|
||||
value: 432,
|
||||
percentage: 22,
|
||||
newFindings: 15,
|
||||
change: 5,
|
||||
},
|
||||
{ name: "High", value: 1232, percentage: 32, newFindings: 45, change: 8 },
|
||||
{
|
||||
name: "Medium",
|
||||
value: 925,
|
||||
percentage: 28,
|
||||
newFindings: 32,
|
||||
change: -3,
|
||||
},
|
||||
{ name: "Low", value: 543, percentage: 15, newFindings: 18, change: -2 },
|
||||
{ name: "Info", value: 108, percentage: 3, newFindings: 5, change: 0 },
|
||||
];
|
||||
|
||||
// Sample data for SankeyChart
|
||||
const sampleSankeyData = {
|
||||
nodes: [
|
||||
{ name: "Success" },
|
||||
{ name: "Fail" },
|
||||
{ name: "AWS" },
|
||||
{ name: "Azure" },
|
||||
{ name: "Google" },
|
||||
{ name: "Critical", newFindings: 5, change: 15 },
|
||||
{ name: "High", newFindings: 12, change: 8 },
|
||||
{ name: "Medium", newFindings: 28, change: -3 },
|
||||
{ name: "Low", newFindings: 45, change: -8 },
|
||||
{ name: "Info", newFindings: 18, change: 2 },
|
||||
],
|
||||
links: [
|
||||
// Success to Providers
|
||||
{ source: 0, target: 2, value: 300 }, // Success -> AWS
|
||||
{ source: 0, target: 3, value: 283 }, // Success -> Azure
|
||||
{ source: 0, target: 4, value: 300 }, // Success -> Google
|
||||
|
||||
// Fail to Providers
|
||||
{ source: 1, target: 2, value: 200 }, // Fail -> AWS
|
||||
{ source: 1, target: 3, value: 300 }, // Fail -> Azure
|
||||
{ source: 1, target: 4, value: 500 }, // Fail -> Google
|
||||
|
||||
// AWS to Severities (Critical to Info order)
|
||||
{ source: 2, target: 5, value: 8 }, // AWS -> Critical
|
||||
{ source: 2, target: 6, value: 25 }, // AWS -> High
|
||||
{ source: 2, target: 7, value: 67 }, // AWS -> Medium
|
||||
{ source: 2, target: 8, value: 80 }, // AWS -> Low
|
||||
{ source: 2, target: 9, value: 20 }, // AWS -> Info
|
||||
|
||||
// Azure to Severities
|
||||
{ source: 3, target: 5, value: 10 }, // Azure -> Critical
|
||||
{ source: 3, target: 6, value: 35 }, // Azure -> High
|
||||
{ source: 3, target: 7, value: 95 }, // Azure -> Medium
|
||||
{ source: 3, target: 8, value: 130 }, // Azure -> Low
|
||||
{ source: 3, target: 9, value: 30 }, // Azure -> Info
|
||||
|
||||
// Google to Severities
|
||||
{ source: 4, target: 5, value: 7 }, // Google -> Critical
|
||||
{ source: 4, target: 6, value: 40 }, // Google -> High
|
||||
{ source: 4, target: 7, value: 163 }, // Google -> Medium
|
||||
{ source: 4, target: 8, value: 190 }, // Google -> Low
|
||||
{ source: 4, target: 9, value: 100 }, // Google -> Info
|
||||
],
|
||||
};
|
||||
|
||||
// Sample data for ScatterPlot
|
||||
const sampleScatterPlotData = [
|
||||
{ x: 8.2, y: 45, provider: "AWS", name: "S3 Buckets", size: 120 },
|
||||
{ x: 7.5, y: 62, provider: "AWS", name: "EC2 Instances", size: 95 },
|
||||
{ x: 6.8, y: 78, provider: "Azure", name: "Storage Accounts", size: 85 },
|
||||
{ x: 9.1, y: 32, provider: "AWS", name: "RDS Databases", size: 110 },
|
||||
{ x: 5.4, y: 88, provider: "Google", name: "Cloud Storage", size: 75 },
|
||||
{ x: 7.9, y: 55, provider: "Azure", name: "Virtual Machines", size: 90 },
|
||||
{ x: 8.5, y: 48, provider: "AWS", name: "Lambda Functions", size: 105 },
|
||||
{ x: 6.2, y: 82, provider: "Google", name: "Compute Engine", size: 80 },
|
||||
{ x: 7.2, y: 68, provider: "Azure", name: "SQL Databases", size: 88 },
|
||||
{ x: 8.8, y: 41, provider: "AWS", name: "IAM Users", size: 115 },
|
||||
];
|
||||
|
||||
// Extract only query params that start with "filter[" for API calls
|
||||
function pickFilterParams(
|
||||
params: SearchParamsProps | undefined | null,
|
||||
@@ -741,90 +76,6 @@ export default async function Home({
|
||||
<SSRDataNewFindingsTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Security Categories
|
||||
</h3>
|
||||
<RadarChartWithSelection />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">Threat Map</h3>
|
||||
<ThreatMap data={sampleThreatMapData} height={400} />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Findings by Severity
|
||||
</h3>
|
||||
<HorizontalBarChart data={sampleHorizontalBarData} />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Severity Distribution
|
||||
</h3>
|
||||
<DonutChart
|
||||
data={sampleDonutChartData}
|
||||
height={350}
|
||||
showLegend
|
||||
centerLabel={{
|
||||
value: "3,240",
|
||||
label: "Total Findings",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Findings Trend Over Time
|
||||
</h3>
|
||||
<LineChart
|
||||
data={sampleLineChartData}
|
||||
lines={sampleLineConfig}
|
||||
height={350}
|
||||
xLabel="Month"
|
||||
yLabel="Findings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Provider to Severity Flow
|
||||
</h3>
|
||||
<SankeyChart data={sampleSankeyData} height={400} />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">Compliance Score</h3>
|
||||
<RadialChart
|
||||
percentage={78}
|
||||
label="Overall Compliance"
|
||||
color="var(--chart-success-color)"
|
||||
height={350}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Risk Score vs Compliance by Service
|
||||
</h3>
|
||||
<ScatterPlotWithSelection
|
||||
data={sampleScatterPlotData}
|
||||
height={400}
|
||||
xLabel="Risk Score"
|
||||
yLabel="Compliance %"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ContentLayout>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,10 @@ const CustomTooltip = ({ active, payload }: any) => {
|
||||
</span>
|
||||
</div>
|
||||
{data.change !== undefined && (
|
||||
<p className="mt-2 text-xs" style={{ color: "var(--chart-text-secondary)" }}>
|
||||
<p
|
||||
className="mt-2 text-xs"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
<span className="font-bold">
|
||||
{data.change > 0 ? "+" : ""}
|
||||
{data.change}%
|
||||
|
||||
@@ -99,7 +99,7 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Bell
|
||||
size={14}
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
style={{ color: "var(--chart-fail)" }}
|
||||
/>
|
||||
<span
|
||||
className="text-sm"
|
||||
|
||||
@@ -55,7 +55,10 @@ const CustomLineTooltip = ({
|
||||
borderColor: "var(--chart-border-emphasis)",
|
||||
}}
|
||||
>
|
||||
<p className="mb-3 text-xs" style={{ color: "var(--chart-text-secondary)" }}>
|
||||
<p
|
||||
className="mb-3 text-xs"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
|
||||
@@ -75,13 +78,19 @@ const CustomLineTooltip = ({
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: item.stroke }}
|
||||
/>
|
||||
<span className="text-sm" style={{ color: "var(--chart-text-primary)" }}>
|
||||
<span
|
||||
className="text-sm"
|
||||
style={{ color: "var(--chart-text-primary)" }}
|
||||
>
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
{newFindings !== undefined && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell size={14} style={{ color: "var(--chart-text-secondary)" }} />
|
||||
<Bell
|
||||
size={14}
|
||||
style={{ color: "var(--chart-fail)" }}
|
||||
/>
|
||||
<span
|
||||
className="text-xs"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
@@ -91,7 +100,10 @@ const CustomLineTooltip = ({
|
||||
</div>
|
||||
)}
|
||||
{change !== undefined && typeof change === "number" && (
|
||||
<p className="text-xs" style={{ color: "var(--chart-text-secondary)" }}>
|
||||
<p
|
||||
className="text-xs"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
<span className="font-bold">
|
||||
{change > 0 ? "+" : ""}
|
||||
{change}%
|
||||
|
||||
@@ -96,7 +96,9 @@ const CustomDot = (props: any) => {
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
r={isSelected ? 9 : 6}
|
||||
fill={isSelected ? "var(--chart-success-color)" : "var(--chart-radar-primary)"}
|
||||
fill={
|
||||
isSelected ? "var(--chart-success-color)" : "var(--chart-radar-primary)"
|
||||
}
|
||||
fillOpacity={1}
|
||||
className={isSelected ? "drop-shadow-[0_0_8px_#86da26]" : ""}
|
||||
style={{
|
||||
|
||||
@@ -68,7 +68,10 @@ export function RadialChart({
|
||||
y="50%"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
className="fill-white text-4xl font-bold"
|
||||
className="text-4xl font-bold"
|
||||
style={{
|
||||
fill: "var(--chart-text-primary)",
|
||||
}}
|
||||
>
|
||||
{percentage}%
|
||||
</text>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { useState } from "react";
|
||||
import { Rectangle, ResponsiveContainer, Sankey, Tooltip } from "recharts";
|
||||
|
||||
import { CHART_COLORS } from "./shared/constants";
|
||||
import { ChartTooltip } from "./shared/chart-tooltip";
|
||||
import { CHART_COLORS } from "./shared/constants";
|
||||
|
||||
interface SankeyNode {
|
||||
name: string;
|
||||
@@ -193,8 +193,7 @@ const CustomLink = (props: any) => {
|
||||
const color = COLORS[sourceName] || CHART_COLORS.defaultColor;
|
||||
const isHidden = targetName === "";
|
||||
|
||||
const isHovered =
|
||||
props.hoveredLink !== null && props.hoveredLink === index;
|
||||
const isHovered = props.hoveredLink !== null && props.hoveredLink === index;
|
||||
const hasHoveredLink = props.hoveredLink !== null;
|
||||
|
||||
const pathD = `
|
||||
|
||||
@@ -15,14 +15,7 @@ import { AlertPill } from "./shared/alert-pill";
|
||||
import { ChartLegend } from "./shared/chart-legend";
|
||||
import { CHART_COLORS } from "./shared/constants";
|
||||
import { getSeverityColorByRiskScore } from "./shared/utils";
|
||||
|
||||
interface ScatterDataPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
provider: string;
|
||||
name: string;
|
||||
size?: number;
|
||||
}
|
||||
import type { ScatterDataPoint } from "./types";
|
||||
|
||||
interface ScatterPlotProps {
|
||||
data: ScatterDataPoint[];
|
||||
|
||||
@@ -17,13 +17,14 @@ export function AlertPill({
|
||||
}: AlertPillProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="bg-alert-pill-bg flex items-center gap-1 rounded-full px-2 py-1">
|
||||
<AlertTriangle size={iconSize} className="text-alert-pill-text" />
|
||||
<div
|
||||
className="flex items-center gap-1 rounded-full px-2 py-1"
|
||||
style={{ backgroundColor: "var(--chart-alert-bg)" }}
|
||||
>
|
||||
<AlertTriangle size={iconSize} style={{ color: "var(--chart-alert-text)" }} />
|
||||
<span
|
||||
className={cn(
|
||||
`text-${textSize}`,
|
||||
"text-alert-pill-text font-semibold",
|
||||
)}
|
||||
className={cn(`text-${textSize}`, "font-semibold")}
|
||||
style={{ color: "var(--chart-alert-text)" }}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Bell, VolumeX } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { TooltipData } from "../types";
|
||||
import { CHART_COLORS } from "./constants";
|
||||
|
||||
interface ChartTooltipProps {
|
||||
active?: boolean;
|
||||
@@ -27,7 +28,13 @@ export function ChartTooltip({
|
||||
const color = payload[0].color || data.color;
|
||||
|
||||
return (
|
||||
<div className="min-w-[200px] rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<div
|
||||
className="min-w-[200px] rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{showColorIndicator && color && (
|
||||
<div
|
||||
@@ -38,10 +45,18 @@ export function ChartTooltip({
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
)}
|
||||
<p className="text-sm font-semibold text-white">{label || data.name}</p>
|
||||
<p
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{label || data.name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="mt-1 text-xs text-white">
|
||||
<p
|
||||
className="mt-1 text-xs"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{typeof data.value === "number"
|
||||
? data.value.toLocaleString()
|
||||
: data.value}
|
||||
@@ -50,8 +65,8 @@ export function ChartTooltip({
|
||||
|
||||
{data.newFindings !== undefined && data.newFindings > 0 && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<Bell size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">
|
||||
<Bell size={14} style={{ color: "var(--chart-fail)" }} />
|
||||
<span className="text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
{data.newFindings} New Findings
|
||||
</span>
|
||||
</div>
|
||||
@@ -59,20 +74,24 @@ export function ChartTooltip({
|
||||
|
||||
{data.new !== undefined && data.new > 0 && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<Bell size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">{data.new} New</span>
|
||||
<Bell size={14} style={{ color: "var(--chart-fail)" }} />
|
||||
<span className="text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
{data.new} New
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.muted !== undefined && data.muted > 0 && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<VolumeX size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">{data.muted} Muted</span>
|
||||
<VolumeX size={14} style={{ color: CHART_COLORS.textSecondary }} />
|
||||
<span className="text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
{data.muted} Muted
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.change !== undefined && (
|
||||
<p className="mt-1 text-xs text-slate-400">
|
||||
<p className="mt-1 text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
<span className="font-bold">
|
||||
{data.change > 0 ? "+" : ""}
|
||||
{data.change}%
|
||||
@@ -97,8 +116,19 @@ export function MultiSeriesChartTooltip({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-w-[200px] rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<p className="mb-2 text-sm font-semibold text-white">{label}</p>
|
||||
<div
|
||||
className="min-w-[200px] rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<p
|
||||
className="mb-2 text-sm font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
|
||||
{payload.map((entry: any, index: number) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
@@ -106,12 +136,17 @@ export function MultiSeriesChartTooltip({
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-xs text-white">{entry.name}:</span>
|
||||
<span className="text-xs font-semibold text-white">
|
||||
<span className="text-xs" style={{ color: CHART_COLORS.textPrimary }}>
|
||||
{entry.name}:
|
||||
</span>
|
||||
<span
|
||||
className="text-xs font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{entry.value}
|
||||
</span>
|
||||
{entry.payload[`${entry.dataKey}_change`] && (
|
||||
<span className="text-xs text-slate-400">
|
||||
<span className="text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
({entry.payload[`${entry.dataKey}_change`] > 0 ? "+" : ""}
|
||||
{entry.payload[`${entry.dataKey}_change`]}%)
|
||||
</span>
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
GeoJsonProperties,
|
||||
Geometry,
|
||||
} from "geojson";
|
||||
import { AlertTriangle, ChevronDown, Info, MapPin } from "lucide-react";
|
||||
import { AlertTriangle, Info, MapPin } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { feature } from "topojson-client";
|
||||
import type {
|
||||
@@ -16,6 +16,14 @@ import type {
|
||||
Topology,
|
||||
} from "topojson-specification";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/shared/shadcn";
|
||||
|
||||
import { HorizontalBarChart } from "./horizontal-bar-chart";
|
||||
import { BarDataPoint } from "./types";
|
||||
|
||||
@@ -152,7 +160,10 @@ function MapTooltip({
|
||||
</span>
|
||||
</div>
|
||||
{location.change !== undefined && (
|
||||
<p className="mt-1 text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
<p
|
||||
className="mt-1 text-xs"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
>
|
||||
<span className="font-bold">
|
||||
{location.change > 0 ? "+" : ""}
|
||||
{location.change}%
|
||||
@@ -180,7 +191,11 @@ function EmptyState() {
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Info size={48} className="mx-auto mb-2" style={{ color: CHART_COLORS.textSecondary }} />
|
||||
<Info
|
||||
size={48}
|
||||
className="mx-auto mb-2"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
/>
|
||||
<p className="text-sm" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
Select a location on the map to view details
|
||||
</p>
|
||||
@@ -398,30 +413,26 @@ export function ThreatMap({
|
||||
>
|
||||
Threat Map
|
||||
</h3>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedRegion}
|
||||
onChange={(e) => setSelectedRegion(e.target.value)}
|
||||
className="appearance-none rounded-lg border px-4 py-2 pr-10 text-sm focus:outline-none"
|
||||
<Select value={selectedRegion} onValueChange={setSelectedRegion}>
|
||||
<SelectTrigger
|
||||
className="w-full rounded-lg"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
color: CHART_COLORS.textPrimary,
|
||||
}}
|
||||
>
|
||||
<option value="All Regions">All Regions</option>
|
||||
<SelectValue placeholder="All Regions" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="All Regions">All Regions</SelectItem>
|
||||
{data.regions.map((region) => (
|
||||
<option key={region} value={region}>
|
||||
<SelectItem key={region} value={region}>
|
||||
{region}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
/>
|
||||
</div>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -502,4 +513,3 @@ export function ThreatMap({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,14 @@ export interface RadarDataPoint {
|
||||
change?: number;
|
||||
}
|
||||
|
||||
export interface ScatterDataPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
provider: string;
|
||||
name: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface LineConfig {
|
||||
dataKey: string;
|
||||
color: string;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { RadarChart } from "@/components/graphs";
|
||||
import type { RadarDataPoint } from "@/components/graphs/types";
|
||||
|
||||
interface RadarChartWithSelectionProps {
|
||||
data?: RadarDataPoint[];
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const sampleRadarData: RadarDataPoint[] = [
|
||||
{ category: "IAM", value: 85, change: 5 },
|
||||
{ category: "Storage", value: 72, change: -2 },
|
||||
{ category: "Network", value: 88, change: 8 },
|
||||
{ category: "Compute", value: 76, change: 3 },
|
||||
{ category: "Database", value: 82, change: -1 },
|
||||
{ category: "Encryption", value: 91, change: 6 },
|
||||
];
|
||||
|
||||
export function RadarChartWithSelection({
|
||||
data = sampleRadarData,
|
||||
height = 400,
|
||||
}: RadarChartWithSelectionProps) {
|
||||
const [selectedPoint, setSelectedPoint] = useState<RadarDataPoint | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
return (
|
||||
<RadarChart
|
||||
data={data}
|
||||
height={height}
|
||||
selectedPoint={selectedPoint}
|
||||
onSelectPoint={setSelectedPoint}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { ScatterPlot } from "@/components/graphs";
|
||||
|
||||
interface DataPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
provider: string;
|
||||
name: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface ScatterPlotWithSelectionProps {
|
||||
data: DataPoint[];
|
||||
height?: number;
|
||||
xLabel?: string;
|
||||
yLabel?: string;
|
||||
}
|
||||
|
||||
export function ScatterPlotWithSelection({
|
||||
data,
|
||||
height = 400,
|
||||
xLabel = "Risk Score",
|
||||
yLabel = "Compliance %",
|
||||
}: ScatterPlotWithSelectionProps) {
|
||||
const [selectedPoint, setSelectedPoint] = useState<DataPoint | null>(null);
|
||||
|
||||
return (
|
||||
<ScatterPlot
|
||||
data={data}
|
||||
height={height}
|
||||
xLabel={xLabel}
|
||||
yLabel={yLabel}
|
||||
selectedPoint={selectedPoint}
|
||||
onSelectPoint={setSelectedPoint}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./select/Select";
|
||||
@@ -0,0 +1,12 @@
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./Select";
|
||||
@@ -12,6 +12,5 @@ export * from "./feedback-banner/feedback-banner";
|
||||
export * from "./headers/navigation-header";
|
||||
export * from "./label/Label";
|
||||
export * from "./main-layout/main-layout";
|
||||
export * from "./select/Select";
|
||||
export * from "./sidebar";
|
||||
export * from "./toast";
|
||||
|
||||
@@ -10,16 +10,15 @@ import Link from "next/link";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { getPaginationInfo } from "@/lib";
|
||||
import { MetaDataProps } from "@/types";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../select/Select";
|
||||
} from "@/components/shared/shadcn";
|
||||
import { getPaginationInfo } from "@/lib";
|
||||
import { MetaDataProps } from "@/types";
|
||||
|
||||
interface DataTablePaginationProps {
|
||||
metadata?: MetaDataProps;
|
||||
|
||||
Reference in New Issue
Block a user