refactor: integrate PanelCard and enhance dashboard components for improved layout
CI / changes (push) Successful in 10s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 52s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m7s

Refactored multiple dashboard components to utilize the new PanelCard for better organization and presentation. Updated the DashboardFramePanel, DashboardQuickLinks, and DashboardOperationsBreakdown components to streamline layouts and enhance user experience. Removed deprecated components and improved loading states in various sections, ensuring a cohesive interface throughout the application.
This commit is contained in:
Denozordec
2026-07-09 21:39:20 +07:00
parent 1a142e68a9
commit a3f3ffd672
161 changed files with 17496 additions and 605 deletions
@@ -1,5 +1,3 @@
"use client"
import { useMemo, useState } from "react"
import { Badge } from "@/components/reui/badge"
import { Column } from "@tanstack/react-table"
@@ -1,3 +1,5 @@
"use client"
import { HTMLAttributes, memo, ReactNode, useMemo } from "react"
import {
getColumnHeaderLabel,
@@ -1,5 +1,3 @@
"use client"
import { ReactElement } from "react"
import { getColumnHeaderLabel } from "@/components/reui/data-grid/data-grid"
import { Table } from "@tanstack/react-table"
@@ -1,3 +1,5 @@
"use client"
import React, { ReactNode } from "react"
import { useDataGrid } from "@/components/reui/data-grid/data-grid"
@@ -1,5 +1,3 @@
"use client"
import {
PointerEvent,
ReactNode,
@@ -1,3 +1,5 @@
"use client"
import {
createContext,
CSSProperties,
@@ -1,5 +1,3 @@
"use client"
import {
CSSProperties,
Fragment,
@@ -1,3 +1,5 @@
"use client"
import {
CSSProperties,
memo,
@@ -1,5 +1,3 @@
"use client"
import {
CSSProperties,
Fragment,
@@ -1,3 +1,5 @@
"use client"
import { createContext, ReactNode, useContext, useMemo } from "react"
import {
Column,
+1 -8
View File
@@ -409,12 +409,6 @@ function FilterRemoveButton({
}: FilterRemoveButtonProps) {
const context = useFilterContext()
const sizeMap = {
sm: "sm" as const,
default: "sm" as const,
lg: "default" as const,
}
return (
<Button
variant="outline"
@@ -425,6 +419,7 @@ function FilterRemoveButton({
? "icon-lg"
: "icon"
}
className={className}
{...props}
>
{icon}
@@ -1009,8 +1004,6 @@ function FilterValueSelector<T = unknown>({
operator,
autoFocus,
}: FilterValueSelectorProps<T>) {
const context = useFilterContext()
if (operator === "empty" || operator === "not_empty") {
return null
}
@@ -0,0 +1,90 @@
import { cn } from "@evobgp/ui/lib/utils"
type IconStackProps = React.ComponentProps<"div">
function IconStack({ className, children, style, ...props }: IconStackProps) {
return (
<div
data-slot="icon-stack"
className={cn(
"text-foreground **:data-[slot=icon-stack-layer]:fill-background relative h-20 w-18",
className
)}
style={
{
"--icon-stack-content-x": "71%",
"--icon-stack-content-y": "58%",
...style,
} as React.CSSProperties
}
{...props}
>
<svg
aria-hidden="true"
viewBox="0 0 72 81"
fill="none"
className="h-full w-full overflow-visible"
>
<ellipse
cx="36"
cy="76"
rx="30"
ry="7"
fill="currentColor"
fillOpacity="0.055"
className="blur-[4px]"
/>
<IconStackLayer opacity="0.4" />
<IconStackLayer opacity="0.6" x={13.65} y={6.04} />
<IconStackLayer opacity="0.8" x={27.32} y={12.08} active />
</svg>
{children ? (
<div
data-slot="icon-stack-content"
className="text-muted-foreground pointer-events-none absolute top-[var(--icon-stack-content-y)] left-[var(--icon-stack-content-x)] flex -translate-x-1/2 -translate-y-1/2 scale-x-90 -skew-y-26 items-center justify-center"
>
{children}
</div>
) : null}
</div>
)
}
function IconStackLayer({
active = false,
opacity,
x = 0,
y = 0,
}: {
active?: boolean
opacity: string
x?: number
y?: number
}) {
return (
<g opacity={opacity} transform={`translate(${x} ${y})`}>
<path
data-slot="icon-stack-layer"
d="M42.2538 2.046C41.4408 1.6325 40.3965 1.6677 39.2612 2.2424L7.9616 18.1934C5.3895 19.5039 3.301 23.1064 3.301 26.2322V64.3226C3.301 66.0677 3.9458 67.2943 4.962 67.8199L1.8363 66.229C0.8201 65.7104 0.1753 64.4771 0.1753 62.732V24.6412C0.1753 21.5085 2.2638 17.913 4.8359 16.6024L36.1355 0.6515C37.2778 0.0698 38.322 0.0416 39.128 0.4551L42.2538 2.046Z"
stroke="currentColor"
strokeOpacity={active ? "0.3" : "0.2"}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
data-slot="icon-stack-layer"
d="M42.2545 2.0456C43.2707 2.5643 43.9155 3.7979 43.9155 5.543V43.6337C43.9155 46.7665 41.827 50.3616 39.2549 51.6722L7.9554 67.6235C6.813 68.2052 5.7687 68.2331 4.9628 67.8196C3.9465 67.301 3.3018 66.0673 3.3018 64.3222V26.2318C3.3018 23.0991 5.3903 19.5036 7.9624 18.193L39.2619 2.2421C40.4043 1.6604 41.4486 1.6321 42.2545 2.0456Z"
stroke="currentColor"
strokeOpacity={active ? "0.3" : "0.2"}
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
)
}
export { IconStack, type IconStackProps }
+166
View File
@@ -0,0 +1,166 @@
"use client"
import { useState } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@evobgp/ui/lib/utils"
import { StarIcon } from "lucide-react"
const ratingVariants = cva("flex items-center", {
variants: {
size: {
sm: "gap-2",
default: "gap-2.5",
lg: "gap-3",
},
},
defaultVariants: {
size: "default",
},
})
const starVariants = cva("", {
variants: {
size: {
sm: "w-4 h-4",
default: "w-5 h-5",
lg: "w-6 h-6",
},
},
defaultVariants: {
size: "default",
},
})
const valueVariants = cva("text-muted-foreground w-5", {
variants: {
size: {
sm: "text-xs",
default: "text-sm",
lg: "text-base",
},
},
defaultVariants: {
size: "default",
},
})
function Rating({
rating,
maxRating = 5,
size,
className,
starClassName,
showValue = false,
editable = false,
onRatingChange,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof ratingVariants> & {
/**
* Current rating value (supports decimal values for partial stars)
*/
rating: number
/**
* Maximum rating value (number of stars to show)
*/
maxRating?: number
/**
* Whether to show the numeric rating value
*/
showValue?: boolean
/**
* Class name for the value span
*/
starClassName?: string
/**
* Whether the rating is editable (clickable)
*/
editable?: boolean
/**
* Callback function called when rating changes
*/
onRatingChange?: (rating: number) => void
}) {
const [hoveredRating, setHoveredRating] = useState<number | null>(null)
const displayRating =
editable && hoveredRating !== null ? hoveredRating : rating
const handleStarClick = (starRating: number) => {
if (editable && onRatingChange) {
onRatingChange(starRating)
}
}
const handleStarMouseEnter = (starRating: number) => {
if (editable) {
setHoveredRating(starRating)
}
}
const handleStarMouseLeave = () => {
if (editable) {
setHoveredRating(null)
}
}
const renderStars = () => {
const stars = []
for (let i = 1; i <= maxRating; i++) {
const filled = displayRating >= i
const partiallyFilled = displayRating > i - 1 && displayRating < i
const fillPercentage = partiallyFilled
? (displayRating - (i - 1)) * 100
: 0
stars.push(
<div
key={i}
className={cn("relative", editable && "cursor-pointer")}
onClick={() => handleStarClick(i)}
onMouseEnter={() => handleStarMouseEnter(i)}
onMouseLeave={handleStarMouseLeave}
>
{/* Background star (empty) */}
<StarIcon data-slot="rating-star-empty" className={cn(starVariants({ size }), "text-muted-foreground/30")} />
{/* Filled star */}
<div
className="absolute inset-0 overflow-hidden"
style={{
width: filled ? "100%" : `${fillPercentage}%`,
}}
>
<StarIcon data-slot="rating-star-filled" className={cn(
starVariants({ size }),
"fill-yellow-400 text-yellow-400"
)} />
</div>
</div>
)
}
return stars
}
return (
<div
data-slot="rating"
className={cn(ratingVariants({ size }), className)}
{...props}
>
<div className="flex items-center">{renderStars()}</div>
{showValue && (
<span
data-slot="rating-value"
className={cn(valueVariants({ size }), starClassName)}
>
{displayRating.toFixed(1)}
</span>
)}
</div>
)
}
export { Rating }