mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 13:42:19 +00:00
feat(frontend): Complete shadcn migration of skeleton and styling components
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
committed by
Elisiário Couto
parent
bfb5a7ef76
commit
c83386b1d5
36
frontend/package-lock.json
generated
36
frontend/package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-progress": "^1.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
"react-day-picker": "^9.10.0",
|
"react-day-picker": "^9.10.0",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"recharts": "^3.2.0",
|
"recharts": "^3.2.0",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
@@ -1637,6 +1639,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-progress": {
|
||||||
|
"version": "1.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
|
||||||
|
"integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-primitive": "2.1.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-select": {
|
"node_modules/@radix-ui/react-select": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
|
||||||
@@ -5765,6 +5791,16 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sonner": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.7.6",
|
"version": "0.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-progress": "^1.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"react-day-picker": "^9.10.0",
|
"react-day-picker": "^9.10.0",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"recharts": "^3.2.0",
|
"recharts": "^3.2.0",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
|
|||||||
@@ -37,23 +37,23 @@ const getStatusIndicator = (status: string) => {
|
|||||||
};
|
};
|
||||||
case "pending":
|
case "pending":
|
||||||
return {
|
return {
|
||||||
color: "bg-yellow-500",
|
color: "bg-amber-500",
|
||||||
tooltip: "Pending",
|
tooltip: "Pending",
|
||||||
};
|
};
|
||||||
case "error":
|
case "error":
|
||||||
case "failed":
|
case "failed":
|
||||||
return {
|
return {
|
||||||
color: "bg-red-500",
|
color: "bg-destructive",
|
||||||
tooltip: "Error",
|
tooltip: "Error",
|
||||||
};
|
};
|
||||||
case "inactive":
|
case "inactive":
|
||||||
return {
|
return {
|
||||||
color: "bg-gray-500",
|
color: "bg-muted-foreground",
|
||||||
tooltip: "Inactive",
|
tooltip: "Inactive",
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
color: "bg-blue-500",
|
color: "bg-primary",
|
||||||
tooltip: status,
|
tooltip: status,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -201,8 +201,8 @@ export default function AccountsOverview() {
|
|||||||
{uniqueBanks}
|
{uniqueBanks}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-purple-100 dark:bg-purple-900/20 rounded-full">
|
<div className="p-3 bg-muted rounded-full">
|
||||||
<Building2 className="h-6 w-6 text-purple-600" />
|
<Building2 className="h-6 w-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Component } from "react";
|
import { Component } from "react";
|
||||||
import type { ErrorInfo, ReactNode } from "react";
|
import type { ErrorInfo, ReactNode } from "react";
|
||||||
import { AlertTriangle, RefreshCw } from "lucide-react";
|
import { AlertTriangle, RefreshCw } from "lucide-react";
|
||||||
|
import { Card, CardContent } from "./ui/card";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -39,46 +42,49 @@ class ErrorBoundary extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-lg shadow p-6">
|
<Card>
|
||||||
<div className="flex items-center justify-center text-center">
|
<CardContent className="p-6">
|
||||||
<div>
|
<div className="flex items-center justify-center text-center">
|
||||||
<AlertTriangle className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
<div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<AlertTriangle className="h-12 w-12 text-destructive mx-auto mb-4" />
|
||||||
Something went wrong
|
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||||
</h3>
|
Something went wrong
|
||||||
<p className="text-gray-600 mb-4">
|
</h3>
|
||||||
An error occurred while rendering this component. Please try
|
<p className="text-muted-foreground mb-4">
|
||||||
refreshing or check the console for more details.
|
An error occurred while rendering this component. Please try
|
||||||
</p>
|
refreshing or check the console for more details.
|
||||||
|
</p>
|
||||||
|
|
||||||
{this.state.error && (
|
{this.state.error && (
|
||||||
<div className="bg-red-50 border border-red-200 rounded-md p-3 mb-4 text-left">
|
<Alert variant="destructive" className="mb-4 text-left">
|
||||||
<p className="text-sm font-mono text-red-800">
|
<AlertTriangle className="h-4 w-4" />
|
||||||
<strong>Error:</strong> {this.state.error.message}
|
<AlertTitle>Error Details</AlertTitle>
|
||||||
</p>
|
<AlertDescription className="space-y-2">
|
||||||
{this.state.error.stack && (
|
<p className="text-sm font-mono">
|
||||||
<details className="mt-2">
|
<strong>Error:</strong> {this.state.error.message}
|
||||||
<summary className="text-sm text-red-600 cursor-pointer">
|
</p>
|
||||||
Stack trace
|
{this.state.error.stack && (
|
||||||
</summary>
|
<details className="mt-2">
|
||||||
<pre className="text-xs text-red-700 mt-1 whitespace-pre-wrap">
|
<summary className="text-sm cursor-pointer">
|
||||||
{this.state.error.stack}
|
Stack trace
|
||||||
</pre>
|
</summary>
|
||||||
</details>
|
<pre className="text-xs mt-1 whitespace-pre-wrap">
|
||||||
)}
|
{this.state.error.stack}
|
||||||
</div>
|
</pre>
|
||||||
)}
|
</details>
|
||||||
|
)}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<Button onClick={this.handleReset}>
|
||||||
onClick={this.handleReset}
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
Try Again
|
||||||
>
|
</Button>
|
||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
</div>
|
||||||
Try Again
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
|
import { Skeleton } from "./ui/skeleton";
|
||||||
|
import { Card, CardContent } from "./ui/card";
|
||||||
|
|
||||||
export default function FiltersSkeleton() {
|
export default function FiltersSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-lg shadow animate-pulse">
|
<Card>
|
||||||
<div className="px-6 py-4 border-b border-gray-200">
|
<div className="px-6 py-4 border-b border-border">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="h-6 bg-gray-200 rounded w-32"></div>
|
<Skeleton className="h-6 w-32" />
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="h-8 bg-gray-200 rounded w-24"></div>
|
<Skeleton className="h-8 w-24" />
|
||||||
<div className="h-8 bg-gray-200 rounded w-20"></div>
|
<Skeleton className="h-8 w-20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
<CardContent className="px-6 py-4 border-b border-border bg-muted/30">
|
||||||
{/* Quick Date Filters Skeleton */}
|
{/* Quick Date Filters Skeleton */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="h-4 bg-gray-200 rounded w-32 mb-3"></div>
|
<Skeleton className="h-4 w-32 mb-3" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<div className="h-10 bg-gray-200 rounded-lg w-24"></div>
|
<Skeleton className="h-10 w-24 rounded-lg" />
|
||||||
<div className="h-10 bg-gray-200 rounded-lg w-20"></div>
|
<Skeleton className="h-10 w-20 rounded-lg" />
|
||||||
<div className="h-10 bg-gray-200 rounded-lg w-28"></div>
|
<Skeleton className="h-10 w-28 rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<div className="h-10 bg-gray-200 rounded-lg w-24"></div>
|
<Skeleton className="h-10 w-24 rounded-lg" />
|
||||||
<div className="h-10 bg-gray-200 rounded-lg w-20"></div>
|
<Skeleton className="h-10 w-20 rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,40 +34,40 @@ export default function FiltersSkeleton() {
|
|||||||
{/* Filter Fields Skeleton */}
|
{/* Filter Fields Skeleton */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div className="sm:col-span-2 lg:col-span-1">
|
<div className="sm:col-span-2 lg:col-span-1">
|
||||||
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div>
|
<Skeleton className="h-4 w-16 mb-1" />
|
||||||
<div className="h-10 bg-gray-200 rounded"></div>
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div>
|
<Skeleton className="h-4 w-16 mb-1" />
|
||||||
<div className="h-10 bg-gray-200 rounded"></div>
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div>
|
<Skeleton className="h-4 w-20 mb-1" />
|
||||||
<div className="h-10 bg-gray-200 rounded"></div>
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div>
|
<Skeleton className="h-4 w-16 mb-1" />
|
||||||
<div className="h-10 bg-gray-200 rounded"></div>
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Amount Range Filters Skeleton */}
|
{/* Amount Range Filters Skeleton */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div>
|
<Skeleton className="h-4 w-20 mb-1" />
|
||||||
<div className="h-10 bg-gray-200 rounded"></div>
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div>
|
<Skeleton className="h-4 w-20 mb-1" />
|
||||||
<div className="h-10 bg-gray-200 rounded"></div>
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
|
|
||||||
{/* Results Summary Skeleton */}
|
{/* Results Summary Skeleton */}
|
||||||
<div className="px-6 py-3 bg-gray-50 border-b border-gray-200">
|
<CardContent className="px-6 py-3 bg-muted/30 border-b border-border">
|
||||||
<div className="h-4 bg-gray-200 rounded w-48"></div>
|
<Skeleton className="h-4 w-48" />
|
||||||
</div>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,15 +49,15 @@ export default function Header({ setSidebarOpen }: HeaderProps) {
|
|||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
{healthLoading ? (
|
{healthLoading ? (
|
||||||
<>
|
<>
|
||||||
<Activity className="h-4 w-4 text-yellow-500 animate-pulse" />
|
<Activity className="h-4 w-4 text-muted-foreground animate-pulse" />
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
Checking...
|
Checking...
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : healthError || healthStatus?.status !== "healthy" ? (
|
) : healthError || healthStatus?.status !== "healthy" ? (
|
||||||
<>
|
<>
|
||||||
<WifiOff className="h-4 w-4 text-red-500" />
|
<WifiOff className="h-4 w-4 text-destructive" />
|
||||||
<span className="text-sm text-red-500">Disconnected</span>
|
<span className="text-sm text-destructive">Disconnected</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { RefreshCw } from "lucide-react";
|
import { RefreshCw } from "lucide-react";
|
||||||
|
import { cn } from "../lib/utils";
|
||||||
|
|
||||||
interface LoadingSpinnerProps {
|
interface LoadingSpinnerProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LoadingSpinner({
|
export default function LoadingSpinner({
|
||||||
message = "Loading...",
|
message = "Loading...",
|
||||||
|
className,
|
||||||
}: LoadingSpinnerProps) {
|
}: LoadingSpinnerProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className={cn("flex items-center justify-center p-8", className)}>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<RefreshCw className="h-8 w-8 animate-spin text-blue-600 mx-auto mb-2" />
|
<RefreshCw className="h-8 w-8 animate-spin text-primary mx-auto mb-2" />
|
||||||
<p className="text-gray-600 text-sm">{message}</p>
|
<p className="text-muted-foreground text-sm">{message}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
|||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Label } from "./ui/label";
|
import { Label } from "./ui/label";
|
||||||
|
import { Badge } from "./ui/badge";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -233,12 +234,8 @@ export default function Notifications() {
|
|||||||
{service.name}
|
{service.name}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex items-center space-x-2 mt-1">
|
<div className="flex items-center space-x-2 mt-1">
|
||||||
<span
|
<Badge
|
||||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
variant={service.enabled ? "default" : "destructive"}
|
||||||
service.enabled
|
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{service.enabled ? (
|
{service.enabled ? (
|
||||||
<CheckCircle className="h-3 w-3 mr-1" />
|
<CheckCircle className="h-3 w-3 mr-1" />
|
||||||
@@ -246,18 +243,14 @@ export default function Notifications() {
|
|||||||
<AlertCircle className="h-3 w-3 mr-1" />
|
<AlertCircle className="h-3 w-3 mr-1" />
|
||||||
)}
|
)}
|
||||||
{service.enabled ? "Enabled" : "Disabled"}
|
{service.enabled ? "Enabled" : "Disabled"}
|
||||||
</span>
|
</Badge>
|
||||||
<span
|
<Badge
|
||||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
variant={service.configured ? "secondary" : "outline"}
|
||||||
service.configured
|
|
||||||
? "bg-blue-100 text-blue-800"
|
|
||||||
: "bg-yellow-100 text-yellow-800"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{service.configured
|
{service.configured
|
||||||
? "Configured"
|
? "Configured"
|
||||||
: "Not Configured"}
|
: "Not Configured"}
|
||||||
</span>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Skeleton } from "./ui/skeleton";
|
||||||
|
import { Card } from "./ui/card";
|
||||||
|
|
||||||
interface TransactionSkeletonProps {
|
interface TransactionSkeletonProps {
|
||||||
rows?: number;
|
rows?: number;
|
||||||
view?: "table" | "mobile";
|
view?: "table" | "mobile";
|
||||||
@@ -11,93 +14,89 @@ export default function TransactionSkeleton({
|
|||||||
|
|
||||||
if (view === "mobile") {
|
if (view === "mobile") {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-lg shadow divide-y divide-gray-200">
|
<Card className="divide-y divide-border">
|
||||||
{skeletonRows.map((_, index) => (
|
{skeletonRows.map((_, index) => (
|
||||||
<div key={index} className="p-4 animate-pulse">
|
<div key={index} className="p-4">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className="p-2 rounded-full bg-gray-200 flex-shrink-0">
|
<Skeleton className="h-10 w-10 rounded-full flex-shrink-0" />
|
||||||
<div className="h-4 w-4 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0 space-y-2">
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
<Skeleton className="h-4 w-3/4" />
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
<Skeleton className="h-3 w-1/2" />
|
||||||
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
|
<Skeleton className="h-3 w-2/3" />
|
||||||
<div className="h-3 bg-gray-200 rounded w-1/3"></div>
|
<Skeleton className="h-3 w-1/3" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right ml-3 flex-shrink-0 space-y-2">
|
<div className="text-right ml-3 flex-shrink-0 space-y-2">
|
||||||
<div className="h-6 bg-gray-200 rounded w-20"></div>
|
<Skeleton className="h-6 w-20" />
|
||||||
<div className="h-4 bg-gray-200 rounded w-16 ml-auto"></div>
|
<Skeleton className="h-4 w-16 ml-auto" />
|
||||||
<div className="h-6 bg-gray-200 rounded w-12 ml-auto"></div>
|
<Skeleton className="h-6 w-12 ml-auto" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-lg shadow overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-border">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-muted/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left">
|
<th className="px-6 py-3 text-left">
|
||||||
<div className="h-4 bg-gray-200 rounded w-20 animate-pulse"></div>
|
<Skeleton className="h-4 w-20" />
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left">
|
<th className="px-6 py-3 text-left">
|
||||||
<div className="h-4 bg-gray-200 rounded w-16 animate-pulse"></div>
|
<Skeleton className="h-4 w-16" />
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left">
|
<th className="px-6 py-3 text-left">
|
||||||
<div className="h-4 bg-gray-200 rounded w-12 animate-pulse"></div>
|
<Skeleton className="h-4 w-12" />
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left">
|
<th className="px-6 py-3 text-left">
|
||||||
<div className="h-4 bg-gray-200 rounded w-8 animate-pulse"></div>
|
<Skeleton className="h-4 w-8" />
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-card divide-y divide-border">
|
||||||
{skeletonRows.map((_, index) => (
|
{skeletonRows.map((_, index) => (
|
||||||
<tr key={index} className="animate-pulse">
|
<tr key={index}>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className="p-2 rounded-full bg-gray-200 flex-shrink-0">
|
<Skeleton className="h-10 w-10 rounded-full flex-shrink-0" />
|
||||||
<div className="h-4 w-4 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 space-y-2">
|
<div className="flex-1 space-y-2">
|
||||||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
<Skeleton className="h-4 w-3/4" />
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
<Skeleton className="h-3 w-1/2" />
|
||||||
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
|
<Skeleton className="h-3 w-2/3" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="h-6 bg-gray-200 rounded w-24 ml-auto mb-1"></div>
|
<Skeleton className="h-6 w-24 ml-auto mb-1" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="h-4 bg-gray-200 rounded w-20"></div>
|
<Skeleton className="h-4 w-20" />
|
||||||
<div className="h-3 bg-gray-200 rounded w-16"></div>
|
<Skeleton className="h-3 w-16" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="h-6 bg-gray-200 rounded w-12"></div>
|
<Skeleton className="h-6 w-12" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
26
frontend/src/components/ui/progress.tsx
Normal file
26
frontend/src/components/ui/progress.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
));
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Progress };
|
||||||
138
frontend/src/components/ui/sheet.tsx
Normal file
138
frontend/src/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root;
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger;
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close;
|
||||||
|
|
||||||
|
const SheetPortal = SheetPrimitive.Portal;
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||||
|
bottom:
|
||||||
|
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||||
|
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||||
|
right:
|
||||||
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
interface SheetContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
|
SheetContentProps
|
||||||
|
>(({ side = "right", className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(sheetVariants({ side }), className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
));
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const SheetHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
SheetHeader.displayName = "SheetHeader";
|
||||||
|
|
||||||
|
const SheetFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
SheetFooter.displayName = "SheetFooter";
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
};
|
||||||
14
frontend/src/components/ui/skeleton.tsx
Normal file
14
frontend/src/components/ui/skeleton.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||||
|
|
||||||
|
function Skeleton({ className, ...props }: SkeletonProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton };
|
||||||
27
frontend/src/components/ui/sonner.tsx
Normal file
27
frontend/src/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Toaster as Sonner } from "sonner";
|
||||||
|
|
||||||
|
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
className="toaster group"
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast:
|
||||||
|
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||||
|
description: "group-[.toast]:text-muted-foreground",
|
||||||
|
actionButton:
|
||||||
|
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||||
|
cancelButton:
|
||||||
|
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Toaster };
|
||||||
Reference in New Issue
Block a user